Loading content...
The User/Group/World model works beautifully for simple scenarios. But modern computing demands more flexibility. Consider a collaborative project where:
With only three permission classes and one group per file, this is impossible. Access Control Lists (ACLs) solve this by attaching arbitrary permission entries to files, allowing per-user and per-group specifications without the administrative overhead of creating groups for every permission combination.
By the end of this page, you will understand: how ACLs extend traditional Unix permissions, POSIX ACL structure and semantics, how to read, set, and manage ACLs using getfacl and setfacl, the ACL mask mechanism and its purpose, default ACLs for new file creation, and how ACLs interact with standard permission commands like chmod.
An Access Control List is a list of permissions attached to a file system object, specifying which users or groups have which access rights. Unlike the fixed three-class Unix model, ACLs can contain an arbitrary number of entries.
Conceptual model:
Think of an ACL as implementing the column of an access control matrix (covered in Page 1). For a single file, the ACL stores:
| Feature | Traditional Unix | POSIX ACLs |
|---|---|---|
| Permission entries | Exactly 3 (owner, group, other) | Unlimited named users/groups |
| Groups per file | Exactly 1 | Multiple via ACL entries |
| Granularity | Class-based | User-specific and group-specific |
| Storage | In inode (9 bits + 3 special) | Extended attributes (additional metadata) |
| Compatibility | Universal | Requires filesystem and tool support |
| Complexity | Simple to audit mentally | Requires tools (getfacl) to fully view |
| Default for new files | umask-derived | Can inherit from parent directory ACL |
Two types of ACLs:
Access ACLs — Applied directly to files and directories. Control access to that specific object.
Default ACLs — Apply only to directories. Specify ACL entries that new files and subdirectories inherit. This enables permission inheritance without setgid.
POSIX ACLs (IEEE 1003.1e/2c) are the standard on Linux and most Unix systems. Windows uses NTFS ACLs, which are more complex and support different semantics.
The POSIX ACL draft (1003.1e) was withdrawn before final standardization due to lack of consensus. However, the draft was widely implemented and is now the de facto standard on Linux (via libacl), FreeBSD, Solaris, and macOS. The specification is stable despite never being officially ratified.
A POSIX ACL consists of multiple entries, each specifying permissions for a specific qualifier (user, group, or category). There are six entry types:
| Entry Type | Syntax | Description | Required? |
|---|---|---|---|
| user::perms | user::rwx | File owner permissions (like traditional owner) | Yes |
| user:name:perms | user:bob:r-x | Named user permissions (specific user) | No |
| group::perms | group::r-x | Owning group permissions (like traditional group) | Yes |
| group:name:perms | group:dev:rw- | Named group permissions (specific group) | No |
| mask::perms | mask::r-x | Maximum effective perms for named users/groups | If named entries exist |
| other::perms | other::r-- | Permissions for everyone else | Yes |
The minimal ACL:
Every file has at least three ACL entries: user::, group::, and other::. These exactly correspond to the traditional Unix permission bits. A file with only these three entries has a minimal ACL and behaves identically to the traditional model.
Extended ACLs:
When named user or named group entries are added, the ACL becomes an extended ACL. Extended ACLs require a mask entry and have more complex evaluation rules.
How you know a file has an ACL:
123456789101112131415161718
# Files with extended ACLs show '+' in ls output$ ls -l-rw-r--r-- 1 alice alice 1024 Jan 16 10:00 normal.txt-rw-r--r--+ 1 alice alice 2048 Jan 16 10:00 with_acl.txt ^ └── The '+' indicates an extended ACL exists # View full ACL with getfacl$ getfacl with_acl.txt# file: with_acl.txt# owner: alice# group: aliceuser::rw- # File owner (alice)user:bob:r-- # Named user entry (bob)group::r-- # Owning groupgroup:developers:rw- # Named group entrymask::rw- # Effective rights maskother::--- # Everyone elseNamed entries use the format type:qualifier:permissions. The qualifier is the username or groupname. For example: user:bob:r-x grants bob read and execute. group:interns:r-- grants the interns group read-only. Without a qualifier (just user::), it refers to the owner/owning group.
The mask entry is the most misunderstood aspect of POSIX ACLs. It defines the maximum effective permissions for all named user entries, named group entries, AND the owning group entry.
The formula:
Effective Permissions = Granted Permissions AND Mask
Only the owner (user::) and other (other::) entries are NOT affected by the mask. Everything else is filtered through it.
123456789101112131415161718192021222324252627
# Demonstrating mask behavior $ getfacl restricted.txt# file: restricted.txt# owner: alice# group: aliceuser::rw- # Alice (owner): rw- (NOT masked)user:bob:rw- # Bob: rw- in ACL...group::rw- # Owning group: rw- in ACL...group:developers:rwx # Developers: rwx in ACL...mask::r-- # BUT mask is read-only!other::--- # Others: no access (NOT masked) # Effective permissions (what actually applies):# - alice: rw- (owner bypasses mask)# - bob: rw- AND r-- = r-- (masked to read-only)# - owning group: rw- AND r-- = r-- (masked)# - developers: rwx AND r-- = r-- (masked, even execute removed!)# - others: --- (not masked, but already no access) # getfacl shows effective permissions when mask applies:$ getfacl restricted.txtuser:bob:rw- #effective:r--group::rw- #effective:r--group:developers:rwx #effective:r-- # The #effective: comment shows what actually applies!Why does the mask exist?
The mask solves a critical compatibility problem:
chmod) only understand 9 permission bitschmod g-w file, the system can't know about named entriesThe mask-group synchronization:
12345678910111213141516171819202122232425262728
# chmod interacts with the mask, not the group entry $ getfacl myfile.txtuser::rw-user:bob:rw-group::rw-mask::rw-other::r-- # Traditional group bits show: rw-r--r-- (group is rw-)$ ls -l myfile.txt-rw-rw-r--+ 1 alice alice 1024 Jan 16 10:00 myfile.txt # Now remove group write with chmod$ chmod g-w myfile.txt$ ls -l myfile.txt-rw-r--r--+ 1 alice alice 1024 Jan 16 10:00 myfile.txt # What actually changed? The MASK!$ getfacl myfile.txtuser::rw-user:bob:rw- #effective:r-- ← Bob lost write!group::rw- ← Still says rw-!mask::r-- ← Mask changed!other::r-- # The group:: entry still has write, but mask filters it out.# This is how chmod stays compatible with ACLs.If you have named user entries granting write access, and someone runs chmod 644 file, the mask becomes r-- and all named users lose write access! Their ACL entries still exist, but the mask limits them. Always use getfacl to check effective permissions after chmod on ACL-enabled files.
The setfacl command modifies ACLs. Unlike chmod, it can add, modify, or remove individual ACL entries.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778
# =====================================================# Adding ACL Entries (-m / --modify)# ===================================================== # Grant user bob read permissionsetfacl -m u:bob:r myfile.txt # Grant user carol read/write permissionsetfacl -m u:carol:rw myfile.txt # Grant group developers read/execute permissionsetfacl -m g:developers:rx myfile.txt # Multiple entries at once (comma-separated)setfacl -m u:alice:rw,u:bob:r,g:staff:r myfile.txt # =====================================================# Modifying Existing Entries# ===================================================== # Same syntax - if entry exists, it's updatedsetfacl -m u:bob:rw myfile.txt # Change bob from r to rw # Set the mask explicitly (affects all named entries)setfacl -m m::rx myfile.txt # =====================================================# Removing ACL Entries (-x / --remove)# ===================================================== # Remove bob's ACL entrysetfacl -x u:bob myfile.txt # Remove developers group entrysetfacl -x g:developers myfile.txt # Note: you cannot remove owner/group/other/mask entries# Those are structural - use chmod to modify them # =====================================================# Removing All Extended ACLs (-b / --remove-all)# ===================================================== # Restore to minimal ACL (just owner/group/other)setfacl -b myfile.txt # After -b, the '+' disappears from ls -l # =====================================================# Recursive Operations (-R)# ===================================================== # Apply ACL to directory and all contentssetfacl -R -m u:bob:r project/ # Remove specific entry recursivelysetfacl -R -x u:bob project/ # =====================================================# Copying ACLs (--copy-from)# ===================================================== # Copy ACL from one file to anothergetfacl source.txt | setfacl --set-file=- dest.txt # =====================================================# Common Patterns# ===================================================== # Make file readable by specific user only (plus owner)chmod 600 secret.txtsetfacl -m u:auditor:r secret.txt # Collaborative file: owner full, group write, specific user readsetfacl -m g:team:rw,u:contractor:r project.doc # Verify the resultgetfacl secret.txtPermissions can be specified as: rwx (letters), 7 (octal digit), or --- (explicit none). You can also use - for denied bits: r-x means read and execute, no write. The format is flexible: u:bob:r, user:bob:r, user:bob:4 all mean the same thing.
Default ACLs apply only to directories and control what ACL entries new files and subdirectories inherit. They solve the problem of ensuring consistent permissions in shared directories without requiring manual setup for each new file.
How default ACLs work:
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970
# =====================================================# Setting Default ACLs (-d flag or d: prefix)# ===================================================== # Set default ACL: all files created here will be readable by bobsetfacl -d -m u:bob:r shared_project/ # Alternative syntax with d: prefixsetfacl -m d:u:bob:r shared_project/ # Set multiple default entriessetfacl -d -m u::rwx,g::rx,o::---,u:bob:r,g:developers:rw shared_project/ # =====================================================# Viewing Default ACLs# ===================================================== $ getfacl shared_project/# file: shared_project/# owner: alice# group: aliceuser::rwxgroup::r-xother::--- # Default entries (apply to new files/dirs):default:user::rwxdefault:user:bob:r--default:group::r-xdefault:group:developers:rw-default:mask::rwxdefault:other::--- # =====================================================# Demonstrating Inheritance# ===================================================== # Create a file in the directory$ touch shared_project/newfile.txt$ getfacl shared_project/newfile.txt# file: shared_project/newfile.txt# owner: alice# group: aliceuser::rw- # From default:user:: (minus execute - it's a file)user:bob:r-- # Inherited from d:u:bob:rgroup::r-x # From default:group::group:developers:rw- # Inherited from d:g:developers:rwmask::rw- # Computed from inherited entriesother::--- # From default:other:: # Create a subdirectory$ mkdir shared_project/subdir$ getfacl shared_project/subdir/# Inherits BOTH access ACL AND default ACLdefault:user::rwxdefault:user:bob:r--default:group::r-xdefault:group:developers:rw-default:mask::rwxdefault:other::--- # =====================================================# Removing Default ACLs# ===================================================== # Remove all default ACL entriessetfacl -k shared_project/ # Remove specific default entrysetfacl -x d:u:bob shared_project/When a file inherits a default ACL, the creating process's umask is still applied to the inherited permissions. If your default ACL grants rw- but umask is 0077, new files get --- for non-owner entries. Set umask appropriately or use restrictive defaults.
When a process requests access to a file with an ACL, the kernel evaluates entries in a specific order. Understanding this order is critical for predicting access outcomes.
123456789101112131415161718192021222324252627282930313233
ACL Permission Evaluation Algorithm: 1. If process effective UID == file owner UID: → Use user:: entry → DONE (owner never uses mask) 2. If there's a user:username: entry matching process UID: → Use that entry's permissions AND mask → DONE 3. If process has a group matching any group:groupname: entry: → Collect permissions from ALL matching group entries → UNION the permissions (most permissive wins) → Apply mask to the union → DONE 4. If process has a group matching file's owning group: → Use group:: entry AND mask → DONE 5. Otherwise: → Use other:: entry → DONE (other never uses mask) Key Points:┌─────────────────────────────────────────────────────────────┐│ • Owner always uses user:: entry (never masked) ││ • Named users are checked BEFORE group memberships ││ • If a named user matches, groups are NOT checked ││ • Multiple matching group entries: permissions UNION ││ • Owner and Other: NOT subject to mask ││ • Everything else: subject to mask │└─────────────────────────────────────────────────────────────┘Example: Multiple group matches:
1234567891011121314151617181920212223242526272829
# Carol is in groups: staff, developers, marketing $ getfacl project.txtuser::rw-group::r-- # Owning group (not 'staff')group:staff:r--group:developers:rw-group:marketing:--x # Unusual but legalmask::rwxother::--- # Carol's access calculation:# 1. Is carol the owner? No# 2. Is there a user:carol: entry? No# 3. Is carol in any named groups? Yes! All three!# - staff: r--# - developers: rw-# - marketing: --x# Union: rwx (all bits that ANY group grants)# Apply mask (rwx): rwx AND rwx = rwx# 4. (Skip - used named groups)# 5. (Skip - not other) # Result: Carol has rwx access! # Important implication:# If you want to DENY a specific group, you can't just give them# fewer permissions - they might get access through another group.# ACLs don't support DENY entries in POSIX model.Unlike Windows NTFS ACLs, POSIX ACLs can only grant permissions, not explicitly deny them. If a user is in multiple groups, they get the UNION of all matching group permissions. To deny access, you must ensure no matching entry grants access—sometimes requiring removal from groups or using other:: restrictions.
ACLs require support at multiple levels: the filesystem, the kernel, and userspace tools. Not all environments fully support ACLs, and this can cause problems when files are transferred.
| Filesystem | ACL Support | Notes |
|---|---|---|
| ext4 | Full POSIX ACL | Default on most Linux; mount option may be needed on older systems |
| XFS | Full POSIX ACL | Enabled by default |
| Btrfs | Full POSIX ACL | Enabled by default |
| ZFS (Linux) | Full POSIX ACL | Also supports NFSv4-style ACLs |
| NTFS (via ntfs-3g) | Emulated | Maps NTFS ACLs to POSIX where possible |
| FAT32/exFAT | None | No ACL support; permissions are fake |
| NFS | Depends on version | NFSv4 has native ACL support; v3 requires extensions |
| CIFS/SMB | Translated | Windows ACLs mapped to POSIX; imperfect translation |
Enabling ACLs:
On modern Linux with ext4, ACLs are typically enabled by default. If not:
123456789101112131415
# Check if ACLs are enabled (look for 'acl' in mount options)$ mount | grep /dev/sda1/dev/sda1 on / type ext4 (rw,relatime,acl) # Enable ACLs at mount timesudo mount -o remount,acl / # Or add to /etc/fstab for persistence# /dev/sda1 / ext4 defaults,acl 0 1 # Verify ACL support by setting one$ touch testfile$ setfacl -m u:nobody:r testfile$ getfacl testfile# If this works without errors, ACLs are supportedWhen copying or archiving files, ACLs can be lost!
cp preserves ACLs only with -a or --preserve=alltar needs --acls flag to include ACLs in archiversync needs -A or --acls flagAlways verify ACLs are preserved in your backup and deployment procedures.
ACLs provide powerful flexibility, but can also create complex, hard-to-audit permission structures. Follow these practices for maintainable systems:
u:bob:rw,u:carol:rw,u:dave:rw, add all three to a group and use g:team:rw. Easier to manage when team composition changes.getfacl -R directory to dump ACLs recursively. Review for stale entries (former employees, old contractors, obsolete groups).other::--- for sensitive directories.For reproducible permission setups, script your ACL configuration:
#!/bin/bash
setfacl -R -b /project # Remove all ACLs first
setfacl -R -m g:developers:rwx /project
setfacl -R -d -m g:developers:rwx /project
setfacl -m u:auditor:rx /project/reports
Version control this script alongside your infrastructure-as-code.
What's next:
The next page explores Capabilities—an entirely different approach to access control where permission to access objects is represented as unforgeable tokens held by the subject, rather than policies stored with the object. This model underpins file descriptors, some modern security frameworks, and several experimental operating systems.
You now understand POSIX ACLs in depth—their structure, the crucial mask mechanism, how to set and manage them, default ACL inheritance, and evaluation semantics. This knowledge enables fine-grained access control that overcomes the limitations of basic Unix permissions.