}

Linux File Permissions Explained 2026: chmod, chown, umask, SUID/SGID, and ACLs

Linux file permissions are one of the fundamental building blocks of system security. Whether you are a developer deploying an application, a sysadmin hardening a server, or someone just getting started with Linux, understanding how permissions work is non-negotiable. This guide covers every layer: standard read/write/execute bits, the chmod and chown commands, umask, the special SUID/SGID/sticky bits, and finally POSIX Access Control Lists (ACLs) for cases where the standard model is not flexible enough.


Understanding Permission Bits

Every file and directory on a Linux filesystem has three sets of permission bits assigned to three categories of users:

  • Owner (user) — the account that owns the file
  • Group — a group of accounts that share access
  • Other — everyone else on the system

Each category has three independent bits:

BitSymbolOctal valueMeaning on a fileMeaning on a directory
Readr4View file contentsList directory contents (ls)
Writew2Modify file contentsCreate, rename, delete files inside
Executex1Run the file as a programEnter the directory (cd)

The total permission for one category is the sum of its bits. For example, rwx = 4+2+1 = 7, rw- = 4+2+0 = 6, r-x = 4+0+1 = 5, and r-- = 4+0+0 = 4.

A full permission set is three digits: one for owner, one for group, one for other. 755 means owner has rwx (7), group has r-x (5), other has r-x (5).


Reading ls -la Output

Run ls -la in any directory and you will see output like this:

-rwxr-xr--  2 alice  developers  4096  May 12 09:00  deploy.sh
drwxr-x---  5 alice  developers  4096  May 12 08:45  config/
lrwxrwxrwx  1 root   root          11  May 12 07:00  latest -> release-1.4

Each field has a precise meaning:

FieldExampleMeaning
File type- d l- = regular file, d = directory, l = symbolic link, c = char device, b = block device, p = named pipe, s = socket
Permissionsrwxr-xr--Three groups of three characters: owner, group, other
Hard link count2Number of hard links pointing to the inode
OwneraliceUser that owns the file
GroupdevelopersGroup associated with the file
Size4096Size in bytes (use -h for human-readable)
Date/timeMay 12 09:00Last modification time
Namedeploy.shFilename (symlinks show name -> target)

For the permissions string rwxr-xr--:

  • Characters 1-3 (rwx): owner can read, write, execute
  • Characters 4-6 (r-x): group members can read and execute, not write
  • Characters 7-9 (r--): others can only read

chmod — Changing Permissions

chmod (change mode) sets file permissions. You can use octal notation or symbolic notation.

Octal Notation

Specify three digits (owner, group, other), each being the sum of the desired bits.

chmod 755 script.sh      # owner rwx, group r-x, other r-x
chmod 644 config.yml     # owner rw-, group r--, other r--
chmod 600 ~/.ssh/id_rsa  # owner rw-, group ---, other ---  (private key)
chmod 777 /tmp/shared    # everyone rwx — use sparingly!
chmod 700 ~/private/     # owner rwx only, no access for anyone else
chmod 640 /etc/secret    # owner rw-, group r--, other ---

Common octal values quick-reference:

OctalSymbolicTypical use
777rwxrwxrwxFully open (avoid in production)
755rwxr-xr-xExecutables, public directories
750rwxr-x---Executables accessible to owner's group only
700rwx------Private executables
664rw-rw-r--Collaborative files (shared write within group)
644rw-r--r--Public read-only files, web content
640rw-r-----Config files readable by group only
600rw-------Private files (SSH keys, credentials)
400r--------Read-only by owner, immutable feel

Symbolic Notation

Symbolic mode is easier to reason about when you want to make a targeted change without affecting other bits.

Syntax: [who][operator][permissions]

  • Who: u (owner/user), g (group), o (other), a (all three)
  • Operator: + (add), - (remove), = (set exactly)
  • Permissions: r, w, x, s (setuid/setgid), t (sticky)
chmod u+x deploy.sh         # add execute for owner
chmod g-w sensitive.conf    # remove write from group
chmod o=r public.html       # set other to read-only (remove w and x if set)
chmod a+r README.txt        # add read for all (owner, group, other)
chmod ug+rw shared.log      # add read+write for owner and group
chmod o-rwx private/        # remove all permissions from other
chmod u=rwx,g=rx,o=        # owner rwx, group r-x, other nothing

Recursive chmod

Apply permissions to a directory and everything inside it:

