Linux file permissions have always been a cornerstone of system security, but the traditional owner-group-others model often falls short in complex multi-user environments. Enter setfacl – the powerful Access Control List (ACL) utility that transforms how you manage file permissions on Linux servers. Whether you need to grant a developer read-only access to production files or implement fine-grained security policies, setfacl is your go-to tool.
In this comprehensive guide, you’ll learn how to use setfacl to add read-only users, implement recursive permissions, configure default ACLs for new files, and leverage advanced features that most system administrators overlook.
Why Standard Linux Permissions Aren’t Enough
Traditional Unix permissions (chmod) only allow you to set access for three entities: the owner, a single group, and everyone else. This creates real-world challenges:
- A developer needs read access to a website directory, but they shouldn’t be in the
www-datagroup - Multiple teams require different access levels to the same project
- New files should automatically inherit specific permissions for certain users
- You need to audit who has access to sensitive directories
The Access Control List (ACL) system solves these problems by allowing multiple users and groups to have custom permissions on any file or directory.
Installing ACL Support on Linux
Most modern Linux distributions come with ACL support pre-installed. To verify or install:
# Check if ACL is installed
getfacl --version
# Install on RHEL/CentOS/Rocky Linux/AlmaLinux
sudo dnf install acl
# Install on Debian/Ubuntu
sudo apt install acl
Your filesystem must also support ACLs. Most modern filesystems (ext4, XFS, Btrfs) have ACL support enabled by default. You can verify this in your mount options:
mount | grep acl
If ACLs are not enabled, you can enable them by adding acl to your mount options in /etc/fstab and remounting.
Adding a Read-Only User to a Website with setfacl
This is one of the most common use cases: granting a developer or auditor read-only access to website files without modifying group ownership. Let’s say you have a website at /srv/www/example.com and want to give the user sftpdev read-only access.
The Simple Command
setfacl --recursive --modify u:sftpdev:rX /srv/www/example.com
Let’s break this down:
--recursive(-R): Apply to all files and subdirectories--modify(-m): Add or modify an ACL entryu:sftpdev:rX: Grant usersftpdevread (r) and conditional execute (X) permissions- The uppercase
Xis crucial – it grants execute permission only on directories and files that already have execute permission
Why Use Capital X Instead of Lowercase x?
The difference between x and X is critical:
| Permission | Effect on Files | Effect on Directories |
|---|---|---|
x |
Execute permission | Enter directory permission |
X |
Execute only if already executable | Always enter directory permission |
Using lowercase x would make all files executable (a security risk), while uppercase X intelligently applies execute only where needed.
Making ACLs Persist for New Files: Default ACLs
Here’s where many administrators make a mistake. The command above only affects existing files. New files created in the directory won’t have the ACL. To fix this, you need default ACLs:
setfacl --recursive --modify d:u:sftpdev:rX /srv/www/example.com
The d: prefix creates a default ACL that automatically applies to newly created files and directories.
The Complete Solution: Both Current and Future Files
To grant read-only access that applies to both existing and future files, combine both commands:
setfacl --recursive --modify u:sftpdev:rX,d:u:sftpdev:rX /srv/www/example.com
This single command:
1. Grants read-only access to all existing files and directories
2. Sets default ACLs so new files automatically inherit the same permissions
Understanding setfacl Syntax in Depth
The general syntax for setfacl is:
setfacl [options] [acl_spec] file/directory
ACL Specification Format
The ACL specification (acl_spec) follows this pattern:
[d:]<type>:<qualifier>:<permissions>
| Component | Description | Examples |
|---|---|---|
d: |
Optional prefix for default ACLs | d:u:bob:rw |
<type> |
u (user), g (group), o (other), m (mask) |
u, g, m |
<qualifier> |
Username, group name, or empty for file owner/group | bob, developers, (empty) |
<permissions> |
r (read), w (write), x/X (execute), - (none) |
rw, r-x, rwx |
Examples of ACL Specifications
# User 'alice' gets read and write
u:alice:rw
# Group 'devteam' gets read, write, execute
g:devteam:rwx
# Remove all permissions for user 'bob'
u:bob:---
# Set default ACL for group 'auditors' with read-only
d:g:auditors:r
# Modify the ACL mask (effective permissions limit)
m::rx
Essential setfacl Options Every Admin Should Know
Recursive Operations
# Apply ACL recursively to directories and files
setfacl -R -m u:developer:rX /var/www
# The long form
setfacl --recursive --modify u:developer:rX /var/www
Removing ACLs
# Remove specific ACL entry
setfacl -x u:developer /var/www/project
# Remove all ACLs (restore to standard permissions)
setfacl -b /var/www/project
# Remove default ACLs only
setfacl -k /var/www/project
# Remove all ACLs recursively
setfacl -R -b /var/www/project
Copying ACLs Between Files
# Copy ACLs from one file to another
getfacl source_file | setfacl --set-file=- target_file
# Copy ACLs recursively
getfacl -R /source/dir > acl_backup.txt
setfacl --restore=acl_backup.txt
Setting ACLs from a File
Create an ACL specification file:
# acl_config.txt
user:developer:rX
user:auditor:r
group:webteam:rwX
default:user:developer:rX
default:group:webteam:rwX
Apply it:
setfacl -M acl_config.txt /srv/www/example.com
Viewing ACLs with getfacl
The getfacl command displays current ACLs:
getfacl /srv/www/example.com
Output example:
# file: srv/www/example.com
# owner: www-data
# group: www-data
user::rwx
user:sftpdev:r-x
group::r-x
mask::r-x
other::---
default:user::rwx
default:user:sftpdev:r-x
default:group::r-x
default:mask::r-x
default:other::---
Understanding the Output
- user:: – Permissions for the file owner
- user:sftpdev:r-x – ACL entry for user
sftpdev - group:: – Permissions for the owning group
- mask::r-x – Maximum effective permissions for ACL entries
- other:: – Permissions for everyone else
- default: entries – Inherited by new files/directories
The ACL Mask: Your Security Safety Net
The mask entry is often overlooked but crucial. It defines the maximum effective permissions for all ACL entries (except the owner). Even if you grant rwx to a user, the mask limits what’s actually effective:
# Set mask to read-only
setfacl -m m::r /sensitive/file
# Now even users with 'rwx' ACL only get 'r'
To see effective permissions (considering the mask):
getfacl /sensitive/file
You’ll see output like:
user:developer:rwx #effective:r--
Real-World Use Cases
1. Development Team Access to Staging Server
Grant different access levels to multiple teams:
# Website root
WEB_ROOT="/srv/www/staging.example.com"
# Developers get full access
setfacl -R -m u:dev1:rwX,u:dev2:rwX,d:u:dev1:rwX,d:u:dev2:rwX $WEB_ROOT
# QA team gets read-only
setfacl -R -m g:qa:rX,d:g:qa:rX $WEB_ROOT
# External auditor gets read-only to logs only
setfacl -R -m u:auditor:rX /srv/www/staging.example.com/logs
2. Protecting Configuration Files
Allow web server to read configs but prevent accidental writes:
# Apache/NGINX needs read access
setfacl -m u:www-data:r /etc/myapp/config.yml
# Developer can edit
setfacl -m u:developer:rw /etc/myapp/config.yml
# Default: no one else
setfacl -m o::--- /etc/myapp/config.yml
3. Shared Upload Directory
Multiple users can upload, but only specific users can delete:
UPLOAD_DIR="/var/www/uploads"
# Users can read and write (create files)
setfacl -m g:uploaders:rwx $UPLOAD_DIR
# But only admin can delete (via directory sticky bit + ACL)
chmod +t $UPLOAD_DIR
setfacl -m u:admin:rwx $UPLOAD_DIR
4. Git Workflow: Developer Read-Only Access to Production
In a Git-based deployment workflow, give developers read access to verify deployments without write access:
# Create the SFTP user
useradd -m -s /sbin/nologin sftpdev
# Grant read-only recursive access with defaults for new files
setfacl -R -m u:sftpdev:rX,d:u:sftpdev:rX /srv/www/production.example.com
# Verify
sudo -u sftpdev cat /srv/www/production.example.com/index.php # Works
sudo -u sftpdev touch /srv/www/production.example.com/test # Permission denied
Troubleshooting Common setfacl Issues
“Operation not supported” Error
Your filesystem may not have ACL support enabled:
# Check current mount options
mount | grep "on /srv"
# Remount with ACL support
sudo mount -o remount,acl /srv
# Make permanent in /etc/fstab
# Add 'acl' to options: /dev/sda1 /srv ext4 defaults,acl 0 2
ACLs Not Inherited by New Files
You forgot to set default ACLs:
# Add the 'd:' prefix for defaults
setfacl -m d:u:developer:rX /var/www
Permissions Don’t Match ACL
The mask is limiting effective permissions:
# Check effective permissions
getfacl /path/to/file
# Adjust mask if needed
setfacl -m m::rwx /path/to/file
ACLs Lost After rsync or cp
Standard cp and rsync don’t preserve ACLs by default:
# Use cp with ACL preservation
cp -a --preserve=all source dest
# rsync with ACL support
rsync -avA source/ dest/
# The -A flag preserves ACLs
Security Best Practices with setfacl
- Principle of Least Privilege: Start with no permissions and add only what’s needed
# Remove all access first
setfacl -b /sensitive/dir
chmod 700 /sensitive/dir
# Add specific access
setfacl -m u:authorized_user:rX /sensitive/dir
- Audit ACLs Regularly: Create scripts to check ACL configurations
# Find all files with ACLs
getfacl -R /srv/www 2>/dev/null | grep -B3 "^user:" | grep "^# file:"
- Document Your ACLs: Backup ACL configurations
# Backup
getfacl -R /srv/www > /backup/www-acls-$(date +%Y%m%d).txt
# Restore
setfacl --restore=/backup/www-acls-20260118.txt
- Use Groups When Possible: Easier to manage than individual user ACLs
# Create group and add users
groupadd webdevs
usermod -aG webdevs alice
usermod -aG webdevs bob
# Set ACL for group instead of individuals
setfacl -R -m g:webdevs:rwX,d:g:webdevs:rwX /var/www
setfacl vs chmod: When to Use Each
| Feature | chmod | setfacl |
|---|---|---|
| Users supported | Owner only | Multiple users |
| Groups supported | One group | Multiple groups |
| Default inheritance | No (use umask) | Yes (default ACLs) |
| Complexity | Simple | More complex |
| Compatibility | Universal | Requires ACL-enabled FS |
| Best for | Simple scenarios | Multi-user environments |
Rule of thumb: Use chmod for simple owner/group scenarios. Use setfacl when you need more granular control or multiple users/groups with different permissions.
Performance Considerations
ACLs add minimal overhead to file operations. However, for filesystems with millions of files and complex ACLs, consider:
- Keeping ACL entries minimal (use groups instead of individual users)
- Using default ACLs wisely (they’re checked on every file creation)
- Regular cleanup of orphaned ACL entries (for deleted users)
# Find ACL entries for non-existent users
getfacl -R /srv/www 2>&1 | grep "No such user"
Quick Reference: Common setfacl Commands
Here’s a handy cheat sheet for the most frequently used setfacl operations:
# Add read-only access for a user
setfacl -m u:username:r file
# Add read/write/execute for a group
setfacl -m g:groupname:rwx directory
# Add recursive read-only with directory traversal
setfacl -R -m u:username:rX directory
# Set default ACL for new files
setfacl -m d:u:username:rX directory
# Combined: current + default ACL in one command
setfacl -R -m u:username:rX,d:u:username:rX directory
# Remove specific user ACL
setfacl -x u:username file
# Remove all ACLs
setfacl -b file
# Remove default ACLs only
setfacl -k directory
# View current ACLs
getfacl file
# Backup ACLs
getfacl -R directory > backup.acl
# Restore ACLs
setfacl --restore=backup.acl
Frequently Asked Questions
Do ACLs override standard permissions?
No, ACLs work alongside standard permissions. The most restrictive permission wins when both are evaluated. The file owner’s permissions and the “other” permissions are always determined by standard permissions, while ACLs extend control for additional users and groups.
Will ACLs survive a system reboot?
Yes, ACLs are stored in the filesystem’s metadata and persist across reboots. However, if you restore files from a backup that doesn’t preserve ACLs, you’ll lose them.
Can I use ACLs with SELinux or AppArmor?
Yes, ACLs operate at the discretionary access control (DAC) level, while SELinux and AppArmor provide mandatory access control (MAC). Both systems are evaluated independently – a file access must be permitted by both DAC (including ACLs) and MAC to succeed.
How do I know if a file has ACLs?
Files with ACLs show a + sign at the end of the permission string in ls -l output:
$ ls -l
-rw-r--r--+ 1 www-data www-data 1234 Jan 18 10:00 index.php
The + indicates extended ACLs are present.
Conclusion
The setfacl command transforms Linux file permission management from a limitation to a powerful feature. Whether you’re setting up secure developer access to production files, implementing multi-team access controls, or ensuring new files automatically inherit the right permissions, ACLs provide the flexibility that traditional Unix permissions lack.
Key takeaways:
- Use
setfacl -R -m u:user:rXfor recursive read-only access - Add
d:prefix for default ACLs on new files - Uppercase
Xgrants execute only on directories and already-executable files - Always verify with
getfaclafter making changes - Backup your ACL configurations regularly
- Use the
+sign inls -loutput to identify files with ACLs
With these techniques, you can implement enterprise-grade access control on any Linux server without the complexity of dedicated access management systems. The combination of granular user permissions, group-based access, and automatic inheritance through default ACLs makes setfacl an indispensable tool in any Linux administrator’s arsenal.

