Access Control Lists
Topic 28

Access Control Lists

PermissionsSecurity

The classic permission model grants exactly three sets of rwx bits per file: one for the owning user, one for the owning group, one for everyone else. That covers most of a server, until the day two different groups need different access to the same directory tree, or one contractor needs read access without being added to the group that owns everything. The traditional model has no entry to express "this one extra user" — your only escape is to invent another group and re-chown files into it.

Access control lists (ACLs) remove that ceiling. They attach any number of named-user and named-group entries to a file or directory, each with its own rwx, layered on top of the base mode the kernel already checks. ACLs are a POSIX-draft extension stored in the filesystem's extended attributes, supported on ext4, XFS, and Btrfs and mounted with ACL support by default on Debian and Ubuntu. The operational consequence is subtler than the feature list suggests: the moment a file carries an ACL, ls -l appends a + to the mode and the group bits stop meaning what they appear to mean — they become a mask, and misreading that mask is the most common way an ACL silently grants less than you intended.

Limits of the Three-Triad Model

Standard permissions answer a fixed question with a fixed shape: one user, one group, everyone else. Picture a finance directory that the finance team writes to, the audit team must read but never write, and a single external accountant needs read access to one file. Three triads cannot encode three distinct positive grants plus a default-deny. The usual workaround — making an audit group, an accountant group, and shuffling group ownership per file — turns a permissions problem into a group-membership sprawl that nobody can reason about six months later.

ACLs solve exactly this case and only this case. When the access pattern genuinely needs per-principal rights — distinct users or groups each with their own rwx on the same object — an ACL says so directly. When the pattern is "everyone on this team gets the same access," a single group is simpler, faster for the kernel to evaluate, and far easier to audit. The discipline is to reach for ACLs for the irregular case, not as a default for ordinary shared storage.

getfacl and setfacl

Every ACL contains three base entries that mirror the classic triad: user:: (the owner), group:: (the owning group), and other::. To these you add user:name: entries for named users and group:name: entries for named groups — the entries the old model could not express. getfacl prints the full list, including the named entries that ls -l collapses into a single +; setfacl -m modifies an entry and setfacl -x removes one. Both ship in the acl package — priority optional on Debian and Ubuntu, so a minimal server may lack it and need apt install acl before either command exists.

# grant user bob read+write, audit group read-only
setfacl -m u:bob:rw report.csv
setfacl -m g:audit:r report.csv

# ls only signals that an ACL exists — the trailing +
ls -l report.csv
-rw-rw----+ 1 alice finance 8214 report.csv

# getfacl shows the real entries
getfacl report.csv
# file: report.csv
# owner: alice
# group: finance
user::rw-
user:bob:rw-
group::rw-
group:audit:r--
mask::rw-
other::---

To remove the whole ACL and return a file to plain bits, use setfacl -b. To copy one file's ACL onto another, pipe them: getfacl source | setfacl --set-file=- target. There is no chmod equivalent that wipes named entries by accident — but chmod does interact with the mask, which is where the surprises start.

The Mask and Effective Permissions

Whenever a file has at least one named-user or named-group entry, the ACL also carries a mask:: entry. The mask is a ceiling: the effective permission of every named entry and of the owning group is the bitwise AND of that entry and the mask. If user:bob:rwx meets mask::r--, bob's effective permission is r--, no matter what his own entry says. getfacl prints this explicitly with an #effective: comment when an entry is being clipped, which is the single most useful thing to look for when an ACL "isn't working."

The trap is that chmod rewrites the mask. On an ACL-bearing file the middle group of bits in ls -l no longer shows the owning group's permission — it shows the mask. Run chmod 640 on a file where bob has an ACL entry and you have just set the mask to r--, silently downgrading every named entry to read-only while the entries themselves still read rw-. setfacl recalculates the mask to cover the entries it sets unless you pass -n, so prefer setfacl over chmod on any file you have given an ACL.

EntryACL valueMaskEffective
user:bobrwxr-xr-x
group:auditrw-r-xr--
user:: (owner)rwxrwx

The owner entry (user::) and other:: are never masked — only named entries and the owning group are. That is why the file owner can still write a file whose mask reads r--: the mask was never in their path.

Default ACLs and Inheritance

A directory can carry a second, parallel ACL called the default ACL, set with setfacl -d. It grants nothing itself — instead it is the template that every new file and subdirectory created inside inherits as its access ACL. This is the only inheritance mechanism in POSIX ACLs: a regular ACL on a directory governs access to the directory, while the default ACL seeds children. Set a default of g:finance:rwx on a shared tree and every file dropped in by any team member is automatically group-writable, without anyone re-running setfacl.

# shared dir: finance read+write, audit read-only, for all new files
setfacl -d -m g:finance:rwx /srv/finance
setfacl -d -m g:audit:rx /srv/finance

# apply to the directory itself AND its current contents
setfacl -R -m g:finance:rwx /srv/finance

