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
postgresfrom traversing it โ no execute (x) bit. The file could be 777 and it still won't work if/var/data/is mode700owned 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. UsespostgresOS 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.

