Kokil Thapa - Professional Web Developer in Nepal
Freelancer Web Developer in Nepal with 15+ Years of Experience

Kokil Thapa is an experienced full-stack web developer focused on building fast, secure, and scalable web applications. He helps businesses and individuals create SEO-friendly, user-focused digital platforms designed for long-term growth.

Linux File Permissions and ACLs Explained

By Kokil Thapa | Last reviewed: September 2026

Linux file permissions and ACLs explained starts with a simple idea: every file and directory carries an owner, a group, and a rule set that decides who can read, write, or execute it. That model works until a team grows, a web app needs shared write access, or a deployment script breaks because storage/ is not writable. On production Ubuntu servers I maintain for Linux system administration clients, permission errors still rank among the top post-deploy failures. This guide walks through classic permission bits, special modes, ACLs, and the commands you actually use when debugging a live box.

What Are the Basic Linux File Permission Bits?

Every file on a Linux filesystem stores metadata in an inode. Part of that metadata is the permission mask. You see it as ten characters when you run ls -l. The first character shows the file type. The next nine are permission triplets for the owner, the group, and everyone else.

Each triplet uses three letters: r (read), w (write), and x (execute). A dash means the bit is off. Directories treat execute differently: you need execute on a folder to enter it or list its contents through that path.

$ ls -l /var/www/app/storage/logs/laravel.log
-rw-rw-r-- 1 www-data deploy 4821 Sep 10 09:14 laravel.log

Read that line left to right. User www-data owns the file. Group deploy applies to members of that group. Owner bits are rw-. Group bits match. Other bits are r--. Only the owner and group can append log lines. Everyone else can read.

Numeric (octal) notation

Octal notation packs the same nine bits into three digits. Read equals 4. Write equals 2. Execute equals 1. Add them per triplet. Common values:

  • 644 — owner read/write, group read, other read (typical config file)
  • 755 — owner full, group/other read+execute (scripts, directories)
  • 600 — owner read/write only (private keys, .env)
  • 775 — shared group write on directories (deploy + web server)
chmod 640 .env
chmod 775 storage bootstrap/cache
chmod 755 artisan

On a Laravel web application, those three paths show up in almost every permission checklist. The app binary stays executable. Secrets stay tight. Writable runtime dirs stay group-writable for the deploy user and PHP-FPM pool user.

Classic Permission ModelOwner (u)r w xUser who owns fileGroup (g)r w xPrimary group membersOther (o)r w xAll remaining usersExample: -rw-rw-r-- (664)Owner + group read/writeOthers read only
Linux file permissions and ACLs explained: the owner, group, and other triplets that chmod modifies

How Do chmod, chown, and umask Work Together?

Three tools handle most day-to-day permission work. They overlap in purpose but do not replace each other.

  1. chmod — changes permission bits on existing files.
  2. chown — changes owner and/or group.
  3. umask — subtracts bits from the default mode on new files.

chmod: symbolic and numeric modes

Symbolic mode is readable in scripts. Numeric mode is compact. Both are valid.

chmod u+x deploy.sh
chmod g+w shared/uploads
chmod o-rwx private.key
chmod -R g+s storage

The -R flag recurses into directories. Use it carefully on large trees. A wrong recursive chmod on / can brick a server. Scope commands to the project root.

chown and chgrp

Only root (or a process with CAP_FOWNER) can give away ownership. That is intentional. A common pattern after Ubuntu file permissions hardening:

sudo chown -R deploy:www-data /var/www/myapp
sudo find /var/www/myapp -type f -exec chmod 644 {} \;
sudo find /var/www/myapp -type d -exec chmod 755 {} \;

chgrp changes only the group. It is shorthand when the owner stays the same.

umask: default mask for new files

When a process creates a file, the kernel applies a default mode, then subtracts the umask. A umask of 022 yields 644 files and 755 directories from typical defaults. Check yours:

umask
umask 002

Setting umask 002 in a shared dev group gives group-writable new files. That helps CI runners and developers co-edit without constant chmod fixes. Document the umask in your deploy notes alongside PHP and Node versions.

Application-level roles differ from filesystem roles. Laravel Spatie Permission role management handles who sees an admin panel. Linux permissions handle whether PHP can write a cache file. Both layers must align.

What Are setuid, setgid, and the Sticky Bit?

Beyond rwx, three special bits change behaviour. They show up in the first octal digit or as s and t in ls -l.

BitOn filesOn directoriesExample use
setuid (4xxx)Run as file ownerNo effectpasswd, legacy tools
setgid (2xxx)Run as file groupNew files inherit dir groupShared storage/ trees
sticky (1xxx)No effectOnly owner deletes own files/tmp

The setgid bit on a directory is the one I use most on web servers. When www-data and deploy share group www-data, setgid keeps uploaded files group-owned correctly:

sudo chgrp -R www-data storage bootstrap/cache
sudo chmod -R g+rws storage bootstrap/cache

The sticky bit on /tmp stops users from deleting each other's temp files. You rarely set it yourself. The OS already did.

Compare this with stopping Git from tracking file permission changes. Git stores the executable bit, not full Unix modes. A chmod 777 on your laptop may not match production unless you enforce ownership in deploy scripts.

Special Permission Bitssetuid (s on u)Process runs asfile ownerchmod 4755 binarysetgid (s on g)Dir: new filesinherit groupchmod 2775 storage/sticky (t on o)Dir: delete ownfiles onlychmod 1777 /tmpWeb deploy pattern: setgid on shared write dirsdeploy user + www-data share groupNew uploads stay group-writable
setuid, setgid, and sticky bits extend basic Linux file permissions beyond simple rwx triplets

When Do You Need ACLs Instead of Standard Permissions?

Classic permissions allow exactly one owner, one group, and one other rule. That breaks down when you need:

  • Two groups with different write levels on one directory
  • A single extra user with read-only access to one subtree
  • CI, backup, and web-server accounts with distinct rights on the same path

Access Control Lists (ACLs) attach extended entries to a file or directory. POSIX ACLs are the standard on Linux ext4, XFS, and Btrfs when the feature is enabled. Check mount options with mount | grep acl or tune2fs -l /dev/sda1 for ext4.

Reading and writing ACLs

getfacl /var/www/shared/docs
setfacl -m u:backup:rx /var/www/shared/docs
setfacl -m g:developers:rw /var/www/shared/docs
setfacl -d -m g:developers:rw /var/www/shared/docs

The -m flag modifies entries. The -d flag sets defaults for new children inside a directory. The -x flag removes an entry. The -b flag strips all ACLs and returns to classic mode only.

After you set an ACL, ls -l shows a plus sign at the end of the mode string:

drwxrwsr-x+ 4 deploy www-data 4096 Sep 10  storage

The plus means "more ACL entries exist—run getfacl." That detail saves hours when auditing a server inherited from another host.

ACL vs classic permissions: decision guide

ScenarioClassic chmod/chownACL (setfacl)
Single web user + deploy groupUsually enough with setgidOptional
Backup user needs read, no group membershipAwkward world-readable dirsClean fit
Three teams, different folder accessMultiple groups + sudo jugglingPreferred
Public read, restricted write644/755 worksOverkill
Legal document portal with role foldersHard to maintainStrong fit

On legal-tech portals where staff, clients, and automated jobs touch the same upload tree, ACLs reduce the temptation to chmod 777. That shortcut creates real file upload security exposure. Named ACL entries beat world-writable directories every time.

Official references: the chmod manual page documents permission bits, and the setfacl manual page covers ACL entry syntax on current Linux distributions.

ACL Permission Check OrderProcess requests access1. Owner class (ACL_USER_OBJ)2. Named user entries3. Owning group + named groups4. Mask limits max group rights5. Other class — allow or deny
Linux ACL evaluation walks owner, named users, groups, mask, and other in a fixed order

How Do You Fix Permission Problems on a Production Web Server?

Permission bugs look like application errors. Laravel throws "failed to open stream" when storage/logs is not writable. Nginx returns 403 when the worker cannot traverse a parent directory. PHP-FPM runs as a pool user—often www-data on Ubuntu—not as your SSH login.

Diagnose before you chmod

namei -l /var/www/app/storage/logs/laravel.log
sudo -u www-data test -w /var/www/app/storage/logs && echo writable
id deploy
groups deploy

namei -l prints each path component with permissions. A common failure: every file is correct but /var/www lacks execute for others, so the web user cannot descend the tree. Fix the chain, not just the leaf.

A repeatable Laravel permission recipe

I've used this pattern on production Laravel applications deployed with Deployer 7 and GitLab CI:

  1. Create a shared group: www-data or a project-specific group.
  2. Add deploy and PHP-FPM users to that group.
  3. Set ownership: deploy:www-data on the release tree.
  4. Directories 775 with setgid; files 664.
  5. Keep .env at 640 or 600.
  6. Persist storage and bootstrap/cache in a shared directory across releases.
setfacl -R -m g:www-data:rwX storage bootstrap/cache
setfacl -R -d -m g:www-data:rwX storage bootstrap/cache

The capital X in ACL mode adds execute only on directories. That avoids marking every file executable. Document this in your runbook next to automated database backups on Linux and log rotation policies.

Sister sites on shared EC2 infrastructure—legal portals, translation services—share the same deploy pipeline. Wrong ownership after symlink swap is a recurring issue. Reload PHP-FPM after deploy so opcache picks up code, but permissions live on disk independently of opcache.

What not to do