chmod -R 755 /var/www/html/

A common pattern for web directories is to apply different permissions to files vs. directories:

find /var/www/html -type d -exec chmod 755 {} \;
find /var/www/html -type f -exec chmod 644 {} \;

This ensures directories remain traversable while files are not accidentally made executable.


chown and chgrp — Changing Ownership

chown

chown changes the owner and optionally the group of a file.

chown alice file.txt             # change owner to alice
chown alice:developers file.txt  # change owner to alice, group to developers
chown :developers file.txt       # change only the group (note the colon prefix)
chown -R alice:web /var/www/     # recursively change owner and group

You must be root (or use sudo) to change ownership to a different user. A regular user can change their own files' group to a group they belong to.

chgrp

chgrp changes only the group ownership:

chgrp developers project/        # change group of project directory
chgrp -R developers /srv/app/    # recursively change group

chown :groupname and chgrp groupname are equivalent; chown is more commonly used because it handles both in one call.


umask — Default Permission Mask

When a new file or directory is created, the kernel applies a umask (user file creation mask) to strip certain permission bits from the default. The umask is subtracted from the maximum defaults:

  • New files start with a maximum of 666 (no execute by default — you must explicitly add it)
  • New directories start with a maximum of 777

The umask value specifies which bits to remove. The actual permission = maximum − umask.

umaskNew file permissionsNew directory permissionsCommon use
022644 (rw-r--r--)755 (rwxr-xr-x)Standard default for most systems
027640 (rw-r-----)750 (rwxr-x---)More restrictive; others get nothing
077600 (rw-------)700 (rwx------)Private: nothing shared with group/other
002664 (rw-rw-r--)775 (rwxrwxr-x)Collaborative team environments

Viewing and Setting umask

umask          # show current umask (e.g., 0022)
umask 027      # set umask to 027 for current session
umask -S       # show in symbolic form: u=rwx,g=rx,o=

To make a umask persistent, add it to ~/.bashrc (per user) or /etc/profile (system-wide):

echo "umask 027" >> ~/.bashrc

Practical Example

With umask 022 in effect:

touch newfile.txt    # created as 644 (666 - 022 = 644)
mkdir newdir/        # created as 755 (777 - 022 = 755)

Special Permission Bits

Beyond the standard nine bits, Linux has three special bits that provide powerful (and potentially dangerous) capabilities.

SUID — Set User ID (4xxx)

When the SUID bit is set on an executable file, the program runs with the privileges of the file's owner, not the user who launched it. The execute bit for owner becomes s instead of x.

chmod u+s /usr/local/bin/myapp   # add SUID
chmod 4755 /usr/local/bin/myapp  # octal: 4 prefix sets SUID
ls -la /usr/bin/passwd
# -rwsr-xr-x 1 root root 59976 Mar  2 12:00 /usr/bin/passwd

The s in the owner execute position indicates SUID is set. The /usr/bin/passwd binary is the canonical example: it must write to /etc/shadow (owned by root) but is run by ordinary users. SUID allows this safely because the passwd binary is carefully written to only do what it is supposed to.

Security implications of SUID:

  • Any bug or vulnerability in a SUID binary can be exploited to gain the file owner's privileges (often root).
  • SUID shell scripts are ignored by the kernel in Linux (for this reason).
  • Attackers frequently look for world-writable SUID binaries or unusual SUID files as privilege escalation vectors.
  • Regularly audit SUID files — any unexpected SUID binary on a server is a serious red flag.

If the execute bit is NOT set for owner but SUID is, the s appears as S (capital), indicating a meaningless/broken SUID.

SGID — Set Group ID (2xxx)

SGID behaves differently depending on whether it is set on a file or a directory.

On a file: the executable runs with the privileges of the file's group.

chmod g+s /usr/bin/write    # example: write command uses tty group
chmod 2755 /usr/bin/write   # octal: 2 prefix sets SGID

On a directory: any new files created inside the directory inherit the directory's group rather than the creator's primary group. The execute bit for group becomes s.

chmod g+s /srv/shared-project/
ls -la /srv/
# drwxrwsr-x 3 root developers 4096 May 12 09:00 shared-project/

This is extremely useful for team shared directories. When multiple developers work in /srv/shared-project/, every new file they create automatically gets the developers group, so all team members retain access without manual chgrp calls.

Setting up a team shared directory:

