Linux Fundamentals

Permissions & Ownership

18 min Lesson 6 of 26

Permissions & Ownership

Every file and directory on a Linux system carries a small but powerful security record: an owner, a group, and a set of permission bits. Get these wrong on a production server and you create either a security hole or an outage — usually both, at the worst possible moment. This lesson breaks down the entire model, from the raw bit layout to the production defaults that big-tech operations teams enforce on day one.

The Three Layers: Owner, Group, Others

Linux tracks three identities for every filesystem object:

  • Owner (u — user): the account that created the file, or whoever was assigned with chown.
  • Group (g): a named collection of users. Any member of the assigned group receives the group permissions.
  • Others (o): everyone else — any process running as a user that is neither the owner nor in the group.

The kernel checks these layers in order and stops at the first match. If you are the owner, only the owner bits apply — even if the group bits are more permissive.

The rwx Bits — Reading the Permission String

Run ls -l on any file and the leftmost column is a 10-character string like -rwxr-xr--. Breaking it down:

Linux Permission Bit Layout - r w x r - x r - - Type (- file) Owner (u) rwx = 7 Group (g) r-x = 5 Others (o) r-- = 4 Bit Values r (read) = 4 w (write) = 2 x (execute) = 1 - (none) = 0 Full mode: -rwxr-xr-- = 0754 Owner: r+w+x = 4+2+1 = 7 Group: r+x = 4+0+1 = 5 Others: r = 4+0+0 = 4 Directory x bit x = can cd into it w = can create/delete r = can list contents
Linux permission string anatomy: type bit, then three rwx triplets for owner, group, and others.

For directories, the bits mean something slightly different: r lets you list the contents (ls), w lets you create or delete files inside it, and x (the "execute" bit) is the search bit — without it you cannot cd into the directory or traverse it in a path, even if you have read permission.

chmod — Changing Permissions

Two syntaxes exist: symbolic and octal. Both are used daily; know both cold.

# --- Symbolic mode --- chmod u+x deploy.sh # add execute for owner chmod g-w secrets.conf # remove write from group chmod o=r report.txt # set others to read-only (exactly) chmod a+x script.sh # add execute for ALL (a = ugo) chmod u+x,g-w,o-rwx app.sh # multiple changes in one call # --- Octal mode (most common in scripts and IaC) --- chmod 755 /usr/local/bin/myapp # rwxr-xr-x (typical binary) chmod 644 /etc/nginx/nginx.conf # rw-r--r-- (config file) chmod 600 ~/.ssh/id_rsa # rw------- (private key — required by ssh) chmod 700 ~/.ssh # rwx------ (ssh dir — required by ssh) chmod 664 uploads/avatar.png # rw-rw-r-- (shared writable by group) # --- Recursive --- chmod -R 755 /var/www/html # applies to directory tree
Never use chmod 777 in production. It grants write permission to every user on the system. A common mistake is "fixing" a web app that cannot write to storage/ by setting 777 — this gives any compromised process the ability to overwrite your application files. The correct fix is to set the right owner or group, not to open permissions to the world.

chown — Changing Ownership

Only root (or a process with CAP_CHOWN) can change the owner of a file. The syntax is chown user:group target.

# Change owner only chown deployer app.py # Change owner and group together chown www-data:www-data /var/www/html # Change group only (note the colon prefix) chown :developers /srv/shared/ # Recursive — essential after untarring a release archive chown -R www-data:www-data /var/www/myapp # Inspect current ownership stat -c "%U %G %a" /etc/nginx/nginx.conf # Output: root root 644
Production pattern — least-privilege service accounts: In big-tech environments every long-running service runs as a dedicated non-root system account (e.g. www-data, nginx, postgres). The application files are owned by that account, and no other user can write to them. This limits the blast radius if the service is compromised — the attacker controls only what that account can touch.

umask — The Default Permission Filter

