Control panels

Linux file system permissions: ACL vs standard permissions

Standard Unix permissions give a file one owner, one group and one set of bits for everyone else. POSIX ACLs add per-user and per-group rules on top, and this guide shows you how to read, set and debug them.

AdvancedLinux server7 min read

Standard Linux permissions give every file exactly one owner, one group and one set of read/write/execute bits for everybody else, while POSIX ACLs add extra per-user and per-group rules on top of those bits. This guide walks through how the kernel actually decides access, how to read both models with ls, stat and getfacl, how to set ACLs with setfacl, and how to fix the mask problems that make an ACL entry look like it is being ignored.

Before you start

You need:

  • A Linux VPS running Ubuntu 22.04/24.04, Debian 12, AlmaLinux 8–9 or Rocky 8–9, with root or sudo access.
  • A filesystem that supports ACLs. ext4, XFS and Btrfs all do. Check yours before you start.
  • The getfacl and setfacl tools. They ship in the acl package. On Debian and Ubuntu run sudo apt install acl; on AlmaLinux and Rocky run sudo dnf install acl. RHEL-family images usually have it already.
  • Two test accounts so you can prove the rules work. If you do not have them yet, see add a new user on Linux.

Work in a scratch directory such as /srv/share so you never experiment on live data.

How standard permissions decide access

Every file has a mode made of nine bits: three for the owner, three for the group, three for others. Look at one:

Bash
ls -l /srv/share/report.txt
stat -c '%A %a %U:%G %n' /srv/share/report.txt
-rw-r----- 1 root devs 412 Jan  1 09:00 /srv/share/report.txt
640 root:devs /srv/share/report.txt

The kernel checks in a fixed order:

  1. If your UID matches the file owner, the owner bits apply. Group and other bits are ignored.
  2. Otherwise, if your GID or any of your supplementary groups matches the file group, the group bits apply.
  3. Otherwise the other bits apply.

That order is the whole limitation. Only one group can be granted access at a time, and you cannot give a single extra user read access without either adding them to the group or loosening the bits for everyone. That is the gap ACLs fill.

What ACLs add

A POSIX ACL is a list of entries attached to the file. It always contains the three base entries (user::, group::, other::) plus any named entries you add, and a mask:: entry that caps the effective permissions of named users, named groups and the owning group.

There are two kinds:

  • Access ACL — controls who can open the file right now.
  • Default ACL — only on directories, and only inherited by files and subdirectories created inside.

When a file has an ACL, ls -l shows a + after the mode:

-rw-r-----+ 1 root devs 412 Jan  1 09:00 /srv/share/report.txt

The group field in that listing shows the mask, not the owning group's permissions. That surprises people, so always confirm with getfacl.

Check ACL support on your filesystem

Bash
findmnt -no SOURCE,FSTYPE,OPTIONS /
/dev/vda1 ext4 rw,relatime,errors=remount-ro

If you see noacl in the options, ACLs are disabled for that mount. Remove noacl from the matching line in /etc/fstab, then remount.

Warning: Remounting the root filesystem briefly interrupts writes. Do it from the console in your VPS provider's control panel if SSH drops, and never edit /etc/fstab without testing with mount -a first.

Bash
sudo mount -o remount /
findmnt -no OPTIONS /

Read existing ACLs with getfacl

Bash
getfacl /srv/share/report.txt
# file: srv/share/report.txt
# owner: root
# group: devs
user::rw-
user:deploy:rw-
group::r--
mask::rw-
other::r--

Read it line by line: user:: is the owner, user:deploy:rw- is a named entry, group:: is the owning group, mask:: is the ceiling, other:: is everyone else. If an entry is limited by the mask, getfacl adds a comment such as # effective:r--.

Useful flags: getfacl -c drops the comment header, getfacl -t prints tab-separated output, and getfacl -R /srv/share walks a whole tree.

Set ACLs with setfacl

Grant one user write access without touching the mode or the group:

Bash
sudo setfacl -m u:deploy:rw /srv/share/report.txt
sudo setfacl -m u:alice:r-- /srv/share/report.txt

Grant a group read and execute on a directory:

Bash
sudo setfacl -m g:devs:rx /srv/share

Set several entries at once, comma separated:

Bash
sudo setfacl -m u:deploy:rwx,u:alice:r-x,g:devs:rx /srv/share

Verify immediately:

Bash
getfacl -c /srv/share/report.txt
ls -l /srv/share/report.txt

Tip: setfacl -m recalculates the mask automatically as the union of all entries. If you want the mask left alone, add -n.

Control the mask

The mask is the most common reason an ACL "does not work". A named entry can never exceed it.

Bash
sudo setfacl -m m::rx /srv/share
getfacl -c /srv/share

Two rules to remember:

  • chmod g-w file changes the mask, not the owning group's entry, on a file that already has an ACL.
  • setfacl -m m::rw file sets the mask explicitly without touching anything else.

If getfacl shows # effective:r-- next to user:deploy:rw-, the mask is the blocker.

Default ACLs for new files

Default entries live on directories and are copied into every new file and subdirectory created inside.

Bash
sudo setfacl -m d:u:deploy:rwx,d:g:devs:rx /srv/share
sudo setfacl -m d:m::rx /srv/share
getfacl -c /srv/share

Entries prefixed with d: are the default ACL; the same entries without the prefix are the access ACL on the directory itself. Setting a default ACL also sets an access ACL, so check both.

Note that the kernel strips execute bits from inherited default entries on regular files, so a d:u:deploy:rwx rule gives a new text file rw-, not rwx.

Remove and copy ACLs

Remove one entry, then all of them:

Bash
sudo setfacl -x u:deploy /srv/share/report.txt
sudo setfacl -b /srv/share/report.txt

Warning: setfacl -b deletes every extended entry on the target. Combined with -R on a directory it can silently revoke access for real users. Back up first, as shown below.

Back up and restore a tree:

Bash
sudo getfacl -R -p /srv/share > /root/share-acl.bak
cd /
sudo setfacl --restore=/root/share-acl.bak

When you copy data, preserve ACLs explicitly: rsync -aA, cp -a, or tar --acls. A plain cp or scp drops them.

Check it works

Test as the real users rather than as root, because root bypasses most checks.

Bash
sudo -u alice cat /srv/share/report.txt
sudo -u alice touch /srv/share/newfile.txt
getfacl -c /srv/share/newfile.txt
ls -l /srv/share

Expected: alice reads the file, the new file inherits the default entries, and the directory listing shows + on the ACL-protected items. If alice gets Permission denied, run getfacl again and look at the mask.

Troubleshooting

  • setfacl: file: Operation not supported — the filesystem was mounted with noacl, or it is a type that does not carry POSIX ACLs (some network mounts). Check findmnt -no OPTIONS / and remove noacl from /etc/fstab.
  • ACL entry exists but the user is still denied — the mask is lower than the entry. Compare mask:: with the named entry in getfacl and raise it with setfacl -m m::rw.
  • New files in a shared directory do not inherit access — you set an access ACL but no default ACL. Add the same entries with the d: prefix on the directory.
  • Permissions changed after a chmod — chmod rewrites the mask on files that have ACLs. Re-apply the mask with setfacl -m m:: after any scripted chmod.
  • ACLs vanished after a backup or migration — the copy tool did not preserve them. Use rsync -aA, tar --acls, or getfacl/setfacl --restore. Also note that NFSv4 ACLs and RichACLs are a different model from POSIX ACLs; check your storage vendor's documentation when the target is not a local ext4 or XFS volume.

Next steps

Was this guide helpful?

Still stuck?

Ask MdmHost AI for a quick answer, or open a ticket and our team will take a look. We are here 24/7.