when one owner and one group are not enough
chmod only understands a single owner and a single group per file, yet real server environments often need several access levels at once. This guide explains setfacl and getfacl, walks through a deploy user example showing granular access control beyond rwx, and covers default ACLs that automatically apply the right rights to newly created files.
Table of Contents
- 1. The limit of chmod: one owner, one group
- 2. getfacl: reading and understanding ACL entries
- 3. setfacl: setting, modifying, removing entries
- 4. Practical example: deploy user writes to a foreign directory
- 5. Default ACLs: inheritance for newly created files
- 6. The ACL mask: the underestimated factor
- 7. Backing up, copying, and restoring ACLs
- 8. File system requirements and mount options
- 9. ACLs compared to chmod and chown
- 10. Summary
- 11. FAQ
1. The limit of chmod: one owner, one group
The classic Unix permission model assigns exactly three target classes to every file: the owner, a single associated group, and everyone else. chmod and chown cover most day-to-day cases with that, but they hit a hard wall as soon as more than one group needs different rights on the same path at the same time. A typical example: a directory is owned by a service's system user, say redis or www-data, and a deploy team also needs to write there, without changing the file's primary group or forcing the service account into a foreign group.
With plain chmod, the only options left are unattractive compromises: either switch the group and break the service's original ownership logic, or grant other-class rights and thereby open access to every local process. Both paths violate the principle of least privilege. Access Control Lists (ACLs) solve exactly this problem: they extend every file and directory with an arbitrary number of additional user and group entries, completely independent of the primary ownership. The classic rwx bits remain fully intact and are only supplemented, never replaced.
ACLs are not a replacement for chmod, but a targeted extension for edge cases. On most servers, owner, group, and umask are entirely sufficient for 95 percent of all paths. The remaining 5 percent, shared directories between multiple teams, monitoring access to log directories owned by other services, or deploy pipelines with restricted rights, are exactly where ACLs earn their keep.
2. getfacl: reading and understanding ACL entries
Before setting any rights, you should know the current state. The command getfacl file shows every entry of a file or directory in a readable format: owner, group, the classic rwx bits as user::, group::, and other::, plus additional entries as user:name:rwx or group:name:rwx. An additional entry called mask:: caps the effective rights of all named user and group entries, covered in more detail in the ACL mask section below.
A hint that ACL entries exist appears even before you run getfacl: ls -l appends an extra + after the regular nine permission characters for files carrying an ACL, for example drwxr-x---+. That plus sign is the only clue in standard output; the actual details always come from getfacl. For analyzing entire directory trees, getfacl -R directory/ recursively dumps all ACL entries and works well as a text baseline for diffing two server states. A typical output summarizes owner, group, and every entry compactly: user::rwx, user:deploy:rwx, group::r-x, group:monitoring:r-x, mask::rwx, and other::---, each line its own permission set.
3. setfacl: setting, modifying, removing entries
The setfacl command sets, modifies, and removes ACL entries. The -m option (modify) adds an entry or overwrites it if it already exists: setfacl -m u:deploy:rwx file grants the user deploy full rights without touching the classic owner or group permissions at all. For groups the same works with g: instead of u:, for example setfacl -m g:monitoring:r-x directory. Multiple entries can be set in a single call, comma-separated, which keeps scripts compact and reduces the number of process invocations.
To remove a single entry, use -x (remove): setfacl -x u:deploy file removes exactly that entry without touching any other ACL entries or the classic bits. To strip all extended ACL entries at once and fall back to plain chmod logic, use setfacl -b file. The -R option applies any of these operations recursively across an entire directory tree, which becomes especially relevant combined with -d for default ACLs. Important: calling setfacl without -m or -x, but with plain --set, replaces the entire ACL rather than individual entries, which quickly causes unintended loss of rights if confused with -m.
#!/usr/bin/env bash
# setfacl: modify, remove, and reset ACL entries
set -euo pipefail
# Grant a user read/write/execute without touching owner or group
setfacl -m u:deploy:rwx /var/www/shared-uploads
# Grant a group read/traverse access
setfacl -m g:monitoring:r-x /var/www/shared-uploads
# Multiple entries in one call, comma-separated
setfacl -m u:deploy:rwx,g:monitoring:r-x,u:backup:r-x /var/www/shared-uploads
# Remove a single named entry, leave everything else untouched
setfacl -x u:deploy /var/www/shared-uploads
# Remove ALL extended ACL entries, fall back to plain chmod bits
setfacl -b /var/www/shared-uploads
# Apply recursively to an entire tree
setfacl -R -m u:deploy:rwx /var/www/shared-uploads
4. Practical example: deploy user writes to a foreign directory
A concrete scenario from day-to-day operations: a cache directory is owned by a service's system user, say redis:redis, with a strict baseline of 750. A separate deploy user, deploy, writing configuration files into this directory via CI/CD pipelines, needs write access without the service account being added to a foreign group or the ownership being changed. Without ACLs, the only option would be adding deploy to the redis group, which immediately grants that user access to every path owned by that group, far beyond the one target directory.
With a targeted ACL, access stays scoped exactly to the required path: setfacl -m u:deploy:rwx /var/lib/redis/config grants deploy full rights on exactly this directory, while owner and group remain unchanged at redis:redis. For directories with many subfolders, combine it with -R to cover the existing tree completely. What matters is that this solution stays stable even through a later change of the service account or a reinstall of the service, as long as the ACL is reapplied after a reinstall, since ACLs are not automatically managed by package managers.
For Magento setups, this pattern is especially relevant when a separate reporting or backup service needs read access to var/log, which is owned by the PHP-FPM user. Instead of adding the backup user to the www-data group and thereby potentially opening write access to the entire application, a targeted r-x entry for exactly this path is enough.
#!/usr/bin/env bash
# Grant a deploy user write access to a directory owned
# by an unrelated service account, without group changes
set -euo pipefail
readonly TARGET_DIR="/var/lib/redis/config"
readonly DEPLOY_USER="deploy"
readonly READONLY_USER="backup"
# Confirm current ownership stays with the service account
stat -c "%U:%G %n" "$TARGET_DIR" # redis:redis /var/lib/redis/config
# Grant the deploy user full access, scoped to this path only
setfacl -R -m u:${DEPLOY_USER}:rwx "$TARGET_DIR"
# Grant a backup service read-only traversal, no write at all
setfacl -R -m u:${READONLY_USER}:r-x "$TARGET_DIR"
# Verify: ownership is unchanged, ACL grants the extra access
getfacl "$TARGET_DIR" | grep -E "^(user|owner|group):"
5. Default ACLs: inheritance for newly created files
A regular ACL only applies to the file or directory it was set on. New files created later inside a directory inherit nothing from it. That is exactly what default ACLs are for, set with the d: prefix or the -d option: setfacl -d -m u:deploy:rwx directory/ ensures that every new file and every new subdirectory created inside that directory from that point on automatically receives the same ACL entry. This is comparable to the setgid bit, but with full control over arbitrary users instead of just the group.
Default ACLs are stored separately from regular access ACLs, so a directory can simultaneously carry a current access ACL for itself and a default ACL for future contents. In getfacl output, default entries appear prefixed with default:. There is an important distinction between files and directories: a default ACL granting execute rights does not automatically make new files executable, since a file's executable bit remains bounded by the internal mask and the original creation mode. New subdirectories, on the other hand, inherit both the access ACL and the default ACL of the parent directory, so inheritance continues to arbitrary depth.
#!/usr/bin/env bash
# Default ACLs: automatic inheritance for future files
set -euo pipefail
readonly SHARED_DIR="/var/www/shared-uploads"
# Set both an access ACL (applies now) and a default ACL
# (applies to every file/subdir created from now on)
setfacl -m u:deploy:rwx,g:monitoring:r-x "$SHARED_DIR"
setfacl -d -m u:deploy:rwx,g:monitoring:r-x "$SHARED_DIR"
# Create a new file as another user, then verify inheritance
sudo -u www-data touch "${SHARED_DIR}/report-2026-07-12.json"
getfacl "${SHARED_DIR}/report-2026-07-12.json"
# Expected: user:deploy:rwx and group:monitoring:r-x
# appear automatically, without any manual setfacl call
# Remove only the default ACL, keep the current access ACL intact
setfacl -k "$SHARED_DIR"
6. The ACL mask: the underestimated factor
Every extended ACL carries a mask:: entry that caps the maximum effective rights for all named user and group entries as well as the owning group. The classic owner's user:: entry is exempt from the mask, everything else is not. If you run setfacl -m u:deploy:rwx file on a file whose mask only allows r-x, deploy effectively still only gets r-x, even though the entry shows rwx. getfacl flags this discrepancy with an #effective: comment right next to the affected entry, a detail that is easy to miss in standard output.
setfacl recalculates the mask automatically by default as the union of all set rights, so this problem does not normally occur. It becomes relevant once chmod is applied to the same file after ACL entries were set: chmod on a file carrying an ACL does not modify the group rights in the classic sense at all, but directly rewrites the mask instead. A later chmod g-w file can therefore inadvertently restrict every named ACL entry, even ones explicitly set to rwx. Setting the mask explicitly with setfacl -m m::rwx file is the most reliable way to control this behavior rather than relying on implicit recalculation.
7. Backing up, copying, and restoring ACLs
Standard backup tools like tar silently discard ACL entries unless run with the --acls option, and cp does not carry them over either unless run with -p or --preserve=all. Anyone using ACLs on production directories must build these options explicitly into backup and deploy scripts, or the extended rights silently disappear at the next restore without any error being reported. This is one of the most common causes of permissions that seem to spontaneously vanish after a server migration.
For targeted backup and restore, the pair getfacl -R and setfacl --restore works well: a recursive getfacl dump can be version-controlled as a plain text file and fully restored on another server or after a rebuild with setfacl --restore=file. This approach is particularly valuable for disaster recovery plans, since it documents ACL state independently of the actual file content and makes it fully reproducible without depending on a specific backup format.
#!/usr/bin/env bash
# Backing up and restoring ACLs independently of file content
set -euo pipefail
readonly TARGET="/var/www/shared-uploads"
readonly ACL_DUMP="/var/backups/acl/shared-uploads.acl"
# tar preserves ACLs only with --acls, cp only with --preserve=all
tar --acls -czf /var/backups/shared-uploads.tar.gz "$TARGET"
cp -a --preserve=all "$TARGET" /mnt/backup/shared-uploads
# Dedicated ACL snapshot, independent of file content
mkdir -p "$(dirname "$ACL_DUMP")"
getfacl -R "$TARGET" > "$ACL_DUMP"
# Restore ACLs on a rebuilt directory tree after migration
setfacl --restore="$ACL_DUMP"
echo "[OK] ACL snapshot restored from ${ACL_DUMP}"
8. File system requirements and mount options
ACLs are not a pure userland feature; they must be supported and enabled by the file system itself. On current ext4 and XFS installations, ACL support is enabled by default nowadays, with no explicit mount option required. On older systems or manually written /etc/fstab entries, however, the option can be missing, causing setfacl to fail with the error Operation not supported. In that case, acl must be explicitly added as a mount option and the partition remounted.
Before any production use, it is worth a quick check with tune2fs -l /dev/sdX | grep "Default mount options" on ext file systems to see whether acl is already anchored as a default option in the superblock. If it is missing, it can be set permanently with tune2fs -o acl /dev/sdX, without any changes to /etc/fstab at all. Network file systems like NFS additionally require matching NFSv4 ACL support on both server and client side, which differs from the POSIX ACLs described here in the details but follows the same underlying concept.
# /etc/fstab: ensure ACL support is enabled on the mount
# Modern ext4/XFS defaults already include acl, but older
# or manually written entries may need it explicit.
# UUID=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx /var/www ext4 defaults,acl 0 2
/dev/sdb1 /var/www ext4 defaults,acl 0 2
# Remount without a reboot after editing fstab:
# mount -o remount /var/www
# Persist acl as a default mount option in the ext4 superblock
# tune2fs -o acl /dev/sdb1
9. ACLs compared to chmod and chown
Choosing between classic permissions and ACLs is not an either-or decision, but a question of the right tool for the specific use case. The table below lines up typical situations against the more sensible solution for each.
| Situation | Wrong / risky | Recommended solution | Why |
|---|---|---|---|
| Two teams need different rights | Add user to a foreign group | setfacl -m u:name:rwx path |
Access stays scoped to the path |
| Monitoring reads foreign logs | chmod o+r /var/log/service |
setfacl -m u:monitoring:r-x |
Not world-readable for every process |
| New files should inherit rights | Manual chmod after every upload | setfacl -d -m u:name:rwx directory |
Automatic inheritance, no manual step |
| Simple owner/group setup | ACLs over-engineered for a trivial case | chmod 750 plus chown |
Simpler, more transparent, less error-prone |
| Backup after migration | tar czf without --acls |
tar --acls or a getfacl -R dump |
ACL entries otherwise silently vanish |
As a rule of thumb: chmod and chown remain the first choice for simple, clearly delineated ownership relationships. As soon as more than one group needs different rights on the same path at the same time, or as soon as future files should automatically inherit certain rights, ACLs are the more precise and safer tool, because they uphold the principle of least privilege without disturbing existing ownership structures.
Mironsoft
Server hardening, deployment automation, and Magento hosting operations
Access control that goes beyond chmod?
We analyze shared directories and deploy workflows, set up precise ACLs with default inheritance, and document them so they survive every restore and every migration.
Access audit
Full review of ownership, groups, and existing ACL entries
ACL design
Setting up targeted setfacl entries and default ACLs for shared directories
Backup integration
Anchoring tar --acls and getfacl snapshots firmly in backup and deploy pipelines
10. Summary
ACLs extend the classic chmod model exactly where a single owner and a single group are not enough. getfacl reads existing entries, already recognizable from the appended + in ls -l. setfacl -m sets new entries for users and groups, setfacl -x removes individual entries in a targeted way, setfacl -b clears everything. A deploy user thereby gains write access to a foreign directory managed by a service account, without ownership or the primary group ever needing to be touched.
Default ACLs set with setfacl -d ensure these rights are automatically inherited by newly created files and subdirectories, comparable to the setgid bit but with full control over arbitrary users. The ACL mask caps effective rights and deserves special attention after a later chmod. Backup tools must save ACLs explicitly with --acls or via a getfacl dump, or they silently vanish at restore time. For simple cases chmod remains the right choice, while for shared directories with multiple access levels ACLs are the more precise tool.
ACLs: Extended Permissions Beyond chmod, the Essentials at a Glance
The core problem
chmod only knows one owner and one group. ACLs add an arbitrary number of extra user and group entries per file.
setfacl & getfacl
setfacl -m u:name:rwx sets, -x removes individually, -b clears everything. getfacl shows the current state.
Default ACLs
setfacl -d -m automatically inherits rights to newly created files and subdirectories.
Backup & mask
tar --acls and getfacl -R dumps back up ACLs. The mask caps effective rights after chmod.