could not open file for writing: Permission denied

Fix PostgreSQL VACUUM FULL Permission Denied Error

VACUUM FULL fails with permission denied because PostgreSQL can't write new table files. Usually a directory ownership or SELinux issue. Fix it fast.

You run VACUUM FULL and it dies with ERROR: could not open file for writing: Permission denied. Same thing every time. You check the obvious stuff — file ownership, user context — and it still fails. Let's cut through the noise.

This error means PostgreSQL tried to create or rewrite a table file in your data directory and the OS said no. The cause is almost always one of three things: the PGDATA directory or its subdirectories have wrong ownership, SELinux is blocking writes, or your systemd service has ProtectSystem or ReadOnlyPaths set. I'll go from most common to least.

Cause 1: Data directory ownership is wrong

The most common culprit. Someone (maybe you, maybe a script) changed ownership of PGDATA or a subdirectory. PostgreSQL needs to write to base/, pg_wal/, and global/. If any of those are owned by root or another user, VACUUM FULL bombs out.

Here's the trigger: you moved PGDATA to a new mount point, or restored from backup using tar with default permissions. The files land with UID/GID of whatever user ran the extraction.

Check ownership:

ls -la $PGDATA | head -20

You want everything owned by the postgres user (or whatever user the server runs as). If not, fix it:

chown -R postgres:postgres $PGDATA
chmod 700 $PGDATA

Don't bother with chmod beyond 700. The data directory doesn't need group or other read access. If groups need to read for monitoring, use ACLs, but keep the base perms tight.

But wait — sometimes the ownership looks right and it still fails. That's when you check the actual table file. Use pg_relation_filepath() to see where the table lives:

SELECT pg_relation_filepath('your_table');

Then check that directory's ownership. If the parent dir is owned by someone else, you'll get the error even if the table file itself is fine.

Cause 2: SELinux blocking writes

Second most common in my experience. You're on RHEL, CentOS, or Fedora. SELinux is enforcing, and the file context on the data directory is wrong. Maybe you moved the data directory to a non-standard path like /data/postgres and didn't set the correct context.

The error message doesn't mention SELinux — it just says permission denied. Classic. And yes, ls -la will show correct ownership, but SELinux is separate.

Check if SELinux is blocking:

ausearch -m avc -ts recent | grep postgres

If you see denials, you've got two options:

  1. Set the correct context (the real fix).
  2. Turn off SELinux (don't do this unless you have to).

For a standard data directory, the context is already set by the RPM package. But if you moved things, run:

semanage fcontext -a -t postgresql_db_t "/data/postgres(/.*)?"
restorecon -Rv /data/postgres

Replace /data/postgres with your actual PGDATA path. If semanage isn't installed, install policycoreutils-python-utils (RHEL 8+) or policycoreutils-python (RHEL 7).

After that, check with ls -Z $PGDATA — you should see postgresql_db_t on the directories.

Don't bother checking getsebool for postgresql_can_network — that's for network access, not file writes. I've seen people waste an hour on that.

Cause 3: systemd hardening directives

Less common, but growing because more folks deploy with hardened systemd units. If you're on PostgreSQL 12+ and using a custom systemd service (or you've added hardening to the stock one), check for these:

systemctl cat postgresql

Look for ProtectSystem=, ReadOnlyPaths=, or ReadWritePaths=. If ProtectSystem=full or strict, it makes /usr, /boot, /etc read-only. But your data directory is usually under /var/lib/postgresql, which is still writable unless you set ReadOnlyPaths to include it.

The error shows up specifically with VACUUM FULL because it creates a new file and then removes the old one. A plain UPDATE might write to an existing file without needing to create a new one — that's why you only see it during VACUUM FULL or CLUSTER.

Fix: Edit your unit file and add the data directory to ReadWritePaths:

ReadWritePaths=/var/lib/postgresql/15/main

Then reload and restart:

systemctl daemon-reload
systemctl restart postgresql

If you're not sure if your distro's unit file has these directives, check. Debian and Ubuntu ship fairly plain unit files, but RHEL's is basic too. It's more common in containers or hardened setups.

Quick-reference summary

CauseCheckFix
Wrong ownershipls -la $PGDATAchown -R postgres:postgres $PGDATA
SELinux contextausearch -m avc | grep postgressemanage fcontext -a -t postgresql_db_t + restorecon
systemd hardeningsystemctl cat postgresqlAdd ReadWritePaths

One more thing — if you've checked all three and still see the error, look at disk space. A full disk can produce permission denied messages on some filesystems because writes fail with ENOSPC being masked. df -h and df -i to rule that out.

That covers the real-world cases I've hit. Start with ownership, then SELinux, then systemd. You'll fix it fast.

Related Errors in Database Errors
0X00001A3B Fix ERROR_TRANSACTION_NOT_FOUND (0x00001A3B) in Windows Cannot open database '%.*ls' requested by the login Fix 'Cannot open database' error in SQL Server fast WAL retention due to replication slot overflow Database Replication Slot Overflow Fix 1194 Fix MySQL 'Table Marked as Crashed' Error Fast

Was this solution helpful?

EP
Erropedia Team
Tech Support Editors
The Erropedia editorial team researches and documents real-world tech errors from across Windows, Linux, macOS, networking, databases, cloud platforms, and more. Every solution is reviewed for accuracy and updated as software and systems evolve.