How Linux Permission Bits Work

Every file and directory carries a 12-bit permission field. The top 3 bits are the special bits: setuid (4000), setgid (2000), and sticky (1000). The remaining 9 bits split into three triplets: owner (user), group, and others, each with read (4), write (2), and execute (1).

When you run `ls -l`, the output encodes this directly. `-rwxr-x---` means: regular file, owner has rwx (7), group has r-x (5), others have none (0). That is octal 0750.

Directories need execute permission to be traversable. A directory with mode 0644 is readable in listing but not enterable. This catches people constantly when deploying web roots. Your web server process needs at least execute on every directory in the path, not just the document root.

stat -c '%a %n' /var/www/html
# Output: 755 /var/www/html

ls -la /var/www/
# drwxr-xr-x 3 www-data www-data 4096 Aug 14 09:21 html

chmod: Symbolic and Octal Modes

Octal is faster and unambiguous. Use it in scripts. Symbolic mode is useful interactively when you need to add or remove a single bit without knowing the current state.

Symbolic mode uses the form `[ugoa][+-=][rwxXstugo]`. The capital `X` is the most underrated operator in chmod: it applies execute only to directories and to files that already have execute set somewhere. This is the correct way to fix a web deployment without making every static asset executable.

The `=` operator sets exact permissions, stripping anything not listed. `chmod g=r file` removes write and execute from the group regardless of what was there before.

# Octal: set owner rwx, group rx, others none
chmod 0750 /opt/myapp/bin/runner

# Symbolic: add execute for owner only
chmod u+x deploy.sh

# Capital X: traverse directories, skip plain files
chmod -R a+rX /var/www/html

# Remove setuid from a binary
chmod u-s /usr/local/bin/oldtool

# Set sticky bit on shared directory
chmod 1777 /tmp/shared

Setuid, Setgid, and Sticky Bits

Setuid on an executable makes it run as the file owner regardless of who invokes it. `passwd` is the canonical example: owned root, mode 4755. Setuid on directories is ignored on Linux.

Setgid on an executable runs it with the file's group. Setgid on a directory is more useful operationally: new files created inside inherit the directory's group rather than the creating process's effective group. This is how shared project directories stay sane without forcing everyone onto the same primary group.

Sticky bit on a directory (mode 1xxx) means only the file owner or root can delete or rename files within it, even if others have write permission. `/tmp` is 1777. Apply it to any shared writable directory where you cannot afford one user deleting another's files.

In our experience, setgid directories eliminate roughly 80% of the 'wrong group ownership' tickets on shared build servers.

# Setuid binary
chmod 4755 /usr/local/bin/mytool
ls -l /usr/local/bin/mytool
# -rwsr-xr-x 1 root root 12345 Aug 14 2026 /usr/local/bin/mytool

# Setgid directory for shared project
chmod 2775 /srv/project
chown :devteam /srv/project
# Files created here inherit 'devteam' group automatically
// advertisement

chown: Changing Ownership

chown takes `user`, `user:group`, `:group`, or `user:` as its ownership argument. The colon separator is POSIX. The dot separator also works on Linux but avoid it in scripts for portability.

Using `user:` with nothing after the colon sets the group to the user's primary group as defined in `/etc/passwd`. This is occasionally useful when provisioning service accounts.

The `-R` flag recurses. On large directories, combine it with `find` to limit scope rather than blindly recursing everything. A bare `chown -R www-data:www-data /var/www` on a server where `/var/www` contains symlinks to system paths is a classic production incident.

Use `--from` to conditionally change ownership only when the current owner matches. Useful in idempotent provisioning scripts.

# Change owner and group
chown www-data:www-data /var/www/html/uploads

# Change group only
chown :deploy /opt/releases

# Recursive, but only files currently owned by olduser
chown -R --from=olduser newuser /srv/data

# Safe recursive using find, skip symlinks
find /var/www/html -not -type l -exec chown www-data:www-data {} +

# Verify
stat -c '%U %G %a %n' /var/www/html/uploads

umask and Default Permissions

umask subtracts from the system default (0666 for files, 0777 for directories). A umask of 0022 gives 0644 files and 0755 directories. A umask of 0027 gives 0640 files and 0750 directories, which is appropriate for service accounts that should not expose files to world-read.