chmod -R 777 is a emergency bandage, not a policy. World-writable paths let any local user alter uploads, inject PHP if execution is misconfigured, and exfiltrate data. If you reach for 777, step back and fix group membership or ACLs instead.

Strong filesystem permissions complement app-level checks. Use a password generator for service accounts. Rotate SSH keys. Restrict sudo. Filesystem ACLs are one layer in a stack that includes firewall rules—see iptables vs nftables—and service isolation via systemd service units.

Production Permission Debug Flow403 / 500 errorApp log cluenamei -l pathFind broken linkid pool userCheck group matchtest -wConfirm writeFix: chown + chmod 775/setgidOr setfacl for extra usersDocument in deploy script + runbookPrevent repeat on next release
Debug Linux file permissions on production: trace the path, verify the pool user, then apply chown, chmod, or ACL fixes

How Do Default ACLs and ACL Masks Affect Effective Permissions?

Two ACL concepts trip people up: the mask and default ACLs.

The ACL mask

The mask entry caps the maximum permission granted to named users and groups. It does not limit the file owner or the owning group class entry in the same way. If mask is r-- but a named user entry says rw-, effective access is read-only.

getfacl report.pdf
user:alice:rw-
group:finance:r--
mask::r--
other::r--

Alice gets read only because mask wins. Raise mask with setfacl -m m:rw report.pdf when you truly intend group-level write.

Default ACLs on directories

Default entries do not grant access to the directory itself. They define templates for new files and subdirectories created inside. This mirrors setgid behaviour but with per-user granularity.

setfacl -d -m u:ci-runner:rw /var/www/build-artifacts
setfacl -d -m m:rw /var/www/build-artifacts

Pair default ACLs with cron jobs that rotate artifacts. Old files keep the mode they were born with. New files pick up current defaults.

For deeper background, the Arch Linux ACL wiki documents POSIX ACL types and chmod interaction. Ubuntu enables ACLs on ext4 by default on current LTS releases.

Monitoring helps catch drift. A nightly script can compare getfacl output to a golden file and alert through your stack—similar to Linux server monitoring with Netdata. On a portfolio project like Adventure Third Pole Trek, booking uploads and supplier documents lived in shared trees where ACL defaults prevented permission regression after manual edits.

Key Takeaways

  • Classic Linux permissions use owner, group, and other rwx triplets; octal (644, 775) and symbolic (u+rw,g+r) modes both map to the same bits.
  • Use chown for ownership, chmod for bits, and umask for defaults on newly created files—document all three in deploy runbooks.
  • setgid on shared directories keeps new files group-owned; sticky bit protects world-writable temp dirs like /tmp.
  • ACLs via setfacl add named users and groups when one owner and one group are not enough—avoid chmod 777.
  • Debug with namei -l and sudo -u www-data test -w before changing production modes.
  • Align filesystem permissions with app-level auth; they solve different problems and both matter on live servers.

People Also Ask

What is the difference between chmod and setfacl?

chmod changes the classic owner, group, and other bits that every file has. setfacl adds extended entries for specific users and groups without reshuffling ownership. Use chmod for standard web deploy layouts. Reach for setfacl when a third account needs distinct access on the same path.

What does chmod 755 mean?

755 means the owner can read, write, and execute (7). Group and others can read and execute (5) but not write. It is the usual mode for directories and executable scripts. Config files with secrets should be tighter—typically 640 or 600.

How do I see ACL permissions on a file?

Run getfacl /path/to/file. If ls -l shows a plus sign after the mode (drwxr-xr-x+), extended ACL entries exist beyond the nine classic bits. Review them before copying trees with cp -a or rsync, because ACLs may need explicit preservation flags.

Why do new files not match the parent directory permissions?

New file mode equals the creation default minus umask—not the parent mode directly. On directories with setgid, new files inherit the parent group. With default ACLs, new entries copy those templates. If results look wrong, check umask, setgid, and default ACL entries together.

Put Linux File Permissions and ACLs to Work on Your Stack

Linux file permissions and ACLs explained only matter when they keep production stable. Start with owner, group, and mode on every deploy. Add setgid shared dirs before you reach for ACLs. Reach for ACLs before you reach for 777. Tie ownership fixes into your CI/CD step the same way you handle Composer installs and PHP-FPM reloads.

If permission errors block a Laravel app, WordPress site, or custom portal on Ubuntu, structured hardening beats one-off chmod commands. See support and maintenance services for ongoing server care, or review Notary Kathmandu and similar deployments where document uploads depend on correct filesystem access. For hosting and DNS alignment with file paths, domain and hosting setup covers the full stack.

Need help auditing permissions on a live server or writing a deploy recipe that survives the next release? Contact us and describe your stack—PHP version, web server, and deploy tool. A short getfacl export often points to the fix within minutes.

Frequently Asked Questions

