The two notations, side by side
Octal packs three bits into one digit, so nine bits become three digits and each digit
describes one class completely. Symbolic names the classes and permissions in letters:
u for the owner, g for the group, o for others,
a for all three, then an operator, then the permission letters.
The operator is the part that matters. = sets a class to exactly what follows,
+ adds, - removes. Only = has an octal equivalent,
because octal has no way to say "leave the other bits alone". Take a file sitting at
644 that you now need to run: getting it to 755 is chmod 755 in octal, or chmod a+x relatively, or
chmod u=rwx,g=rx,o=rx absolutely in symbolic form. All three
land on the same mode; only the middle one is safe on a file whose current bits you have
not read.
Where the fourth digit goes in an ls listing
ls -l prints nine characters, but a mode has twelve bits. The three special
bits do not get columns of their own; they overload the three execute positions. This is
the complete rule:
| Position | Special bit | Execute bit | Mode | Renders as | Character printed |
|---|---|---|---|---|---|
| owner execute | clear | clear | 0000 | --------- | - |
| owner execute | clear | set | 0100 | --x------ | x |
| owner execute | setuid set | clear | 4000 | --S------ | S |
| owner execute | setuid set | set | 4100 | --s------ | s |
| group execute | clear | clear | 0000 | --------- | - |
| group execute | clear | set | 0010 | -----x--- | x |
| group execute | setgid set | clear | 2000 | -----S--- | S |
| group execute | setgid set | set | 2010 | -----s--- | s |
| other execute | clear | clear | 0000 | --------- | - |
| other execute | clear | set | 0001 | --------x | x |
| other execute | sticky set | clear | 1000 | --------T | T |
| other execute | sticky set | set | 1001 | --------t | t |
The pattern is uniform: lowercase means both bits are set, uppercase means the special bit is set and the execute bit beneath it is not. 2368 of the 4096 modes render with at least one uppercase letter.
What an uppercase letter does not tell you is that the bit is inert, and this is the part that is usually stated too strongly. It is exactly one class's execute bit that is missing, and only one of the three special bits is disabled by that:
- Setgid on a regular file really is inert. The kernel tests both bits
together —
(mode & (S_ISGID | S_IXGRP)) == (S_ISGID | S_IXGRP)— sorw-r-Sr--changes nothing about how the file runs. - Setuid on a file is not. The same function applies the owner's uid on
mode & S_ISUIDalone, without looking atS_IXUSR. Mode4055renders--Sr-xr-xand is a live setuid binary for anyone in the group, because some class can still execute it. - Setgid and sticky on a directory are not either. Group inheritance is
decided by
dir->i_mode & S_ISGIDand deletion protection byS_ISVTXplus the directory's write bits; neither consults an execute bit.2760rendersrwxrwS---and still forces the group, and1770rendersrwxrwx--Tand is exactly the safe shared-group drop-box.
So an uppercase letter is worth a second look, not an automatic correction. On a regular file it usually is a mistake. On a directory it is often the mode you meant.
Sources: Linux kernel, fs/exec.c — bprm_fill_uid(); fs/inode.c — inode_init_owner(). C source read directly from the kernel’s master branch.
What each notation can and cannot say
| Intent | Symbolic | Octal | Note |
|---|---|---|---|
| make it executable by everyone | chmod a+x file | depends on the current mode | Octal cannot express "add" — you must know all nine current bits to write the equivalent. |
| take write away from group and others | chmod go-w file | depends on the current mode | This is the operation OpenSSH’s 022 mask is testing for. |
| set exactly rwxr-xr-x | chmod u=rwx,g=rx,o=rx file | chmod 755 file | Absolute in both notations; identical result. |
| lock a class out entirely | chmod go= file | depends on the owner bits you want to keep | An empty permission list after = clears the class. This is the shortest fix for a private key. |
| add the sticky bit and change nothing else | chmod +t dir | chmod 1xxx dir | The octal form forces you to restate all nine lower bits, so it can silently undo something. |
| set the execute bit only where one already exists | chmod -R a+X tree | no octal equivalent | Capital X is defined by POSIX and has no numeric spelling at all. |
Memorise the capital-X row. POSIX defines it as "the
execute/search portion of the file mode bits if the file is a directory or if the current
(unmodified) file mode bits have at least one of the execute bits set". That conditional
is what makes chmod -R a+X tree safe where chmod -R 755 tree is
not: directories get their search bit, already-executable scripts keep theirs, and plain
files are left alone. There is no way to write that in octal, because octal has no
conditionals.
Source: POSIX.1-2024 (IEEE Std 1003.1-2024), XCU chmod — EXTENDED DESCRIPTION. The Open Group’s own free HTML edition, read directly.
Reading the leading character
An ls -l line starts with ten characters, not nine. The first is the file
type and is not part of the mode at all: - for a regular file, d
for a directory, l for a symbolic link, c and b for
character and block devices, s for a socket, p for a FIFO. It
cannot be changed with chmod, and the calculator above shows it as a separate toggle for
exactly that reason.
A trailing character after the nine is not part of the mode either. A . or a
+ at the end of the field means an SELinux context or an access control list
is attached — and the + in particular is a warning that the nine bits are no
longer the whole story, because an ACL can grant access the mode does not show. If you are
debugging a permission problem on a file whose listing ends in +, read
getfacl before you read the mode.
Which to use
Use octal when you are stating a final result — provisioning a machine, writing a Dockerfile line, documenting an expected mode. It is idempotent and unambiguous, and a reviewer can check it against the intended value without knowing the previous state.
Use symbolic when you are making a change — fixing one bit on files you did not create,
walking a tree that contains a mixture of executables and data, or touching a special bit
without restating the nine below it. chmod +t /var/shared adds the sticky bit
and changes nothing else; chmod 1777 /var/shared adds the sticky bit and also
silently sets nine other bits to values you may not have intended.