Three developers work on /srv/projet. Alice must read and write,
Bob only read, and the rest of the team has no access. With classic POSIX
permissions
(owner / group / others), it is impossible without creating a dedicated
group per combination of rights.
ACLs (Access Control Lists) solve this problem. They let you grant permissions per user and per group, independently of the owner and the primary group. It is the standard Linux kernel mechanism to manage fine-grained rights on ext4, XFS and Btrfs, the three most common file systems in production.
Without ACLs, the administrator works around the problem by multiplying
groups, loosening the other rights, or using sudo excessively, all
practices that increase the attack surface.
What you will learn
- Tell when ACLs are needed and when classic permissions are enough
- Read an ACL with
getfacland change it withsetfacl - Set default ACLs so that new files inherit the right rights
- Diagnose an access problem related to the mask, to missing inheritance or to a missing traversal right on the path
Classic permissions vs ACLs: when to switch?
Before setting ACLs, you must know whether they are really needed. Classic POSIX permissions cover the majority of cases. ACLs come in when the owner/group/others model is no longer enough.
The POSIX model only exposes three sets of rights per file: owner, group, and everyone else. It therefore cannot grant distinct access to two users who share no group, without creating one for the occasion. ACLs lift this limit by attaching to the file a list of named entries, one per user or per group.
| Situation | Classic permissions | ACL |
|---|---|---|
| One owner, one group, uniform access | Enough | Useless |
| Two users in different groups must reach the same file | Create a dedicated group | Simpler with ACL |
| Each user has a different access level on the same directory | Impossible | The only solution |
| An external contractor must read a folder without joining a group | Risky (widen other) | ACL on the user |
| Automatic inheritance of rights for new files | Limited umask | Default ACL |
Prerequisites
- Know classic POSIX permissions
(
chmod,chown, owner/group/others) - Know how to manage users and groups
- root or sudo access on the machine
- An ACL-compatible file system (ext4, XFS, Btrfs)
- The
aclpackage, which providesgetfaclandsetfacl
Once the tools are present, it remains to know whether the file system holding your data can store ACLs: that is the case for the three common systems, but not for all.
Check that ACLs are active
On modern distributions, ACLs are enabled by default on ext4, XFS and Btrfs. Check anyway before starting.
tune2fs -l /dev/sda1 | grep "Default mount options"Expected output:
Default mount options: user_xattr aclIf acl does not appear, add it in /etc/fstab:
UUID=xxxxxxxx-xxxx / ext4 defaults,acl 0 1Then remount:
mount -o remount /XFS enables ACLs by default, no configuration needed. Check:
mount | grep " / "Expected output:
/dev/mapper/rl-root on / type xfs (rw,relatime,seclabel,attr2,inode64,logbufs=8,noquota)ACLs are always active on XFS, no option to add.
Check: create a quick test to confirm that ACLs work:
touch /tmp/test-aclsetfacl -m u:nobody:r /tmp/test-aclgetfacl /tmp/test-acl | grep "user:nobody"If the output shows user:nobody:r--, ACLs are operational.
rm /tmp/test-aclFirst use: set and read an ACL
Before getting into theory, let's see the basic gesture.
Scenario: the file /srv/rapport.txt belongs to alice. You must give
bob read access, without changing the file's group.
-
Set the ACL:
Terminal window setfacl -m u:bob:r /srv/rapport.txt -
Check with
getfacl:Terminal window getfacl /srv/rapport.txtOutput:
srv/rapport.txt # owner: alice# group: aliceuser::rw-user:bob:r--group::r--mask::r--other::--- -
Confirm with
ls -l, the+at the end of the permissions shows an active ACL:Terminal window ls -l /srv/rapport.txt-rw-r-----+ 1 alice alice 42 avr 5 10:00 /srv/rapport.txt
bob can now read the file, on one condition that the example above hides: he must be able to reach the file.
An ACL on a file is not enough to read it
It is the ACL misunderstanding, and it costs hours. An ACL decides what you
are allowed to do on a file, not the right to get to it. To open
/srv/projet/rapport.txt, the kernel first checks the traversal right
(x) on /, then on /srv, then on /srv/projet. A single missing link and
the read fails, ACL or not.
The previous example only works because /srv ships as 755, so
traversable by everyone. Move the same file under a realistic directory
and the result flips:
sudo chmod 750 /srv/projet # root:root, nobody else can traversesudo -u bob cat /srv/projet/rapport.txtcat: /srv/projet/rapport.txt: Permission deniedThe ACL is nevertheless intact: getfacl still shows user:bob:r--. What
is missing is the x on the parent directory. Grant it, and the read goes
through immediately:
sudo setfacl -m u:bob:x /srv/projetsudo -u bob cat /srv/projet/rapport.txtrapport trimestrielalice keeps read and write. The other users have no access.
Understand the structure of an ACL
Now that you have seen the gesture, let's break down what getfacl shows.
The entries of an ACL
Each line of the getfacl output is an ACL entry:
| Entry | Meaning |
|---|---|
user::rw- | Permissions of the file's owner |
user:bob:r-- | Specific permissions for user bob |
group::r-- | Permissions of the owning group |
group:dev:rw- | Specific permissions for group dev |
mask::rw- | Maximum permissions for named entries |
other::--- | Permissions for everyone else |
The mask: the permission ceiling
The mask is the most important (and most misunderstood) ACL concept. It
acts as a ceiling: even if an entry grants rwx, the effective
permissions are limited by the mask.
Example:
user:bob:rw- #effective:r--mask::r--Bob has rw- in his ACL entry, but the mask is r--. His effective
permissions are therefore only r--.
ls -l shows the mask, not the group
A direct consequence rarely stated: as soon as a + is present, the
group position in ls -l no longer shows the owning group's rights, but the
mask. On a 640 file to which you add an entry for bob:
setfacl -m u:bob:rw /srv/m.txtgetfacl -c /srv/m.txtuser::rw-user:bob:rw-group::r--mask::rw-other::---The owning group only has r--. And yet:
ls -l /srv/m.txt-rw-rw----+ 1 alice alice 2 juil. 23 08:36 /srv/m.txtThe middle rw is the mask, not group::. An administrator reading this
line concludes that the group can write: it is false. Only getfacl
tells the truth on a file carrying the +.
Check: after each setfacl, check the mask:
getfacl fichier | grep maskIf the mask is too restrictive, fix it:
setfacl -m m::rwx fichierDefault ACLs (inheritance)
On a directory, you can set default ACLs. Each new file or subdirectory created inside automatically inherits these ACLs.
Default entries start with default: in the getfacl output:
default:user::rwxdefault:user:bob:rw-default:group::r-xdefault:mask::rwxdefault:other::r-xSet and change ACLs with setfacl
setfacl changes the list attached to the file, without touching the
POSIX permissions shown by ls -l. A file carrying ACLs is spotted by the
+ at the end of the permission field; only getfacl shows the detail.
Add or change an entry
For a user:
setfacl -m u:bob:rw /srv/projet/config.ymlFor a group:
setfacl -m g:dev:r /srv/projet/config.ymlCheck:
getfacl /srv/projet/config.ymlSet default ACLs on a directory
For every file created in /srv/projet to inherit bob's rights:
setfacl -d -m u:bob:rw /srv/projetDo not be surprised by what getfacl shows next: a single default entry
makes five appear. On a directory that carried none, a
setfacl -d -m g:dev:rw produces this:
default:user::rwxdefault:group::r-xdefault:group:dev:rw-default:mask::rwxdefault:other::r-xThe kernel completes the set, because a default ACL must be complete to be applicable: it needs an owner, a group, a mask and an "others". The four extra entries simply reuse the directory's classic permissions. You did nothing wrong.
Check: create a file and inspect its ACLs:
touch /srv/projet/test.txtgetfacl /srv/projet/test.txt | grep "user:bob"Expected output: user:bob:rw-.
This entry is meant for files. A subdirectory created in /srv/projet
also inherits user:bob:rw-, so without x: bob sees the names in it but
cannot enter it, the traversal trap described above. As soon as
subdirectories are expected, set rwx as the default entry. The x does
not make files executable for all that: their creation mode (0666) caps
the mask at rw-, and getfacl then shows user:bob:rwx #effective:rw-.
Measured on ext4 with an r default entry: the subdirectory gets r-- and
cannot be traversed; with rx, it gets r-x, and the file
r-x #effective:r--.
Remove an ACL entry
Remove bob's specific rights:
setfacl -x u:bob /srv/projet/config.ymlRemove all the ACLs of a file
To go back to classic permissions:
setfacl -b /srv/projet/config.ymlCheck: ls -l must no longer show the +:
ls -l /srv/projet/config.ymlApply ACLs in bulk (recursive)
To set an ACL on a whole directory and its content:
setfacl -R -m u:bob:rwX /srv/projetThe capital X sets x only on directories and on files that are
already executable (setfacl(1)): bob can traverse the whole tree without
data files becoming executable. A plain rw would, on the contrary, remove any
traversal right on the subdirectories.
To set default ACLs on every subdirectory:
setfacl -R -d -m u:bob:rwx /srv/projetReal case: shared project space
Here is a full administration scenario. Three people work on /srv/webapp:
- alice (developer): read, write, execute
- bob (integrator): read and write
- carol (external auditor): read only
-
Create the directory and set the ACLs:
Terminal window mkdir -p /srv/webappchown alice:dev /srv/webappchmod 750 /srv/webappsetfacl -m u:bob:rwx /srv/webappsetfacl -m u:carol:rx /srv/webappNote the
xon both entries. It is the directory we are handling here, and without it bob and carol would see the file names without being able to open a single one. The trap is described above, and it checks out in one command: withu:carol:ralone,sudo -u carol cat /srv/webapp/app.pyanswersPermission deniedwhilesudo -u carol ls /srv/webappdoes showapp.py. -
Set the default ACLs for inheritance:
Terminal window setfacl -d -m u:alice:rwx /srv/webappsetfacl -d -m u:bob:rwx /srv/webappsetfacl -d -m u:carol:rx /srv/webappThe default entries carry the
xtoo. They are copied as is onto each subdirectory created, and withoutxbob and carol could list the names in it without entering it. The files created do not become executable for all that: their creation mode (0666) caps the mask atrw-, andgetfaclshowsuser:carol:r-x #effective:r--. -
Check the full state:
Terminal window getfacl /srv/webapp# file: srv/webapp# owner: alice# group: devuser::rwxuser:bob:rwxuser:carol:r-xgroup::r-xmask::rwxother::---default:user::rwxdefault:user:alice:rwxdefault:user:bob:rwxdefault:user:carol:r-xdefault:group::r-xdefault:mask::rwxdefault:other::--- -
Test inheritance, create a file and check:
Terminal window su - alice -c "touch /srv/webapp/app.py"getfacl /srv/webapp/app.pyThe entries for bob and carol must appear automatically.
Back up and restore ACLs
During a migration or a backup, standard tools (cp, tar, rsync) do not
always preserve ACLs. Back them up separately.
Back up, with the -p option, which is not optional:
getfacl -pR /srv/webapp > /root/acl-webapp.bakRestore:
setfacl --restore=/root/acl-webapp.bakThe same risk of silent loss threatens the copy of the data itself: a tool that does not carry ACLs drops them along the way, without the slightest message.
Security: least privilege and audit
ACLs are a powerful tool, but misused, they widen the attack surface instead of reducing it.
Principle of least privilege
- Only grant the rights needed: if bob only needs to read, do not give
rw. - Prefer permissions per group rather than per user when several people have the same need.
- Remove a contractor's ACLs as soon as their assignment ends.
Regular audit
Schedule a periodic check of the ACLs on sensitive directories:
getfacl -R /srv/webapp | grep "user:" | sort -uThis lists every user with specific rights. Compare with the list of authorised accesses.
Interaction with SELinux and AppArmor
ACLs operate at the file system level. If
SELinux or AppArmor is
active, an access can be refused by the MAC (Mandatory Access Control) even
if the ACL allows it. Check both layers on an unexpected
Permission denied.
Troubleshooting
A Permission denied despite a correct ACL almost always has one of these
three causes: path traversal, the mask, or a MAC layer on top.
Check them in this order: the first line of the table covers traversal, the
"mask" and "SELinux or AppArmor" lines the next two.
| Symptom | Likely cause | Fix |
|---|---|---|
Permission denied while getfacl does show the entry | No x right on a directory of the path | namei -l /srv/projet/rapport.txt to see where it blocks, then setfacl -m u:<user>:x on the parent |
The user sees the names with ls but cannot open any file | ACL as r alone on a directory | Use rx on a directory, never r |
setfacl: command not found | acl package missing, the case of minimal AlmaLinux and RHEL | sudo dnf install acl or sudo apt install acl |
setfacl --restore fails on No such file or directory | Backup made without -p, relative paths | Redo with getfacl -pR, or restore from / |
ls -l shows group rights that do not exist | The group position shows the mask as soon as a + is present | Read getfacl, not ls -l |
setfacl: Operation not supported | File system without ACL support (FAT32, old ext3 without the option) | Check tune2fs -l or move to ext4/XFS |
Permission denied despite a correct ACL | The mask caps the rights | getfacl fichier | grep mask then setfacl -m m::rwx fichier |
Permission denied with the mask OK | SELinux or AppArmor blocks | ausearch -m AVC -ts recent (SELinux) or dmesg | grep apparmor |
| New files without inherited ACLs | No default ACL on the parent directory | setfacl -d -m u:bob:rw /srv/projet |
| A new subdirectory can be listed but not entered | Default entry without x, copied onto the subdirectory | setfacl -d -m u:<user>:rwx (or rx) on the parent |
chmod broke the ACLs | chmod changes the mask | Recreate the mask: setfacl -m m::rwx fichier |
The + disappears after cp | cp without -p or --preserve=all | Use cp -a or rsync -A |
Put it into practice
Nothing fixes the role of the mask better than seeing it cap a right before your eyes. The lab has you build a shared directory with setfacl, set default ACLs for inheritance, then check every effective permission with getfacl on real accounts.
Key points
- ACLs complement POSIX permissions when the owner/group/others model is no longer enough.
- The
aclpackage is not installed on a minimal AlmaLinux or RHEL: check before starting. - An ACL on a file is not enough to read it: you need the
xtraversal right on every directory of the path. On a directory, it isrxfor reading andrwxfor writing, neverrorrwalone. getfaclreads ACLs,setfaclsets them, always check after each change.- The mask is the ceiling of named permissions. A simple
chmodcan change it and break your ACLs without warning. - As soon as a
+appears,ls -lshows the mask instead of the group's rights. Onlygetfacltells the truth. - Default ACLs on a directory ensure automatic inheritance for new files;
a single entry set makes five appear. Put the
xin them as soon as subdirectories will be created: it makes them traversable without making the files executable. - Back up ACLs with
getfacl -pR, never-Ralone: without-p, paths are relative and restoring fails outside/. - Apply least privilege: only grant the rights needed, audit accesses regularly.
Knowledge check
Check that the essentials of this guide are acquired. The questions only cover what has just been explained here.
Knowledge check
Check what you have learned with this interactive quiz
How it works
- The timer starts when you click Start
- Multiple choice, true/false and short answer questions
- You can move between questions
- Detailed results are shown at the end
Starts the quiz and the timer
Review
(0/0)Score by domain
A domain groups several guides and gets several questions, so its score is more reliable than a per-topic score, which often rests on a single question.
Summary
Strengths
No topic passed at 80% on at least two questions yet.
Weak areas
No topic below the pass mark.
Recommended next
Hints before you try again?
Continue the course: Linux firewalls: understand before you configure
A new full quiz with random questions
Work again only on the questions you missed