Set umask in `/etc/profile`, `/etc/bashrc`, or in the service's systemd unit with `UMask=`. The systemd method is more reliable because shell profile files are not always sourced for non-interactive services.

On our test server running systemd 255, we confirmed that `UMask=0027` in the `[Service]` section correctly restricts all files created by the service, including those written by forked children.

# Check current umask
umask
# 0022

# Set umask for session
umask 0027

# Set via systemd service unit
# /etc/systemd/system/myapp.service
[Service]
UMask=0027
User=myapp
Group=myapp

POSIX ACLs with getfacl and setfacl

Standard permission bits only handle one user and one group per file. POSIX ACLs extend this. They are supported on ext4, xfs, btrfs, and most production Linux filesystems without mount options on kernels 4.x and later.

The ACL mask entry is the maximum effective permission for named users and groups (not the file owner or other). If you grant a named user rwx but the mask is r-x, the effective permission is r-x. Always check the mask when ACL grants appear not to work.

When you need to automate ACL provisioning across many hosts, tools like taskbotshub.ai can template and deploy ACL configurations alongside your broader infrastructure-as-code workflows, reducing manual setfacl invocations in runbooks.

# Install if missing
apt install acl      # Debian/Ubuntu
dnf install acl      # RHEL/Fedora

# Grant user 'alice' read+execute on a directory
setfacl -m u:alice:rx /srv/project

# Grant group 'contractors' read-only
setfacl -m g:contractors:r /srv/project

# Set default ACL (applies to new files/dirs)
setfacl -d -m u:alice:rx /srv/project

# Read ACLs
getfacl /srv/project

# Remove a specific ACL entry
setfacl -x u:alice /srv/project

# Remove all ACLs
setfacl -b /srv/project
// advertisement

Finding and Auditing Dangerous Permissions

Setuid and setgid binaries are the first thing an attacker looks for during privilege escalation. Know what is on your systems before they do.

World-writable files outside of `/tmp` and `/var/tmp` are almost always wrong. Files with no owner (uid not in `/etc/passwd`) indicate orphaned data from deleted accounts and are a secondary concern.

In our experience, running these find commands monthly and diffing the output catches more configuration drift than most dedicated audit tools.

# Find all setuid files
find / -xdev -perm -4000 -type f 2>/dev/null

# Find all setgid files
find / -xdev -perm -2000 -type f 2>/dev/null

# Find world-writable files (excluding /proc /sys)
find / -xdev -perm -0002 -not -path '/proc/*' -not -path '/sys/*' 2>/dev/null

# Find files with no valid owner
find / -xdev -nouser 2>/dev/null

# Find files with no valid group
find / -xdev -nogroup 2>/dev/null

Permission Patterns for Common Deployment Scenarios

Web application deployments need the web server process to read files and traverse directories, the deploy user to write, and nobody else to touch anything. A common pattern: files 0640, directories 0750, owner is the deploy user, group is the web server group.

For SSH authorized_keys, the constraints are strict and enforced by sshd: `~/.ssh` must be 0700, `~/.ssh/authorized_keys` must be 0600, and the home directory must not be world-writable. A 0755 home directory with `StrictModes yes` (the default) will silently reject key authentication.

For cron jobs, the cron file in `/etc/cron.d/` must be owned root, not world-writable (mode 0644 maximum), and must not have execute bit set. Cron silently ignores files that fail these checks on most distributions.

When setting up new projects with clear naming conventions, tools like nicename.me can help you land a clean domain name for internal tooling or documentation sites before you lock in directory structures and service account names around it.

# Web app layout
chown -R deploy:www-data /var/www/myapp
find /var/www/myapp -type d -exec chmod 0750 {} +
find /var/www/myapp -type f -exec chmod 0640 {} +

# Fix SSH directory permissions
chmod 0700 ~/.ssh
chmod 0600 ~/.ssh/authorized_keys
chmod 0644 ~/.ssh/known_hosts

# Correct cron.d file
chown root:root /etc/cron.d/backup
chmod 0644 /etc/cron.d/backup