Skip to content
DevOps AI ToolKit
Newsletter
All guides
AI for Linux Admins By James Joyner IV · · 9 min read Last reviewed Jul 2026

Linux Error Guide: 'open: Permission denied' — Fix File and Directory Access

Quick answer

Fix the Linux open Permission denied EACCES error by checking mode bits, ownership, parent directory execute bits, ACLs, SELinux, and AppArmor denials.

  • #linux
  • #troubleshooting
  • #errors
  • #permissions
Free toolkit

Stuck on this Linux Admins error? Get the free incident triage checklist

A one-page PDF — the exact steps to isolate, fix, and verify a production error like this one. No spam, unsubscribe anytime.

Overview

Few messages are as common, or as deceptively simple, as a permission failure when a process tries to touch a file. The kernel returns the EACCES errno, and userland tools print it like this:

open: Permission denied

You will see the same underlying error surface as open('/path'): Permission denied, cannot open '/path' for reading: Permission denied, or in Python as PermissionError: [Errno 13] Permission denied. In every case the syscall (open, openat, read, or write) was rejected with errno 13.

The trap is assuming this is always about the file’s own mode bits. In practice EACCES has at least six distinct root causes: the file’s mode, the file’s ownership, a missing execute bit on a parent directory that blocks path traversal, POSIX ACLs, an SELinux AVC denial, or an AppArmor profile denial. This guide walks through each one and gives you a repeatable workflow to identify which is biting you.

Symptoms

  • A service fails to start and its log shows open(...): Permission denied for a config file, socket, or log path.
  • A user can ls a directory but cannot cat a file inside it, or vice versa.
  • The file mode looks correct (-rw-r--r--) yet reads still fail.
  • The command works as root but not as the service account, or works when SELinux is set to permissive.
  • strace shows the failing syscall returning -1 EACCES (Permission denied).
  • On SELinux systems, /var/log/audit/audit.log gains new type=AVC lines the instant you reproduce the failure.

Common Root Causes

  1. Mode bits. The requesting user lacks the read (r) or write (w) permission in the applicable owner/group/other class.
  2. Ownership. The file is owned by another user or group, so the process falls into the other class with fewer rights than expected.
  3. Missing execute bit on a parent directory. To reach /a/b/c/file, the process needs the execute (search, x) bit on /a, /a/b, and /a/b/c. A single missing x anywhere in the chain yields EACCES even if the file itself is world-readable.
  4. POSIX ACLs. An extended ACL (shown by a + in ls -l) can grant or deny access independently of the classic mode bits.
  5. SELinux. The DAC check passes but the mandatory access control layer denies the operation because the file’s security context does not match the process domain’s policy, logged as an AVC.
  6. AppArmor. On Debian/Ubuntu/SUSE, a confined process may be blocked by its profile even though DAC and ownership are fine.

Diagnostic Workflow

Start with the classic discretionary checks, then escalate to mandatory access control.

Inspect the file and confirm who you are:

ls -l /path/to/file
stat /path/to/file
id

Check every parent directory in one shot. namei -l resolves the full path and prints the mode of each component, which is the fastest way to spot a missing x bit mid-chain:

namei -l /path/to/file

Look for ACLs. A trailing + on the mode string (for example -rw-r-----+) means an ACL is present:

getfacl /path/to/file

If DAC looks correct, suspect SELinux. Check the label and recent denials:

getenforce
ls -Z /path/to/file
sudo ausearch -m avc -ts recent
sudo sealert -a /var/log/audit/audit.log

As a quick, reversible test, flip SELinux to permissive and retry. If the operation now succeeds, the root cause is a labeling or policy issue, not DAC:

sudo setenforce 0
# retry the failing operation
sudo setenforce 1

On AppArmor systems, check status and the kernel ring buffer for DENIED lines that name the profile and path:

sudo aa-status
sudo dmesg | grep -i apparmor

Example Root Cause Analysis

A team reported that nginx returned 403s while serving files from a freshly created /srv/webapp/public directory. The files were -rw-r--r-- and owned by the deploy user, so mode and ownership looked fine.

namei -l /srv/webapp/public/index.html told a different story:

