Fix PostgreSQL 'could not open file for reading: Permission denied' with COPY Command

beginner๐Ÿ˜ PostgreSQL2026-04-28| PostgreSQL 12โ€“16, Linux (Ubuntu, Debian, CentOS, RHEL), macOS

Error Message

ERROR: could not open file "/var/data/import.csv" for reading: Permission denied
#postgresql#copy#permission#csv#import

The Error

You run a COPY command to import a CSV file and get this:

ERROR:  could not open file "/var/data/import.csv" for reading: Permission denied

The file is sitting right there. You can cat it just fine. PostgreSQL still refuses. That's because PostgreSQL reads the file as the postgres OS user โ€” not as you. And postgres has no permission to touch that path.

Why This Happens

Server-side COPY FROM runs under the postgres system account. Two things trip people up:

  • The file isn't readable by postgres.
  • A parent directory in the path blocks postgres from traversing it โ€” no execute (x) bit. The file could be 777 and it still won't work if /var/data/ is mode 700 owned by another user.

Note: this is server-side COPY, not \copy. The psql client command \copy runs as your user. Different beast entirely โ€” more on that below.

Step-by-Step Fix

Step 1 โ€” Check who owns the file

ls -la /var/data/import.csv
ls -la /var/data/

Typical output on a fresh Ubuntu server:

-rw------- 1 ubuntu ubuntu 204800 Apr 27 10:00 /var/data/import.csv
drwx------ 2 ubuntu ubuntu   4096 Apr 27 09:55 /var/data/

Mode 700 on the directory means only ubuntu can enter it. The postgres user is blocked before it even reaches the file.

Step 2 โ€” Confirm what user PostgreSQL runs as

ps aux | grep postgres | head -3

The first column is the OS user. On most systems it's postgres, but some custom installs use a different account โ€” worth verifying.

Step 3 โ€” Grant read permission on the file

chmod o+r /var/data/import.csv

Equivalent numeric form:

chmod 644 /var/data/import.csv

Step 4 โ€” Grant execute permission on every directory in the path

PostgreSQL has to traverse every directory in the path, not just the last one. For /var/data/import.csv, both /var and /var/data need the execute bit for postgres. Most systems leave /var world-executable by default, but /var/data is often locked down:

chmod o+x /var/data/

Deeper paths like /home/ubuntu/data/imports/ need o+x on each segment. Go through them one by one.

Step 5 โ€” Retry the COPY command

COPY your_table FROM '/var/data/import.csv' WITH (FORMAT csv, HEADER true);

No more permission errors.

Alternative: Move the File to /tmp

Don't want to open up your directory? Just copy the file somewhere PostgreSQL can already reach:

cp /var/data/import.csv /tmp/import.csv
chmod 644 /tmp/import.csv

Then run the import from there:

COPY your_table FROM '/tmp/import.csv' WITH (FORMAT csv, HEADER true);

Clean up when done:

rm /tmp/import.csv

Quick and low-risk for one-off imports.

Alternative: Switch to \copy

Running queries through psql? Drop the SQL COPY and use \copy instead:

\copy your_table FROM '/var/data/import.csv' WITH (FORMAT csv, HEADER true);

It runs client-side as your own user. No server-side permission issues. For large files โ€” say, 500 MB+ โ€” there's a slight performance cost since data travels through the client connection. Under 100 MB, you won't notice the difference.

Connecting to a remote PostgreSQL server with the CSV on your local machine? \copy is the only option anyway, since server-side COPY can't reach your laptop's filesystem.

Verify Before You Import

Fastest sanity check โ€” impersonate the postgres user and try reading the file directly:

sudo -u postgres cat /var/data/import.csv | head -5

Five lines printed without errors? PostgreSQL can read it. Still seeing Permission denied? A directory in the path still has the wrong permissions โ€” revisit Step 4.

Tips

Double-check your chmod values visually

Octal modes like 644 vs 640 are easy to confuse, especially when you need different access for owner, group, and others. The Unix Permissions Calculator on ToolCraft lets you see exactly what a chmod value grants before you apply it โ€” much faster than decoding octal in your head.

Prefer group ownership over world-readable

Opening files to o+r means every user on the system can read them. Tighter approach: create a shared group, add postgres to it, and use group permissions instead.

# Create a shared group and add postgres to it
sudo groupadd dataimport
sudo usermod -aG dataimport postgres

# Set group ownership
chown :dataimport /var/data/
chown :dataimport /var/data/import.csv

# Only owner and group can access
chmod 750 /var/data/
chmod 640 /var/data/import.csv

Other users on the box can't touch the files. Much cleaner in shared environments.

For recurring imports, use a dedicated staging directory

If you're importing regularly, set up a directory that postgres owns outright:

sudo mkdir -p /var/pgimport
sudo chown postgres:postgres /var/pgimport
sudo chmod 700 /var/pgimport

Drop files there before every import. Permission errors become a non-issue โ€” postgres owns the whole directory.

COPY vs \copy โ€” quick reference

  • COPY โ€” SQL command. Runs on the database server. Uses postgres OS user permissions. File must exist on the server.
  • \copy โ€” psql meta-command. Runs on your client machine. Uses your OS user permissions. File can be anywhere on your local machine.

Related Error Notes