Skip to content
Français
Account: not signed in, sync available My learning
Select theme
Français

Refine permissions with ACLs

By updated

Improve this page

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 getfacl and change it with setfacl
  • 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.

SituationClassic permissionsACL
One owner, one group, uniform accessEnoughUseless
Two users in different groups must reach the same fileCreate a dedicated groupSimpler with ACL
Each user has a different access level on the same directoryImpossibleThe only solution
An external contractor must read a folder without joining a groupRisky (widen other)ACL on the user
Automatic inheritance of rights for new filesLimited umaskDefault ACL

Prerequisites

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.

Terminal window
tune2fs -l /dev/sda1 | grep "Default mount options"

Expected output:

Default mount options: user_xattr acl

If acl does not appear, add it in /etc/fstab:

UUID=xxxxxxxx-xxxx / ext4 defaults,acl 0 1

Then remount:

Terminal window
mount -o remount /

Check: create a quick test to confirm that ACLs work:

Terminal window
touch /tmp/test-acl
setfacl -m u:nobody:r /tmp/test-acl
getfacl /tmp/test-acl | grep "user:nobody"

If the output shows user:nobody:r--, ACLs are operational.

Terminal window
rm /tmp/test-acl

First 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.

  1. Set the ACL:

    Terminal window
    setfacl -m u:bob:r /srv/rapport.txt
  2. Check with getfacl:

    Terminal window
    getfacl /srv/rapport.txt

    Output:

    srv/rapport.txt
    # owner: alice
    # group: alice
    user::rw-
    user:bob:r--
    group::r--
    mask::r--
    other::---
  3. 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:

Terminal window
sudo chmod 750 /srv/projet # root:root, nobody else can traverse
sudo -u bob cat /srv/projet/rapport.txt
cat: /srv/projet/rapport.txt: Permission denied

The 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:

Terminal window
sudo setfacl -m u:bob:x /srv/projet
sudo -u bob cat /srv/projet/rapport.txt
rapport trimestriel

alice 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:

EntryMeaning
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:

Terminal window
setfacl -m u:bob:rw /srv/m.txt
getfacl -c /srv/m.txt
user::rw-
user:bob:rw-
group::r--
mask::rw-
other::---

The owning group only has r--. And yet:

Terminal window
ls -l /srv/m.txt
-rw-rw----+ 1 alice alice 2 juil. 23 08:36 /srv/m.txt

The 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:

Terminal window
getfacl fichier | grep mask

If the mask is too restrictive, fix it:

Terminal window
setfacl -m m::rwx fichier

Default 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::rwx
default:user:bob:rw-
default:group::r-x
default:mask::rwx
default:other::r-x

Set 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:

Terminal window
setfacl -m u:bob:rw /srv/projet/config.yml

For a group:

Terminal window
setfacl -m g:dev:r /srv/projet/config.yml

Check:

Terminal window
getfacl /srv/projet/config.yml

Set default ACLs on a directory

For every file created in /srv/projet to inherit bob's rights:

Terminal window
setfacl -d -m u:bob:rw /srv/projet

Do 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::rwx
default:group::r-x
default:group:dev:rw-
default:mask::rwx
default:other::r-x

The 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:

Terminal window
touch /srv/projet/test.txt
getfacl /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:

Terminal window
setfacl -x u:bob /srv/projet/config.yml

Remove all the ACLs of a file

To go back to classic permissions:

Terminal window
setfacl -b /srv/projet/config.yml

Check: ls -l must no longer show the +:

Terminal window
ls -l /srv/projet/config.yml

Apply ACLs in bulk (recursive)

To set an ACL on a whole directory and its content:

Terminal window
setfacl -R -m u:bob:rwX /srv/projet

The 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:

Terminal window
setfacl -R -d -m u:bob:rwx /srv/projet