mkdir /srv/teamwork
chown root:developers /srv/teamwork
chmod 2775 /srv/teamwork    # SGID + rwxrwsr-x

Now any developer who creates a file in /srv/teamwork will have that file inherit the developers group, preserving group-write access for all team members.

Sticky Bit (1xxx)

The sticky bit on a directory prevents users from deleting or renaming files they do not own, even if they have write permission on the directory. The execute bit for other becomes t.

chmod +t /tmp              # add sticky bit
chmod 1777 /tmp            # octal: 1 prefix sets sticky bit
ls -la /
# drwxrwxrwt 14 root root 4096 May 12 08:00 tmp/

The t in the other-execute position is the sticky bit in action on /tmp. Every user can create files there (because o+w), but no user can delete another user's files — only the file owner and root can.

The sticky bit is also used on /var/tmp. It is rarely needed elsewhere, but can be applied to any shared, world-writable directory.


Auditing Dangerous Permissions with find

Security audits should include searching for files with dangerous permission configurations.

Find All SUID Files

sudo find / -perm -4000 -type f 2>/dev/null

The -perm -4000 flag means "SUID bit is set" (the - prefix means "at least these bits"). Pipe through ls for details:

sudo find / -perm -4000 -type f -exec ls -la {} \; 2>/dev/null

Find All SGID Files

sudo find / -perm -2000 -type f 2>/dev/null

Find World-Writable Files

World-writable files (o+w) are dangerous because any user can modify them:

sudo find / -perm -0002 -type f 2>/dev/null
# Exclude /proc and /sys to reduce noise:
sudo find / -perm -0002 -type f -not -path "/proc/*" -not -path "/sys/*" 2>/dev/null

Find World-Writable Directories

sudo find / -perm -0002 -type d 2>/dev/null

Find Files with 777 Permissions

sudo find / -perm -777 -type f 2>/dev/null

Find Files with No Owner (Orphaned Inodes)

These can indicate deleted user accounts whose files remain on disk:

sudo find / -nouser -o -nogroup 2>/dev/null

Audit Home Directory Permissions

Home directories should never be world-readable or world-writable:

ls -la /home/
# Each home dir should be 700 or 750, not 755 or 777
sudo find /home -maxdepth 1 -type d -perm -o+r 2>/dev/null  # world-readable homes

Access Control Lists (ACLs)

Standard Unix permissions work with exactly one owner, one group, and one "other" category. This is insufficient for many real-world scenarios. Consider:

  • Alice needs read access to a file, but Bob needs read+write, and Charlie needs no access at all.
  • A web server process needs read access to a config file owned by admin, without giving the entire www-data group or all others access.

POSIX ACLs solve this by allowing arbitrary per-user and per-group permission entries on any file or directory.

Checking ACL Support

ACLs require filesystem support. Most modern Linux filesystems (ext4, XFS, Btrfs) support ACLs natively. To verify:

tune2fs -l /dev/sda1 | grep "Default mount options"
# Should include: acl

# Or check mount options:
mount | grep /dev/sda1
# Should include: acl

If ACLs are not enabled, add acl to the mount options in /etc/fstab:

/dev/sda1  /  ext4  defaults,acl  0 1

Install the ACL tools if not present:

# Debian/Ubuntu:
sudo apt install acl

# RHEL/CentOS/Fedora:
sudo dnf install acl

getfacl — View ACL Entries

getfacl file.txt

Output example:

# file: file.txt
# owner: alice
# group: developers
user::rw-
user:bob:rw-
group::r--
group:ops:r-x
mask::rw-
other::---

The lines break down as:

  • user::rw- — the file owner (alice) has rw-
  • user:bob:rw- — bob specifically has rw- (ACL entry)
  • group::r-- — the file's group (developers) has r--
  • group:ops:r-x — the ops group has r-x (ACL entry)
  • mask::rw- — the effective permission mask (caps what ACL entries can grant)
  • other::--- — everyone else has no permissions

setfacl — Set ACL Entries

# Grant alice read+write on file.txt
setfacl -m u:alice:rw file.txt

# Grant the devteam group read access
setfacl -m g:devteam:r file.txt

# Grant bob read+write+execute
setfacl -m u:bob:rwx script.sh

# Multiple entries in one command
setfacl -m u:alice:rw,g:devteam:r file.txt

# Remove a specific ACL entry for alice
setfacl -x u:alice file.txt

