Read and Set Permissions
Problem statement
Read a permission string like -rwxr-xr-x, turn it into a number like 755, and set permissions with chmod in both number form and letter form. "Permission denied" is one of the most common errors on any server, and the fix is almost always reading these bits and changing the right one.
The script creates three files in an empty folder:
deploy.sh (a script that should run)
echo deployingapp.conf (a config others may read)
port=8080id_ed25519 (a private key only you may read)
FAKE PRIVATE KEY FOR PRACTICEDo these steps in order:
- Set the script to
755, the config to644and the key to600, then print each mode as a number and as a string. - With letter form: give the group write access to the config, then take all access away from others, then remove execute from group and others on the script. Print the mode after each change.
- Turn the strings
rw-r-----,rwxr-x---andrw-rw-r--into numbers.
Expected output:
== after setting modes ==755 -rwxr-xr-x deploy.sh644 -rw-r--r-- app.conf600 -rw------- id_ed25519== symbolic changes ==664 -rw-rw-r-- app.conf (g+w)660 -rw-rw---- app.conf (o=)744 -rwxr--r-- deploy.sh (go-x)== reading a mode string ==rw-r----- = 640rwxr-x--- = 750rw-rw-r-- = 664Hints
Approach
Optimal: chmod and stat
Covers: ls -l, the rwx string, octal modes, chmod 755, chmod u+x, chmod g-w, chmod o=, stat -c '%a %A %n'.
Every file answers three questions. Who owns it, which group it belongs to, and what each kind of person may do with it. Linux sorts everyone into three groups:
| Who | Letter | Means |
|---|---|---|
| owner | u (user) |
the one user who owns the file |
| group | g |
every member of the file's group |
| others | o |
everyone else on the machine |
For each of them there are three switches: read (r), write (w) and execute (x).
Reading the string from ls -l. ls -l deploy.sh starts with something like -rwxr-xr-x. That is ten characters: one for the type, then three groups of three.
type"]:::gray S --> OW["rwx
owner"]:::blue S --> GR["r-x
group"]:::yellow S --> OT["r-x
others"]:::green OW --> D7(["= 7"]):::blue GR --> D5(["= 5"]):::yellow OT --> D5b(["= 5"]):::green classDef blue fill:#dbeafe,stroke:#2563eb,color:#1e3a8a,stroke-width:2px classDef yellow fill:#fef3c7,stroke:#d97706,color:#78350f,stroke-width:2px classDef green fill:#d1fae5,stroke:#059669,color:#064e3b,stroke-width:2px classDef red fill:#fee2e2,stroke:#dc2626,color:#7f1d1d,stroke-width:2px classDef purple fill:#ede9fe,stroke:#7c3aed,color:#4c1d95,stroke-width:2px classDef gray fill:#f3f4f6,stroke:#6b7280,color:#111827,stroke-width:2px linkStyle default stroke:#94a3b8,stroke-width:2px
The first character is the type: - for a normal file, d for a folder, l for a link. A - in any other place means "this switch is off".
Turning letters into a number. Each group of three becomes one digit. Add up the switches that are on:
| Letter | Worth |
|---|---|
r read |
4 |
w write |
2 |
x execute |
1 |
- off |
0 |
So rwx = 4 + 2 + 1 = 7, r-x = 4 + 1 = 5, rw- = 4 + 2 = 6 and r-- = 4. That makes rwxr-xr-x the number 755. A few numbers come up again and again:
| Number | String | Typical use |
|---|---|---|
755 |
rwxr-xr-x |
scripts and programs, folders |
644 |
rw-r--r-- |
normal files and configs |
600 |
rw------- |
private keys, files with passwords |
700 |
rwx------ |
private folders like ~/.ssh |
Two ways to use chmod. Number form sets all three groups at once: chmod 644 app.conf. Letter form changes one part and leaves the rest alone. It reads as who, then + (add), - (remove) or = (set exactly), then which switches:
u g o a"]:::blue ~~~ OP(["how
+ - ="]):::purple ~~~ WHAT["what
r w x"]:::green end SYN --> EX["chmod g+w app.conf
group: add write"]:::yellow classDef blue fill:#dbeafe,stroke:#2563eb,color:#1e3a8a,stroke-width:2px classDef yellow fill:#fef3c7,stroke:#d97706,color:#78350f,stroke-width:2px classDef green fill:#d1fae5,stroke:#059669,color:#064e3b,stroke-width:2px classDef red fill:#fee2e2,stroke:#dc2626,color:#7f1d1d,stroke-width:2px classDef purple fill:#ede9fe,stroke:#7c3aed,color:#4c1d95,stroke-width:2px classDef gray fill:#f3f4f6,stroke:#6b7280,color:#111827,stroke-width:2px linkStyle default stroke:#94a3b8,stroke-width:2px style SYN fill:transparent,stroke:#7c3aed,stroke-width:2px
| Command | Does |
|---|---|
chmod u+x deploy.sh |
owner: add execute |
chmod g+w app.conf |
group: add write |
chmod o= app.conf |
others: set to nothing |
chmod go-x deploy.sh |
group and others: remove execute |
chmod a+r notes.txt |
all three: add read |
Use number form when you know the full result you want. Use letter form when you only want to change one thing, for example "make this script runnable".
What execute means on a folder. On a file, x means "can run it". On a folder, x means "can go inside it", with cd or by opening a file in it. r on a folder lets you list the names in it. That is why folders are usually 755 or 700, never 644: without x, nobody can use the folder at all.
Walking through the code. The # Setup: lines only create the sample files, so skip past them.
chmod 755,644and600set each file in number form.stat -c '%a %A %n'prints the mode as a number (%a), as a string (%A) and the name (%n). It is easier to read thanls -l, which also prints owner, size and date.g+w, theno=, thengo-xchange one part each time. Watch how each line differs from the one before.- The
awkloop walks the nine letters, adds 4, 2 or 1 for each letter that is on, and writes a digit after every third letter. It is the same sum you do in your head.
Edge cases. stat -c is GNU (Linux). On macOS the same idea is stat -f '%Lp %N'. Root ignores read and write bits, so a test you run as root may "work" even when the bits say no. Always test permissions as the real user.
# Setup: three files in a fresh temporary folder
cd "$(mktemp -d)"
printf '#!/bin/bash\necho deploying\n' > deploy.sh
printf 'port=8080\n' > app.conf
printf 'FAKE PRIVATE KEY FOR PRACTICE\n' > id_ed25519
# 1. Set modes with numbers: 7 = rwx, 6 = rw-, 5 = r-x, 4 = r--, 0 = ---
chmod 755 deploy.sh
chmod 644 app.conf
chmod 600 id_ed25519
echo "== after setting modes =="
stat -c '%a %A %n' deploy.sh app.conf id_ed25519
# 2. Change one part at a time with letters
echo "== symbolic changes =="
chmod g+w app.conf # group: add write
stat -c '%a %A %n (g+w)' app.conf
chmod o= app.conf # others: nothing at all
stat -c '%a %A %n (o=)' app.conf
chmod go-x deploy.sh # group and others: remove execute
stat -c '%a %A %n (go-x)' deploy.sh
# 3. Turn a permission string into its number
echo "== reading a mode string =="
for mode in rw-r----- rwxr-x--- rw-rw-r--; do
echo "$mode" | awk '{
n = 0; out = ""
for (i = 1; i <= 9; i++) {
c = substr($0, i, 1)
if (c == "r") n += 4
if (c == "w") n += 2
if (c == "x") n += 1
if (i % 3 == 0) { out = out n; n = 0 }
}
print $0 " = " out
}'
doneRecapThe whole problem in a few lines, for the night before
- Spot it: "permission denied", "make the script runnable", "lock down this key"
- Idea: three groups (owner, group, others) times three switches; r = 4, w = 2, x = 1, add them per group
- Cost:
chmodandstatare instant;-Rwalks the whole tree - Trap:
chmod 777to make an error go away; give the right user or group access instead
Interview follow-ups
How do you give a whole team write access to a folder tree, but not everyone?
Put the files in a shared group, then give that group write access and leave others alone.
chgrp -R team /srv/appsets the group, andchmod -R g+w /srv/appadds group write without touching the owner or others bits. Folders also needxfor the group, sofind /srv/app -type d -exec chmod g+x {} +makes sure the team can enter them. Setting the setgid bit on folders (chmod g+s) makes new files inherit the team group, which the next page explains.
Frequently asked questions
777 turns every switch on for everyone, so of course the error goes away. But it also means any user or any hacked service on the machine can change or replace that file. For a script, that means someone else can change the code you run. The right fix is to find out who needs access, then give only that: make the app user the owner, or add the user to the file's group, and use 750 or 640. If you see 777 in a deploy script, treat it as a bug.
SSH checks the private key's permissions before using it. If the group or others can read it, for example 644, SSH refuses the key, because someone else on the machine could have copied it. Fix it with chmod 600 ~/.ssh/id_ed25519 (or 400). The ~/.ssh folder itself should be 700. This error is very common right after copying a key from another machine or a download.
Number form is best when you know the exact result: "this key must be 600". It is clear and gives the same result every time, whatever the file had before. Letter form is best for a small change that should not touch anything else, like chmod +x script.sh to make a script runnable. In scripts that set up servers, number form is safer, because the final state does not depend on the starting state.