When a process creates a new file, the kernel starts with a base permission of 0666 for files and 0777 for directories, then subtracts the umask bits. The umask is a bitmask of permissions to deny.

The most common default is 0022: deny write for group and others. So a new file gets 0666 & ~0022 = 0644 (rw-r--r--), and a new directory gets 0777 & ~0022 = 0755 (rwxr-xr-x).

umask # show current mask (e.g. 0022) umask 027 # deny all for others, deny write for group # new files: 0640 (rw-r-----) # new dirs: 0750 (rwxr-x---) # Set for a specific session in a startup script: # Add to /etc/profile.d/security.sh or ~/.bashrc umask 027 # Verify: create a file and check umask 027 && touch /tmp/testfile && stat -c "%a" /tmp/testfile # Output: 640
Production umask recommendation: Most hardened Linux baselines (CIS Benchmark Level 1) require 027 or 077 for system accounts. Setting 022 in a daemon's environment is the absolute minimum — it prevents group/world-writable config files from appearing after upgrades. Set umask in the service unit file (UMask=0027 in systemd) so it is not dependent on the shell environment that launched the service.

Special Bits: setuid, setgid, and Sticky

Three extra bits live above the standard rwx triplets. They appear as a fourth octal digit or as modified characters in the ls output.

Special Permission Bits: setuid, setgid, sticky setuid (SUID) chmod 4755 file ls: -rwsr-xr-x Octal prefix: 4 ls shows: s in owner x Runs as file OWNER, not calling user. Example: /usr/bin/passwd (writes /etc/shadow as root) setgid (SGID) chmod 2755 dir/ ls: drwxr-sr-x Octal prefix: 2 ls shows: s in group x On dirs: new files inherit parent group. Example: shared /srv/ team/ so all files belong to team group Sticky Bit chmod 1777 /tmp ls: drwxrwxrwt Octal prefix: 1 ls shows: t in others x On dirs: only owner or root can delete a file, even if dir is world-writable. Example: /tmp
The three special permission bits — setuid, setgid, and sticky — and their primary use cases.

Setting these bits follows the same chmod syntax, with the special bits as the leading octal digit:

  • chmod 4755 /usr/local/bin/myprog — setuid + normal 755
  • chmod 2775 /srv/shared — setgid on a shared directory
  • chmod 1777 /tmp — sticky + world-writable (the /tmp standard)

When s appears in uppercase (S) in the ls output, it means the setuid/setgid bit is set but the execute bit is not — a useless and potentially confusing state. Always verify with ls -l after setting special bits.

SUID binaries are a major attack surface. Any SUID root binary can be used to escalate privileges if it has an exploitable vulnerability. Production hardening: audit all SUID binaries with find / -perm /4000 -ls 2>/dev/null, remove the bit from anything that does not genuinely need it, and use nosuid as a mount option on filesystem partitions that should never contain SUID binaries (e.g. /home, /tmp, /var).

Practical Production Checklist

These are the permission patterns you will set repeatedly across every environment:

  • 600 — private keys (~/.ssh/id_rsa), database password files, .env files
  • 644 — static web assets, config files that daemons read as root
  • 755 — binaries, web directories, scripts in /usr/local/bin
  • 700~/.ssh, scripts with embedded credentials
  • 2775 — shared team directories where group ownership must propagate
  • 1777 — world-writable temporary directories

When a web application cannot write to its upload or cache directory, the answer is almost always a missed chown — not a chmod 777. Identify the process user (ps aux | grep nginx), then chown -R processuser:processuser /var/www/app/storage and set the directory to 755 or 775.

Automate permission enforcement in CI/CD: Add a permissions check step to your deploy pipeline. A one-liner like find /var/www/app -perm /o+w -ls will surface any world-writable files created by a buggy deploy script or a developer testing on the server. Make it a pipeline gate — fail the deploy if any world-writable file appears outside explicitly allowed paths.