Real 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
  1. Create the directory and set the ACLs:

    Terminal window
    mkdir -p /srv/webapp
    chown alice:dev /srv/webapp
    chmod 750 /srv/webapp
    setfacl -m u:bob:rwx /srv/webapp
    setfacl -m u:carol:rx /srv/webapp

    Note the x on 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: with u:carol:r alone, sudo -u carol cat /srv/webapp/app.py answers Permission denied while sudo -u carol ls /srv/webapp does show app.py.

  2. Set the default ACLs for inheritance:

    Terminal window
    setfacl -d -m u:alice:rwx /srv/webapp
    setfacl -d -m u:bob:rwx /srv/webapp
    setfacl -d -m u:carol:rx /srv/webapp

    The default entries carry the x too. They are copied as is onto each subdirectory created, and without x bob 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 at rw-, and getfacl shows user:carol:r-x #effective:r--.

  3. Check the full state:

    Terminal window
    getfacl /srv/webapp
    # file: srv/webapp
    # owner: alice
    # group: dev
    user::rwx
    user:bob:rwx
    user:carol:r-x
    group::r-x
    mask::rwx
    other::---
    default:user::rwx
    default:user:alice:rwx
    default:user:bob:rwx
    default:user:carol:r-x
    default:group::r-x
    default:mask::rwx
    default:other::---
  4. Test inheritance, create a file and check:

    Terminal window
    su - alice -c "touch /srv/webapp/app.py"
    getfacl /srv/webapp/app.py

    The 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:

Terminal window
getfacl -pR /srv/webapp > /root/acl-webapp.bak

Restore:

Terminal window
setfacl --restore=/root/acl-webapp.bak

The 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:

Terminal window
getfacl -R /srv/webapp | grep "user:" | sort -u

This 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.

SymptomLikely causeFix
Permission denied while getfacl does show the entryNo x right on a directory of the pathnamei -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 fileACL as r alone on a directoryUse rx on a directory, never r
setfacl: command not foundacl package missing, the case of minimal AlmaLinux and RHELsudo dnf install acl or sudo apt install acl
setfacl --restore fails on No such file or directoryBackup made without -p, relative pathsRedo with getfacl -pR, or restore from /
ls -l shows group rights that do not existThe group position shows the mask as soon as a + is presentRead getfacl, not ls -l
setfacl: Operation not supportedFile system without ACL support (FAT32, old ext3 without the option)Check tune2fs -l or move to ext4/XFS
Permission denied despite a correct ACLThe mask caps the rightsgetfacl fichier | grep mask then setfacl -m m::rwx fichier
Permission denied with the mask OKSELinux or AppArmor blocksausearch -m AVC -ts recent (SELinux) or dmesg | grep apparmor
New files without inherited ACLsNo default ACL on the parent directorysetfacl -d -m u:bob:rw /srv/projet
A new subdirectory can be listed but not enteredDefault entry without x, copied onto the subdirectorysetfacl -d -m u:<user>:rwx (or rx) on the parent
chmod broke the ACLschmod changes the maskRecreate the mask: setfacl -m m::rwx fichier
The + disappears after cpcp without -p or --preserve=allUse 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 acl package 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 x traversal right on every directory of the path. On a directory, it is rx for reading and rwx for writing, never r or rw alone.
  • getfacl reads ACLs, setfacl sets them, always check after each change.
  • The mask is the ceiling of named permissions. A simple chmod can change it and break your ACLs without warning.
  • As soon as a + appears, ls -l shows the mask instead of the group's rights. Only getfacl tells the truth.
  • Default ACLs on a directory ensure automatic inheritance for new files; a single entry set makes five appear. Put the x in them as soon as subdirectories will be created: it makes them traversable without making the files executable.
  • Back up ACLs with getfacl -pR, never -R alone: 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

6 questions
6 min
70% to pass

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

Is this project useful to you?

The guides, courses, quizzes and labs stay free. If you use them regularly, you can contribute to their upkeep and development.

Become a supporting member (new tab) One-time support (new tab) Learn more

Follow the project on LinkedIn