Every file and directory on Linux has an owner, a group, and a permission mask stored in its inode. The mask defines read, write, and execute rights for the owner, the group, and everyone else. You see it as nine rwx characters after the file type when you run ls -l. Directories need execute permission to enter or traverse through that path.

755 means the owner can read, write, and execute (7). Group and others can read and execute (5) but not write. It is the usual mode for directories and executable scripts.

chmod changes the classic owner, group, and other bits every file has. setfacl adds extended entries for specific users and groups without reshuffling ownership. Use chmod for standard web deploy layouts; reach for setfacl when a third account needs distinct access on the same path.

chmod changes permission bits on existing files using symbolic or numeric modes. chown changes owner and group; only root or a process with CAP_FOWNER can give away ownership. umask subtracts bits from the default mode on newly created files—a umask of 022 typically yields 644 files and 755 directories. Setting umask 002 in a shared dev group gives group-writable new files. Document all three in deploy runbooks alongside application versions.

Beyond basic rwx, three special bits change behaviour. setuid on a file runs it as the file owner. setgid on a directory makes new files inherit the directory group—useful when www-data and deploy share storage on web servers. The sticky bit on directories like /tmp lets only each file owner delete their own files. On shared Laravel storage trees, setgid with g+rws keeps uploads group-owned correctly.

Classic permissions allow exactly one owner, one group, and one other rule. That breaks when two groups need different write levels, a backup user needs read without group membership, or CI, backup, and web-server accounts need distinct rights on one path. POSIX ACLs on ext4, XFS, and Btrfs add named user and group entries via setfacl. On legal-tech portals where staff, clients, and automated jobs share upload trees, ACLs beat chmod 777.

Run getfacl /path/to/file. If ls -l shows a plus sign after the mode string, such as drwxr-xr-x+, extended ACL entries exist beyond the nine classic bits. Review them before copying trees with cp -a or rsync, because ACLs may need explicit preservation flags. That plus sign saves hours when auditing a server inherited from another host.

New file mode equals the creation default minus umask—not the parent mode directly. On directories with setgid, new files inherit the parent group. With default ACLs set via setfacl -d, new entries copy those templates. If results look wrong, check umask, setgid, and default ACL entries together. Old files keep the mode they were born with; only new files pick up current defaults.

Default ACL entries on a directory define templates for new children—they do not grant access to the directory itself. The mask entry caps maximum permission for named users and groups; if mask is r-- but a named user entry says rw-, effective access is read-only. Raise mask with setfacl -m m:rw when you truly intend group-level write. Pair default ACLs with artifact rotation so old and new files behave predictably.

Permission bugs look like application errors. Laravel throws failed to open stream when storage is not writable; Nginx returns 403 when the worker cannot traverse a parent directory. PHP-FPM runs as a pool user like www-data, not your SSH login. Diagnose with namei -l on the full path and sudo -u www-data test -w before changing modes. A common failure: files are correct but a parent directory like /var/www lacks execute for the web user.

A pattern I use with Deployer 7 and GitLab CI: create a shared group, add deploy and PHP-FPM users, set ownership deploy:www-data on the release tree, directories 775 with setgid and files 664, keep .env at 640 or 600, persist storage and bootstrap/cache in shared directories across releases. Optionally apply setfacl -R -m g:www-data:rwX with matching default entries. The capital X adds execute only on directories.

World-writable paths let any local user alter uploads, inject PHP if execution is misconfigured, and exfiltrate data. chmod -R 777 is an emergency bandage, not a policy. If you reach for 777, step back and fix group membership or ACLs instead. Named ACL entries beat world-writable directories every time. Strong filesystem permissions complement app-level auth checks—they solve different problems and both matter on live servers.

Trace the full path with namei -l, which prints each component with its permissions. Verify the pool user with sudo -u www-data test -w on the target path and confirm group membership with id and groups. Fix the permission chain, not just the leaf—a file may be writable while a parent directory blocks traversal. Reload PHP-FPM after deploy for opcache, but permissions live on disk independently.

Octal packs nine bits into three digits: read is 4, write is 2, execute is 1, summed per triplet. Common values from production checklists: 644 for config files with owner read/write and group/other read; 755 for scripts and directories; 600 or 640 for private keys and .env; 775 for shared group-write directories used by deploy and the web server together on Laravel storage and bootstrap/cache paths.

Application roles like Laravel Spatie Permission decide who sees an admin panel. Linux permissions decide whether PHP can write a cache file. Both layers must align but they do not replace each other. Filesystem ACLs are one layer in a stack that also includes SSH key rotation, sudo restrictions, firewall rules, and service isolation via systemd. Structured hardening beats one-off chmod commands on every deploy.

Share this article

0 Comments

Leave a comment

Your email is not published. Comments appear once they have been read. Sign in to have your details filled in.

Quick Contact Options
Choose how you want to connect me: