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
sudoaccess. - A filesystem that supports ACLs. ext4, XFS and Btrfs all do. Check yours before you start.
- The
getfaclandsetfacltools. They ship in theaclpackage. On Debian and Ubuntu runsudo apt install acl; on AlmaLinux and Rocky runsudo 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:
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.txtThe kernel checks in a fixed order:
- If your UID matches the file owner, the owner bits apply. Group and other bits are ignored.
- Otherwise, if your GID or any of your supplementary groups matches the file group, the group bits apply.
- 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.txtThe 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
findmnt -no SOURCE,FSTYPE,OPTIONS //dev/vda1 ext4 rw,relatime,errors=remount-roIf 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.
sudo mount -o remount /
findmnt -no OPTIONS /Read existing ACLs with getfacl
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:
sudo setfacl -m u:deploy:rw /srv/share/report.txt
sudo setfacl -m u:alice:r-- /srv/share/report.txtGrant a group read and execute on a directory:
sudo setfacl -m g:devs:rx /srv/shareSet several entries at once, comma separated:
sudo setfacl -m u:deploy:rwx,u:alice:r-x,g:devs:rx /srv/shareVerify immediately:
getfacl -c /srv/share/report.txt
ls -l /srv/share/report.txtTip: 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.
sudo setfacl -m m::rx /srv/share
getfacl -c /srv/shareTwo rules to remember:
chmod g-w filechanges the mask, not the owning group's entry, on a file that already has an ACL.setfacl -m m::rw filesets 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.
sudo setfacl -m d:u:deploy:rwx,d:g:devs:rx /srv/share
sudo setfacl -m d:m::rx /srv/share
getfacl -c /srv/shareEntries 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:
sudo setfacl -x u:deploy /srv/share/report.txt
sudo setfacl -b /srv/share/report.txtWarning: 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:
sudo getfacl -R -p /srv/share > /root/share-acl.bak
cd /
sudo setfacl --restore=/root/share-acl.bakWhen 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.
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/shareExpected: 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 withnoacl, or it is a type that does not carry POSIX ACLs (some network mounts). Checkfindmnt -no OPTIONS /and removenoaclfrom/etc/fstab.- ACL entry exists but the user is still denied — the mask is lower than the entry. Compare
mask::with the named entry ingetfacland raise it withsetfacl -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—chmodrewrites the mask on files that have ACLs. Re-apply the mask withsetfacl -m m::after any scriptedchmod. - ACLs vanished after a backup or migration — the copy tool did not preserve them. Use
rsync -aA,tar --acls, orgetfacl/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
- Linux file permissions explained (rwx, owner, group, others) for the base model behind every ACL.
- chmod: change file permissions in Linux to see exactly how
chmodinteracts with the mask. - chown: change file owner and group and add a user to a group in Linux when a group entry is the simpler answer.
