Why you need to read these nine letters
A secrets file readable by every user, a script anyone can edit, a service that cannot read its own config - all three are the same nine letters in ls -l, set wrong. Reading them at a glance, and setting them exactly, is daily work.
What you need to know already: 1.3 (ls -la), 4.3 (users, groups, sudo -u).
Three triples
Every file has an owner (one user), a group (one group), and a mode: nine permission bits saying what each kind of visitor may do.
-rwxr-x--- 1 root ops 87 Sep 22 20:00 deploy.sh
│└┬┘└┬┘└┬┘
│ │ │ └── other: everyone else - no access at all
│ │ └───── group (ops): read + execute
│ └──────── user (the owner, root): read + write + execute
└────────── type: - file, d directory, l symlink, c/b device
r read the contents, w change them, x execute (run it as a program). A - in a slot means that bit is off. Each group of three is called a triple.
Octal: the same bits as three digits
Writing modes as digits is called octal (base 8, digits 0-7). Each bit has a value - r=4, w=2, x=1 - and you add them up per triple:
750 = rwx r-x --- a script the owner runs and a group may run
640 = rw- r-- --- a config file the group may read
600 = rw- --- --- a secret
644 = rw- r-- r-- a normal world-readable file
755 = rwx r-x r-x a normal program or directory
700 = rwx --- --- a private directory (~/.ssh)
To convert by hand, do one triple at a time: r-x = 4+0+1 = 5, rw- = 4+2+0 = 6. Going the other way, 6 can only be 4+2 (rw-), 5 only 4+1 (r-x), 3 only 2+1 (-wx). There is exactly one way to write each digit, which is why octal works.
Two habits: 600 for anything with a credential in it, and never 777 for anything. 777 on a script does not fix a problem, it advertises one - and it is the first thing an auditor looks for. ("World-readable" / "world-writable" means the other triple has r / w.)
Which triple applies - exactly one
The kernel picks one triple and ignores the other two:
- You are the file's owner -> the user triple. Full stop.
- Else, the file's group is one of your groups -> the group triple.
- Else -> the other triple.
It does not "add up" your best option. An owner with fewer rights than the group gets the owner's rights:
$ cd /tmp; echo 'Q3 numbers' > report.txt; chgrp ops report.txt; chmod 060 report.txt
$ ls -l report.txt
----rw---- 1 learner ops 11 Sep 22 20:00 report.txt
$ cat report.txt
cat: report.txt: Permission denied
$ sudo -u appuser cat report.txt # appuser is in ops
Q3 numbers
(chgrp ops FILE sets the file's group; chmod MODE FILE sets its mode - both below.) You own it, so your triple (---) is the only one checked, even though you are also in ops. Root skips all of this for reading and writing - but not for executing: root can run a file only if at least one x bit is set anywhere.
chmod both ways
chmod ("change mode") takes either octal or a symbolic change - who (u owner, g group, o other, a all), then + add, - remove or = set exactly, then the bits:
chmod 750 deploy.sh absolute - set exactly these bits
chmod u+x script.sh symbolic - add execute for the owner
chmod go-w file remove write from group and other
chmod a=r file everyone gets exactly read
chmod u=rwx,g=rx,o= file a whole mode, spelled out (= 750)
chmod -R g+rX dir -R recursive (the whole tree). Capital X means
"x only on directories and on files that
already have some x" - what makes -R safe
Watch each form change the same file:
$ echo hi > f; ls -l f
-rw-r--r-- 1 learner learner 3 Sep 22 20:00 f
$ chmod 640 f; ls -l f
-rw-r----- 1 learner learner 3 Sep 22 20:00 f
$ chmod u+x,g-r f; ls -l f
-rwx------ 1 learner learner 3 Sep 22 20:00 f
$ chmod a= f; cat f
cat: f: Permission denied
$ chmod o+w f; stat -c '%a %A %U:%G %n' f
002 --------w- learner:learner f
Symbolic changes are relative - they add or remove bits and leave the rest. Octal is absolute - it replaces everything. Use symbolic when you mean "also let the group read", octal when you mean "this file must be exactly 640".
chmod -R 755 dir makes every text file executable, which is noise at best and a security finding at worst. chmod -R g+rX is what you meant.
chown
chown ("change owner") sets the owner, the group, or both:
sudo chown appuser file owner
sudo chown appuser:appgroup file owner and group
sudo chown :ops file group only (same as chgrp)
sudo chown -R root:ops /srv/ops recursive
sudo chgrp ops file group only
Only root can give a file away. The usual pattern for a shared team directory is root:ops ownership with the group bits doing the work, so no human owns it and membership of ops is the access control.
The errors, and what each one means
$ chmod 644 /etc/passwd
chmod: changing permissions of '/etc/passwd': Operation not permitted
$ chown root f
chown: changing ownership of 'f': Operation not permitted
$ chmod 999 f
chmod: invalid mode: '999'
Try 'chmod --help' for more information.
- Only the owner (or root) may chmod a file - write permission on it is not enough. The kernel's error code for this is EPERM, printed as "Operation not permitted". (A plain missing bit gives a different code, EACCES, printed as "Permission denied". The two texts tell you which kind of check refused.)
- Only root may chown a file to another user; an owner may chgrp it only to a group they are in.
- 9 is not an octal digit.
chmod 999is a typo, and so ischmod 0x755.
Reading it back
stat prints everything the filesystem stores about a file:
$ stat -c '%a %U:%G %n' /srv/ops/*
750 root:ops /srv/ops/deploy.sh
640 root:ops /srv/ops/notes.txt
600 root:ops /srv/ops/secrets.env
$ stat /etc/passwd
File: /etc/passwd
Size: 969 Blocks: 8 IO Block: 4096 regular file
Device: 252:0 Inode: 1066 Links: 1
Access: (0644/-rw-r--r--) Uid: ( 0/ root) Gid: ( 0/ root)
stat -c FORMAT prints only what you ask for: %a octal mode, %A the rwx string, %U/%G owner and group names, %u/%g their numbers, %n the name. Scripts and checks compare these numbers, not ls output. (The Inode and Links fields are 4.13.)
What you can now do
- Translate between
-rw-r-----and640in both directions. - Say which triple applies to a given user, and why the owner can be refused.
- Set owner, group and mode with
chown/chmodand verify withstat -c.