# Remove all ACL entries (revert to standard permissions)
setfacl -b file.txt

When an ACL is set, ls -la shows a + at the end of the permissions string:

-rw-rw----+ 1 alice developers 1024 May 12 09:00 file.txt

The + is your signal to run getfacl to see the full picture.

Default ACLs on Directories

Default ACLs are inherited by newly created files and subdirectories inside a directory. This is crucial for shared team directories — without default ACLs, every new file starts with only standard permissions and ACL entries must be re-applied manually.

# Set a default ACL so all new files in /shared/ grant devteam read+write
setfacl -d -m g:devteam:rw /shared/

# Set default ACL for a specific user
setfacl -d -m u:bob:rx /shared/

# View both regular and default ACLs
getfacl /shared/

Example output for a directory with default ACLs:

# file: shared/
# owner: root
# group: developers
user::rwx
group::rwx
other::r-x
default:user::rwx
default:group::rwx
default:group:devteam:rw-
default:other::r-x

Any file created inside /shared/ will automatically receive the devteam:rw- ACL entry.

Full Shared Directory Setup Example

# Create a shared project directory
sudo mkdir /srv/project-alpha

# Set owner and base group
sudo chown root:developers /srv/project-alpha

# rwxrws--- with SGID so new files inherit developers group
sudo chmod 2770 /srv/project-alpha

# Grant the devops group read+execute (deploy access)
sudo setfacl -m g:devops:rx /srv/project-alpha

# Default ACL: new files grant devops group read access
sudo setfacl -d -m g:devops:r /srv/project-alpha

# Grant alice (a contractor) read-only access
sudo setfacl -m u:alice:rx /srv/project-alpha
sudo setfacl -d -m u:alice:r /srv/project-alpha

# Verify
getfacl /srv/project-alpha

Security Checklist

Use this checklist when hardening a Linux system or reviewing permissions after a deployment.

Home directories

# Home dirs should be 700 or at most 750
chmod 700 /home/username
# SSH directory must be 700, authorized_keys must be 600
chmod 700 ~/.ssh && chmod 600 ~/.ssh/authorized_keys

Web application files

# Web content: files 644, directories 755
find /var/www -type f -exec chmod 644 {} \;
find /var/www -type d -exec chmod 755 {} \;
# Config files with credentials: 640 (web process group can read, others cannot)
chmod 640 /var/www/app/config/database.yml
chown root:www-data /var/www/app/config/database.yml

Sensitive configuration files

# /etc/shadow: 640 root:shadow (or 000 on some distros)
ls -la /etc/shadow /etc/passwd /etc/sudoers
# /etc/crontab and cron directories should be root-owned, not world-writable
ls -la /etc/cron*

SUID/SGID audit

# Save baseline list of SUID binaries
sudo find / -perm -4000 -type f 2>/dev/null | sort > /root/suid-baseline.txt
# Compare later to detect new SUID files (possible privilege escalation backdoor)
sudo find / -perm -4000 -type f 2>/dev/null | sort > /tmp/suid-current.txt
diff /root/suid-baseline.txt /tmp/suid-current.txt

World-writable file audit

sudo find /etc /usr /bin /sbin -perm -0002 -type f 2>/dev/null
# Any result here is a serious misconfiguration

Check for files with no owner

sudo find / -nouser -o -nogroup 2>/dev/null | grep -v "^/proc" | grep -v "^/sys"

Summary

Linux file permissions operate in layers. Start with the basics — the nine permission bits for owner, group, and other — and use ls -la to read them fluently. Use chmod in octal for precision and in symbolic form for targeted tweaks. Manage ownership with chown and control defaults with umask.

The special bits (SUID, SGID, sticky) are powerful tools: SGID on shared directories saves teams from constant chgrp calls, the sticky bit protects /tmp, and SUID enables carefully written programs like passwd to run with elevated privileges. Treat any unexpected SUID binary as a security incident until proven otherwise.

When standard permissions are not flexible enough — multiple users need different levels of access to the same resource — reach for ACLs. The setfacl/getfacl pair integrates transparently with the standard permission model and gives you per-user, per-group granularity.

Run periodic audits with find -perm commands to catch world-writable files, unexpected SUID binaries, and orphaned inodes before attackers do.

Further reading:

Leonardo Lazzaro

Software engineer and technical writer. 10+ years experience in DevOps, Python, and Linux systems.

More articles by Leonardo Lazzaro