namei -l /srv/webapp/public/index.html

The output showed /srv/webapp as drwxr-x---, owned by deploy:deploy. The nginx worker runs as user nginx, which fell into the other class and therefore had no execute (search) bit on /srv/webapp. It could not traverse into public at all, so every read failed with EACCES before the file’s own permissive mode ever mattered.

The fix was to grant traversal on the parent, without exposing a listing:

sudo chmod o+x /srv/webapp

Granting only the x bit (not r) lets processes pass through the directory to reach known paths while still preventing them from listing its contents. After this change the 403s stopped. Had ls -Z shown a mislabeled context instead, the correct fix would have been restorecon rather than a mode change.

Prevention Best Practices

  • Fix ownership and mode, not just mode. When a service cannot read its data, set both: chown -R app:app /var/lib/app and chmod 750 /var/lib/app.
  • Grant directory traversal deliberately. Use chmod o+x (or a group with x) on parent directories rather than making files world-readable to work around a missing search bit.
  • Prefer ACLs over loosening base permissions. When one extra account needs access, setfacl -m u:backup:r-- /path is safer than widening the other class.
  • Relabel instead of disabling SELinux. If a file lives in the wrong context, restorecon -Rv /path restores the default policy label; use chcon only for one-off tests. Leaving SELinux enforcing is the secure default.
  • Extend, do not disable, AppArmor profiles. Put a profile into aa-complain while you gather the real denials, then translate them into explicit allow rules and return to enforce mode.
  • Reproduce as the real user. Run failing commands with sudo -u serviceuser so you test the exact effective UID and groups the service uses.

Quick Command Reference

# Discretionary access control
ls -l /path/to/file          # mode, owner, group
namei -l /path/to/file       # mode of every parent component
stat /path/to/file           # detailed metadata
id                           # effective uid/gid/groups
getfacl /path/to/file        # POSIX ACL entries

# Fixes for DAC
chmod u+rw,g+r /path/to/file
chown app:app /path/to/file
chmod o+x /parent/dir        # allow traversal only
setfacl -m u:name:r-- /path  # grant one user read

# SELinux
getenforce
ls -Z /path/to/file
sudo ausearch -m avc -ts recent
sudo sealert -a /var/log/audit/audit.log
sudo restorecon -Rv /path/to/file
sudo setenforce 0            # temporary permissive test

# AppArmor
sudo aa-status
sudo dmesg | grep -i apparmor
sudo aa-complain /etc/apparmor.d/usr.sbin.app

Conclusion

open: Permission denied is not one problem but a family of them, and the fastest path to a fix is to rule out causes in order. Confirm the file’s mode and ownership, then use namei -l to catch the classic missing execute bit on a parent directory. If discretionary checks all pass, move to mandatory access control: inspect ls -Z and ausearch for SELinux AVCs, or aa-status and dmesg for AppArmor denials. Relabel with restorecon and extend profiles rather than disabling security layers wholesale. Working through these layers turns a vague errno 13 into a precise, one-line fix every time.

Free download · 368-page PDF

Fixed it? Get 500 Linux Admins & DevOps AI prompts — free

500 battle-tested, copy-paste AI prompts engineered by a senior systems engineer — every one with fill-in placeholders and safety/back-out notes. Drop your email and it's yours.

  • 500 prompts: Linux · Kubernetes · Terraform · OpenStack · GitLab · Docker · Monitoring · Incident Response
  • Instant PDF download — yours free, forever
  • Plus one practical AI-workflow email a week (no spam)

Single opt-in · unsubscribe anytime · no spam.

Did this fix your issue?

Free download · 368-page PDF

Get 500 Battle-Tested DevOps AI Prompts — Free

500 battle-tested, copy-paste AI prompts engineered by a senior systems engineer — every one with fill-in placeholders and safety/back-out notes. Drop your email and it's yours.

  • 500 prompts: Linux · Kubernetes · Terraform · OpenStack · GitLab · Docker · Monitoring · Incident Response
  • Instant PDF download — yours free, forever
  • Plus one practical AI-workflow email a week (no spam)

Single opt-in · unsubscribe anytime · no spam.