By the end you'll know
- Who you are on a Linux machine, and where users and groups are kept
- What every letter of
rwxr-x---means, for files and for folders - How to change permissions with
chmod, by letters and by numbers - How to change who owns a file with
chown - How to run a command as another user, and why servers don't run apps as root
The problem: everyone is root
So far you've read what the bookshop's server left behind. From this lesson on, you'll build it, one piece at a time, and the practice files are what it will produce.
Start with a question you haven't had to ask yet: who is running everything? So far, the answer has been root, the administrator, who can read, change and delete anything on the machine. That's convenient for learning and dangerous for a server. If an app running as root has a bug, or someone breaks into it, they can do anything root can: read every password, change every setting, delete everything.
The fix is old and simple. Every program runs as a user with only the access it needs, and permissions on every file and folder say who can do what. This lesson sets that up for the bookshop.
On your own computer, start in your practice folder as usual:
cd linux-practice && docker run -it --rm --name lesson5 -v "$PWD":/practice -w /practice ubuntu:24.04 bash
&& runs the second command only if the first one worked, so if you're not in the folder that holds linux-practice, it stops at no such file or directory instead of starting a container with the wrong files. (Lesson 2 cloned it into the folder you were in at the time, such as ~/Apps.)
Everything else runs inside the container. Nothing in this lesson changes the practice files: you'll copy what you need into the container's own folders, and it all disappears when you exit.
Who am I?
whoami
id
root
uid=0(root) gid=0(root) groups=0(root)
Linux knows users by number. uid is your user ID: root is always 0. gid is your main group, and groups lists every group you're in. A group is a named set of users, so you can give several people the same access at once.
Users are listed in /etc/passwd, one per line, with fields separated by :. Lesson 4's awk -F reads it:
grep -E '^(root|nobody):' /etc/passwd
awk -F: '{print $1, $3, $7}' /etc/passwd | tail -3
root:x:0:0:root:/root:/bin/bash
nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin
_apt 42 /usr/sbin/nologin
nobody 65534 /usr/sbin/nologin
ubuntu 1000 /bin/bash
The seven fields are the name, x (the password lives elsewhere, in /etc/shadow), the user ID, the group ID, a description, the home folder, and the shell. Most of these users aren't people: they're system users, made for one program each, with the shell /usr/sbin/nologin so nobody can log in as them. nobody is the classic one, a user with no access to anything, which you'll use later as a stranger. The last line is the one person: ubuntu, user 1000, with a real shell. The Ubuntu image includes it, as Ubuntu cloud servers do. Groups are listed the same way, in /etc/group.
Reading permissions
Every ls -l line starts with ten characters. You've seen them since Lesson 2:
ls -l notes/todo.txt
ls -ld logs
-rw------- 1 root root 127 Oct 29 12:30 notes/todo.txt
drwx------ 6 root root 192 Oct 29 12:39 logs
(Your dates will differ.) The first character is the type: - a file, d a folder. The other nine are three sets of three: what the owner can do, what the group can do, and what everyone else can do. After the number, root root names the owner and the group.
| Letter | Number | On a file | On a folder |
|---|---|---|---|
r read | 4 | Read it | List the names inside |
w write | 2 | Change it | Add, rename and delete files inside |
x execute | 1 | Run it as a program | Go into it, and open the files inside |
- | 0 | Not allowed | Not allowed |
So -rw------- means the owner can read and write, and nobody else can do anything. These files came from your computer through the -v link, which is why they look so locked down.
The numbers. Add up each set of three: rw- is 4 + 2 = 6, r-- is 4, --- is 0. So rw-r----- is 640, and rwxr-xr-x is 755. Three digits, one each for owner, group and everyone else. That's the form you'll see most in instructions and scripts. Try it:
Two surprises are worth knowing now. Deleting a file depends on the folder's w, not the file's: a file you can't change, in a folder you can write to, can still be deleted. And a folder needs x to be any use at all, which "Break it on purpose" shows.
Set up the bookshop
A Linux server keeps things in standard places (Lesson 2): settings in /etc, logs in /var/log. Make a folder in each for the app, and copy in its settings and its log:
mkdir -p /etc/books-api /var/log/books-api
cp config/app/app.conf config/app/override.conf /etc/books-api/
cp logs/app.log /var/log/books-api/
ls -l /etc/books-api /var/log/books-api
/etc/books-api:
total 8
-rw------- 1 root root 151 Oct 29 19:12 app.conf
-rw------- 1 root root 75 Oct 29 19:12 override.conf
/var/log/books-api:
total 36
-rw------- 1 root root 33063 Oct 29 19:12 app.log
The copies kept the originals' permissions, -rw-------, and belong to root, because root made them.
Now two users. The app gets a system user that can't log in, and Sam, who the notes say is on call this week, gets a normal account with a home folder:
useradd --system --shell /usr/sbin/nologin bookshop
useradd --create-home --shell /bin/bash sam
id bookshop
id sam
ls -ld /home/sam
uid=999(bookshop) gid=999(bookshop) groups=999(bookshop)
uid=1001(sam) gid=1001(sam) groups=1001(sam)
drwxr-x--- 2 sam sam 4096 Oct 29 19:12 /home/sam
useradd made a group with the same name for each, and gave them the next free numbers: system users get numbers below 1000, people get 1000 and up. ubuntu already has 1000, so Sam got 1001. Sam's home folder is drwxr-x---, so other users can't look inside it.
Act as another user: su
su (switch user) runs a command as someone else. As root, you don't need their password. bookshop has no real shell, so name one with -s for the test:
su -s /bin/bash bookshop -c 'cat /etc/books-api/app.conf'
su -s /bin/bash bookshop -c 'echo "2026-10-29 09:00:00 INFO started" >> /var/log/books-api/app.log'
cat: /etc/books-api/app.conf: Permission denied
bash: line 1: /var/log/books-api/app.log: Permission denied
Exactly what you'd expect from -rw------- root root: the app can't read its own settings or write its own log. This is the most common reason a freshly installed service won't start.
Change the owner: chown
chown (change owner) takes user:group. The app should own its logs, so it can write them. Its settings should stay owned by root, so the app can't change them, but belong to the bookshop group, so it can read them:
chown -R bookshop:bookshop /var/log/books-api
chown root:bookshop /etc/books-api/app.conf /etc/books-api/override.conf
chmod 640 /etc/books-api/app.conf /etc/books-api/override.conf /var/log/books-api/app.log
ls -l /etc/books-api /var/log/books-api
/etc/books-api:
total 8
-rw-r----- 1 root bookshop 151 Oct 29 19:12 app.conf
-rw-r----- 1 root bookshop 75 Oct 29 19:12 override.conf
/var/log/books-api:
total 36
-rw-r----- 1 bookshop bookshop 33063 Oct 29 19:12 app.log
-R (recursive) changes the folder and everything in it. chmod 640 gives the group read access: changing the owner alone isn't enough, because the copies' group permissions were ---. Try again:
su -s /bin/bash bookshop -c 'head -2 /etc/books-api/app.conf'
su -s /bin/bash bookshop -c 'echo "2026-10-29 09:00:00 INFO started" >> /var/log/books-api/app.log'
tail -1 /var/log/books-api/app.log
su -s /bin/bash bookshop -c 'echo "port = 1" >> /etc/books-api/app.conf'
# Settings for the practice app
name = books-api
2026-10-29 09:00:00 INFO started
bash: line 1: /etc/books-api/app.conf: Permission denied
The app can read its settings and write its log, and can't change its settings. If someone breaks into the app, they can't rewrite its configuration either. That's the goal: the access it needs, and nothing more. It's called least privilege.
Groups: giving a person access
Sam needs to read the app's log while on call:
su - sam -c 'tail -1 /var/log/books-api/app.log'
usermod -aG bookshop sam
id sam
su - sam -c 'tail -1 /var/log/books-api/app.log'
su - sam -c 'echo "a note from sam" >> /var/log/books-api/app.log'
tail: cannot open '/var/log/books-api/app.log' for reading: Permission denied
uid=1001(sam) gid=1001(sam) groups=1001(sam),999(bookshop)
2026-10-29 09:00:00 INFO started
-bash: line 1: /var/log/books-api/app.log: Permission denied
usermod -aG bookshop sam adds Sam to the Group. Leave out -a and -G replaces all of Sam's groups instead, a classic mistake. Now Sam can read the log, through the group's r, but not change it, because the group has no w. The - in su - sam starts a full login as Sam, in Sam's home folder, the way Sam would really log in.
On a real server, a user added to a group has to log out and back in before it takes effect. su starts a fresh login each time, so here it's immediate.
Folders: who can get in
Lock the log folder so only the owner and the group can enter it, and test with nobody, a user in neither:
chmod 750 /var/log/books-api
ls -ld /var/log/books-api
su - sam -c 'ls /var/log/books-api'
su -s /bin/bash nobody -c 'ls /var/log/books-api'
drwxr-x--- 2 bookshop bookshop 4096 Oct 29 19:12 /var/log/books-api
app.log
ls: cannot open directory '/var/log/books-api': Permission denied
750 is rwxr-x---: the owner can do everything, the group can look and read, and everyone else can't get in. Even a file that everyone could read is safe inside a folder they can't enter.
Making a file runnable: chmod +x
A program is just a file with x set. Make a one-line report with Lesson 4's pipeline. The first line, #!/bin/bash, says which program runs the file; Lesson 12 covers scripts properly:
echo '#!/bin/bash' > /usr/local/bin/books-report
echo "awk '{print \$3}' /var/log/books-api/app.log | sort | uniq -c | sort -rn" >> /usr/local/bin/books-report
ls -l /usr/local/bin/books-report
books-report
-rw-r--r-- 1 root root 84 Oct 29 19:12 /usr/local/bin/books-report
bash: /usr/local/bin/books-report: Permission denied
New files get rw-r--r-- (644): anyone can read them, nobody can run them. Add x:
chmod +x /usr/local/bin/books-report
ls -l /usr/local/bin/books-report
books-report
-rwxr-xr-x 1 root root 84 Oct 29 19:12 /usr/local/bin/books-report
449 INFO
153 WARN
37 ERROR
The \$3 in the echo stops the shell from swapping $3 for something else before writing the file. And /usr/local/bin is where programs you add yourself go. It's on the list of folders the shell searches (PATH, from Lesson 1), which is why books-report works without its full path.
chmod by letters
Numbers set all nine at once. Letters change just what you name: u the owner (user), g the group, o others, a all three, then + to add, - to remove, or = to set exactly:
touch /tmp/demo.txt
ls -l /tmp/demo.txt
chmod u+x /tmp/demo.txt
ls -l /tmp/demo.txt
chmod g+w,o-r /tmp/demo.txt
ls -l /tmp/demo.txt
chmod 600 /tmp/demo.txt
ls -l /tmp/demo.txt
-rw-r--r-- 1 root root 0 Oct 29 19:12 /tmp/demo.txt
-rwxr--r-- 1 root root 0 Oct 29 19:12 /tmp/demo.txt
-rwxrw---- 1 root root 0 Oct 29 19:12 /tmp/demo.txt
-rw------- 1 root root 0 Oct 29 19:12 /tmp/demo.txt
Use letters to change one thing, like chmod +x script or chmod o-r secrets.conf. Use numbers when you know exactly what the result should be, like chmod 640 app.conf.
The special cases from Lesson 2
Lesson 2 pointed at three odd-looking permissions. Here they are, explained:
ls -ld /root /tmp
ls -l /etc/shadow /usr/bin/passwd
umask
drwx------ 2 root root 4096 Oct 29 19:12 /root
drwxrwxrwt 2 root root 4096 Oct 29 19:12 /tmp
-rw-r----- 1 root shadow 522 Oct 29 19:12 /etc/shadow
-rwsr-xr-x 1 root root 64152 Apr 9 2024 /usr/bin/passwd
0022
/rootis700. Only root can get into root's home folder./tmpends int, the sticky bit. Everyone can write in/tmp, but thetmeans you can only delete your own files there. Without it, anyone could delete anyone else's./etc/shadowholds the password hashes, readable only by root and theshadowgroup. That's why/etc/passwd, which everyone can read, just saysx.passwdhas answhere the owner'sxwould be: setuid. It runs as its owner, root, whoever starts it. That's how an ordinary user can change their own password, which means writing to/etc/shadow. A setuid program has to be very careful, which is why there are only a few of them.umaskis0022: the permissions taken away from every new file. Files start from666and folders from777, minus022, which is why new files come out644and new folders755.
See the sticky bit work. Sam makes a file in /tmp, and the app tries to delete it:
su - sam -c 'echo "hello" > /tmp/sams-file.txt'
ls -l /tmp/sams-file.txt
su -s /bin/bash bookshop -c 'rm /tmp/sams-file.txt'
-rw-rw-r-- 1 sam sam 6 Oct 29 19:12 /tmp/sams-file.txt
rm: cannot remove '/tmp/sams-file.txt': Operation not permitted
Break it on purpose
chmod 777, the fix that isn't. When something says Permission denied, searches often suggest chmod 777. It does make the error go away:
chmod 777 /etc/books-api/app.conf
ls -l /etc/books-api/app.conf
su -s /bin/bash nobody -c 'echo "port = 1" >> /etc/books-api/app.conf'
tail -1 /etc/books-api/app.conf
-rwxrwxrwx 1 root bookshop 151 Oct 29 19:12 /etc/books-api/app.conf
port = 1
nobody, a user who should have no access to anything, just changed the app's port. 777 means every user on the machine can read, change and run the file. The real fix is always the narrowest one: work out which user needs which access, and give that, with chown or a group. Put it back:
sed -i '$d' /etc/books-api/app.conf
chmod 640 /etc/books-api/app.conf
ls -l /etc/books-api/app.conf
-rw-r----- 1 root bookshop 151 Oct 29 19:12 /etc/books-api/app.conf
sed -i '$d' deletes the last line (Lesson 4). $ means the last line.
Run it once as root. Someone tests the app by hand, as root, and it writes a new log file. Then the real app takes over:
echo "2026-10-29 10:00:00 DEBUG run by hand" >> /var/log/books-api/debug.log
ls -l /var/log/books-api
su -s /bin/bash bookshop -c 'echo "2026-10-29 10:05:00 DEBUG from the app" >> /var/log/books-api/debug.log'
chown bookshop:bookshop /var/log/books-api/debug.log
total 40
-rw-r----- 1 bookshop bookshop 33097 Oct 29 19:12 app.log
-rw-r--r-- 1 root root 38 Oct 29 19:12 debug.log
bash: line 1: /var/log/books-api/debug.log: Permission denied
A new file belongs to whoever made it. Root made debug.log, so the app can't write to it, and on a real server the app would fail to start. When a service suddenly can't write its own files, ls -l and look for root. The last line, chown, fixes it.
A folder without x. Take x off the log folder, leaving r:
chmod 640 /var/log/books-api
ls -ld /var/log/books-api
su - sam -c 'ls /var/log/books-api'
su - sam -c 'ls -l /var/log/books-api'
su - sam -c 'cd /var/log/books-api'
chmod 750 /var/log/books-api
drw-r----- 2 bookshop bookshop 4096 Oct 29 19:12 /var/log/books-api
app.log
debug.log
ls: cannot access '/var/log/books-api/debug.log': Permission denied
ls: cannot access '/var/log/books-api/app.log': Permission denied
total 0
-????????? ? ? ? ? ? app.log
-????????? ? ? ? ? ? debug.log
-bash: line 1: cd: /var/log/books-api: Permission denied
With r alone, Sam can list the names, but not open the files, see their details (the ?s), or cd in. x on a folder is what lets you use what's inside. Folders almost always need x wherever they have r, so it's 755 or 750, never 644 or 640.
Log in as the app. Try a full login as bookshop:
su - bookshop
su: warning: cannot change directory to /home/bookshop: No such file or directory
This account is currently not available.
System users have no home folder, and their shell, /usr/sbin/nologin, prints that message and exits. That's on purpose: nobody should ever log in as the app. You can still run a single command as it, with -s /bin/bash, as you've been doing.
On a real server
- You'll log in as an ordinary user and use
sudo. On an Ubuntu cloud server, that's usuallyubuntu, andsudo commandruns one command as root, after checking that you're allowed. It logs who did what, unlike logging in as root. The container doesn't havesudoinstalled; Lesson 8 installs software. - Services get their own users, exactly like
bookshop. In Lesson 7, systemd starts the app withUser=bookshop, and nginx (Lesson 8) runs as a user calledwww-data. - SSH keys must be private. SSH refuses a private key that others can read, with an error that says
UNPROTECTED PRIVATE KEY FILE.chmod 600on the key fixes it. Lesson 11 covers this. - Containers do this too. The Docker track's images run as user 1000, and Kubernetes'
runAsNonRootrefuses to start a container as root. Same idea, applied to containers. - When you see
Permission denied, ask three questions: who is the process running as (ps, in Lesson 6), who owns the file (ls -l), and does every folder on the way havexfor them?
Spot the problem
Work out what's wrong before you read the explanation.
The app that stopped logging. After a late-night fix, the app keeps running but its log stops growing. ls -l /var/log/books-api shows a new app.log owned by root, and the old one renamed to app.log.1. What happened? Whoever did the fix rotated the log by hand, as root, and the fresh app.log was created by root. The app, running as bookshop, can't write to it. chown bookshop:bookshop app.log fixes it, and so does making the new file as the right user in the first place.
Keep this
| To | Run |
|---|---|
| Who am I, and my groups | whoami, id |
| Someone else's ID and groups | id sam |
| List users or groups | cat /etc/passwd, cat /etc/group |
| A folder's own permissions | ls -ld folder |
| Permissions as a number | stat -c '%a %A %U %G %n' file |
| Add a user who can log in | useradd --create-home --shell /bin/bash sam |
| Add a user for an app | useradd --system --shell /usr/sbin/nologin bookshop |
Add someone to a group (keep -a) | usermod -aG bookshop sam |
| Run one command as another user | su -s /bin/bash bookshop -c 'command' |
| Log in as another user | su - sam |
| Change owner and group | chown bookshop:bookshop file |
| The same, for a folder and everything in it | chown -R bookshop:bookshop folder |
| Set permissions exactly | chmod 640 file, chmod 750 folder |
| Change just one thing | chmod +x script, chmod o-r file, chmod g+w file |
| Settings an app may read but not change | root:bookshop and 640 |
| Private: only the owner | chmod 600 file |
| Never | chmod 777 |
Where you are now
The bookshop now has a proper home: settings in /etc/books-api that the app can read but not change, logs in /var/log/books-api that it owns, a bookshop user that can't log in, and Sam in the group with read access. Lesson 6 starts things running: processes, what they're running as, and how to stop one that won't stop.
Check yourself
What does -rw-r----- mean?
Which number is rwxr-xr-x?
The app can't write its log, and gets Permission denied. What's the best fix?
A folder is drw-r-----. Its owner can list the names inside, but not open the files. Why?
What does usermod -aG bookshop sam do?
Why does the bookshop user have the shell /usr/sbin/nologin?
Key terms
- User and group: who a process or person is, by name and number. A group gives several users the same access.
- Root: user 0, the administrator, who can do anything.
- System user: a user made for one program, with no login, like
bookshopornobody. - Permissions: read, write and execute, for the owner, the group and everyone else.
- Least privilege: giving each user or program only the access it needs.
- Sticky bit: the
ton/tmp: you can only delete your own files there. - Setuid: the
son a program likepasswd: it runs as its owner, whoever starts it. - umask: the permissions taken away from every new file and folder.
Go deeper
- Make a
books-adminsgroup, add Sam to it, and give it write access to/etc/books-apiwithout changing the owner. Which commands does it take? - Run
ls -l /usr/bin | grep rwsto list every setuid program in the container. There are only a few. man 5 passwdexplains every field of/etc/passwd, on a machine that has the manuals.- Lesson 2, The shell, for reading
ls -l.