Two caveats bite here. Default ACLs only seed files created after they are set — -R fixes existing contents, the -d template does not reach backward. And inheritance is copy-on-create, not a live link: change the directory's default later and existing files keep the ACL they were born with. For a shared directory this still beats the setgid-bit approach, which can only propagate the owning group, not arbitrary per-principal rights.

Standard Permissions vs POSIX ACLs

Standard permissions — three triads: owner, owning group, other. Cheap for the kernel to evaluate, trivial to read in ls -l, and visible to every admin without extra tooling. Choose them whenever access splits along a single group boundary — which is most of the time.

POSIX ACLs — arbitrary named-user and named-group entries plus a mask, stored in extended attributes. Choose them only when access genuinely needs per-principal grants the triads cannot express, and you have weighed that against simply creating one more group. The complexity is justified for an irregular access matrix; it is overhead for "the whole team gets read-write."

Common Mistakes
  • Running chmod on an ACL-bearing file and silently shrinking every named entry — chmod 640 rewrites the mask to r--, so a user whose ACL says rw- can no longer write, with nothing in ls -l to explain why. Use setfacl instead.
  • Reading the middle group of bits in ls -l as the owning group's permission. On a file with named entries that field is the mask, not the group entry — getfacl is the only honest view.
  • Setting a default ACL with setfacl -d and expecting existing files to change. The default only seeds files created afterward; existing contents need a separate setfacl -R pass.
  • Copying or backing up with tools that drop ACLs. Plain cp loses them; cp -a or cp --preserve=all keeps them, and rsync needs -A (and -X for other xattrs) or the destination silently reverts to base permissions.
  • Assuming ACLs work on any mount. They need a filesystem that supports them mounted with ACL support — default on ext4/XFS on Debian and Ubuntu, but a hand-rolled noacl mount option or an unusual filesystem makes every setfacl fail with "Operation not supported."
  • Reaching for ACLs where a group would be clearer — scattering per-user entries across a tree that the whole team shares, producing a permission layout no one can audit when a single supplementary group would have done the job.
Best Practices
  • Prefer a supplementary group over an ACL whenever access splits along one boundary; reach for ACLs only when distinct principals genuinely need distinct rights on the same object.
  • Edit ACL-bearing files with setfacl, never chmodsetfacl recalculates the mask to cover your entries, while chmod clobbers it.
  • Set a default ACL with setfacl -d on any shared directory so new files inherit the right access automatically, and follow it with setfacl -R to fix what is already there.
  • Run getfacl before trusting an ACL and watch for the #effective: comment — it tells you exactly when the mask is clipping an entry.
  • Preserve ACLs in every backup and copy path: cp -a, rsync -AX, and tar --acls, or restored data quietly loses its access rules.
  • Audit ACL usage with getfacl -R on shared trees during reviews, and collapse stray per-user entries back into groups whenever the access pattern has regularized.
Comparable toolsWindows — NTFS ACLs, a richer model with allow/deny entries, fine-grained rights, and true hierarchical inheritance beyond POSIX defaultsNFSv4 ACLs — a network-filesystem ACL model closer to NTFS than to POSIX, with explicit allow/deny and inheritance flagsmacOS — extended ACLs on HFS+/APFS, managed through chmod +a, layered over the same Unix base bits

Knowledge Check

You run chmod 640 on a file where user:bob has an ACL entry of rw-. Bob can no longer write. Why?

  • chmod rewrote the ACL mask to r--, and a named entry's effective permission is its own bits ANDed with the mask
  • chmod deleted bob's named ACL entry entirely, so his access falls back to the other:: permission, which is read-only
  • The base mode now overrides the ACL entirely, because standard permissions always take precedence over any named entries
  • Bob was removed from the file's owning group, so the group triad in the base mode no longer applies to him at all

When is reaching for an ACL the better choice than the standard permission model?

  • When distinct users or groups each need their own rwx on the same object — a grant the three triads cannot express
  • Whenever a directory is shared by more than one person, since a single supplementary group can never cover a directory used by a whole team
  • When you need the permission check to apply faster, because the kernel evaluates the ACL entries before it ever consults the base bits
  • Whenever a file must be readable by root, since the base owner-group-other permissions have no way to grant root its own access

You set a default ACL on /srv/finance with setfacl -d -m g:finance:rwx. What happens to the files already in that directory?

  • Nothing — the default ACL only seeds files created afterward; existing files need a separate setfacl -R pass
  • They immediately gain the finance:rwx entry, because the default ACL applies to the whole tree at once
  • They lose all of their existing ACLs, since setting a default entry replaces every access ACL on the files underneath it
  • They inherit the finance:rwx entry only the next time someone opens each of them for writing

A nightly rsync backup restores files but every ACL is gone. What is the likely cause?

  • The rsync command omits -A, so it never transfers the ACL extended attributes — they must be requested explicitly
  • ACLs cannot survive any copy across filesystems and must always be re-applied by hand after a restore
  • rsync silently strips every ACL whenever the destination directory uses a different owning group than the source
  • The ACL mask was clipped to --- during the transfer, hiding all the named entries from view until a chmod resets it

You got correct