Ownership: users and groups on files

chown, chgrp and sharing with groups.

Beginner12 min · lesson 12 of 29

Every file records two labels: one user who owns it and one group it belongs to. Together with the permission bits from the previous lesson, those labels decide who may read, change or run the file. This lesson shows how to read the labels, how the kernel uses them to pick which permissions apply to you, how to change them with chgrp and chown, and how to share a directory with a team without opening it to the whole machine. Ownership is also where a security engineer looks first after something goes wrong, so reading it well pays off twice.

Reading the name tags

The kernel does not store your name. It stores numbers: every user has a UID (user ID) and every group a GID (group ID), and the friendly names are looked up from /etc/passwd and /etc/group for your benefit. ls -l prints both labels, and id shows the numbers behind your own name.

deploy@web01 · Ubuntu 26.04 LTS
$ mkdir -p ~/ownership && cd ~/ownership echo "app log line" > app.log ls -l app.log id
-rw-rw-r-- 1 deploy deploy 13 Sep 27 08:33 app.log uid=1001(deploy) gid=1001(deploy) groups=1001(deploy),4(adm),27(sudo)

On the file line, deploy is the user owner and the second deploy is the group owner. id shows this account is UID 1001, with a primary group deploy and the supplementary groups adm and sudo that an administrator account carries on Ubuntu. The permission block is really three blocks, and ownership decides which one the kernel reads for you.

The kernel reads the first class you match

When you touch a file, the kernel checks one class of permissions and stops. Are you the owner? Then the owner bits apply and the check ends, even if the group or others would grant more. Not the owner but in the file's group? The group bits apply. Neither? You get the "others" bits. It stops at the first match, which surprises people. To see it, give your own file a strange mode, 464: the owner (you) gets only read, and the group deploy, which you are also in, gets read and write:

deploy@web01 · Ubuntu 26.04 LTS
$ chmod 464 ~/ownership/app.log ls -l ~/ownership/app.log
-r--rw-r-- 1 deploy deploy 13 Sep 27 08:33 /home/deploy/ownership/app.log
$ echo "another line" >> ~/ownership/app.log
-bash: line 1: /home/deploy/ownership/app.log: Permission denied

The write is refused even though the group bits would allow it, because deploy matches the owner class first and the owner bits are r--. The more generous group rule is never consulted. When you find yourself locked out of your own file, read the owner bits, not the group ones; as the owner you can run chmod to give yourself write back.

Which permission class the kernel reads for you
You touch a file. Which class applies?
You are the user owner
Owner bits apply
Checked first; the check stops here even if group or others grant more.
Not the owner, but in the file's group
Group bits apply
You never fall through to the others bits below.
Neither owner nor in the group
Others bits apply
The catch-all for every other account on the machine.

Changing the group and the owner

chgrp (change group) sets the group owner. A normal user may run it, but only to hand a file to a group they themselves belong to. To have a group you are not in, create one: groupadd makes a new, empty group, and getent group shows its entry (name, an x, the GID, and the members, none yet). The accounts lesson covers groupadd, useradd and usermod properly; here they only set the scene.

deploy@web01 · Ubuntu 26.04 LTS
$ sudo groupadd ess-own-devs getent group ess-own-devs
ess-own-devs:x:1002:

Now hand your file to that group, which you are not a member of, and the kernel refuses:

deploy@web01 · Ubuntu 26.04 LTS
$ cd ~/ownership chgrp ess-own-devs app.log
chgrp: changing group of 'app.log': Operation not permitted (os error 1)

This is the Rust chgrp on Ubuntu 26.04, so the message reads Operation not permitted (os error 1). Point it at a group you belong to, such as adm, and it succeeds silently; in Linux, no output means it worked.

Group membership is read when you log in. If you add someone to a group, their existing sessions keep the old list until they log out and back in, so id in an old shell will not show the new group; this is the usual answer to "I was added to the group but still get Permission denied".

deploy@web01 · Ubuntu 26.04 LTS
$ cd ~/ownership chgrp adm app.log && ls -l app.log
-r--rw-r-- 1 deploy adm 13 Sep 27 08:33 app.log

Giving a file to a different person is guarded more tightly: changing the user owner with chown (change owner) needs root, even for a file you already own. A normal user is refused.

deploy@web01 · Ubuntu 26.04 LTS
$ cd ~/ownership touch handover.txt chown daemon handover.txt
chown: changing ownership of 'handover.txt': Operation not permitted (os error 1)

With sudo it goes through, and a colon sets the user and group at once.

deploy@web01 · Ubuntu 26.04 LTS
$ cd ~/ownership sudo chown daemon:daemon handover.txt && ls -l handover.txt
-rw-rw-r-- 1 daemon daemon 0 Sep 27 08:33 handover.txt

The reason is trust. If anyone could reassign ownership, you could drop an incriminating file into another account and stamp their name on it, or dodge a disk quota by pushing your files onto someone else's total. Because only root can give a file away, the owner field is a record you can mostly rely on: it usually points to whoever created the file.

Sharing a directory with a group, the right way

Say a few engineers need one shared directory and nobody else should get in. Widening the "others" bits would let the whole machine in, which is the opposite of what you want. The clean answer is a shared group: grant access through membership. The group ess-own-devs exists; add a member to it. Here a second account, ess-own-alice, is created and put in the group (usermod -aG appends a group; the accounts lesson explains why the -a matters):

deploy@web01 · Ubuntu 26.04 LTS
$ sudo useradd -m -s /bin/bash ess-own-alice sudo usermod -aG ess-own-devs ess-own-alice id ess-own-alice
uid=1002(ess-own-alice) gid=1003(ess-own-alice) groups=1003(ess-own-alice),1002(ess-own-devs)

Then set the directory's group owner to the team's group with chgrp, give the group read, write and execute with chmod, and add the setgid bit so new files inside inherit the group. Set it on the directory only, never with chmod -R over a tree of files.

deploy@web01 · Ubuntu 26.04 LTS
$ sudo mkdir -p /srv/ess-own-app sudo chgrp ess-own-devs /srv/ess-own-app sudo chmod 2770 /srv/ess-own-app ls -ld /srv/ess-own-app
drwxrws--- 2 root ess-own-devs 4096 Sep 27 08:33 /srv/ess-own-app

The s where the group's execute letter would be is setgid (the leading 2 in 2770), covered fully in the lesson on SUID, SGID and the sticky bit. It makes a file created inside take the directory's group. Watch a member of the group create one. The next terminal runs as alice: to do the same, switch to her account with sudo -iu ess-own-alice (a login shell as that user; the sudo lesson explains it) and type exit to come back. Because it is a fresh login, it already sees her new group.

ess-own-alice@web01 · Ubuntu 26.04 LTS
$ echo "shared work" > /srv/ess-own-app/plan.txt ls -l /srv/ess-own-app/plan.txt
-rw-rw-r-- 1 ess-own-alice ess-own-devs 12 Sep 27 08:33 /srv/ess-own-app/plan.txt

The file's group is ess-own-devs, inherited from the directory, not alice's own primary group, and it is -rw-rw-r-- because an Ubuntu login's umask leaves group write on. The directory's 2770 still keeps everyone outside the group from reaching it.

Fixing an existing tree

Real shares are rarely empty. Suppose an older subdirectory arrived with the wrong group and a private mode, as happens when someone copies files in with sudo cp -r or unpacks an archive. sudo -u NAME runs one command as that account (the sudo lesson covers it), which is a quick way to test what a member of the team can do:

deploy@web01 · Ubuntu 26.04 LTS
$ sudo mkdir /srv/ess-own-app/old sudo chgrp deploy /srv/ess-own-app/old sudo chmod 700 /srv/ess-own-app/old sudo -u ess-own-alice ls /srv/ess-own-app/old
ls: cannot open directory '/srv/ess-own-app/old': Permission denied

Alice is refused: old belongs to the group deploy, so for her it is the "others" class, which gets nothing. A repair therefore has three parts, in this order. chgrp -R puts the whole tree in the team's group. chmod -R g+rwX,o-rwx gives the group read and write and removes everything from others; the capital X grants execute only to directories and to files that are already executable, which avoids the damage chmod -R 2770 would do (execute and setgid on every regular file). Finally find sets setgid on the directories alone:

deploy@web01 · Ubuntu 26.04 LTS
$ sudo chgrp -R ess-own-devs /srv/ess-own-app sudo chmod -R g+rwX,o-rwx /srv/ess-own-app sudo find /srv/ess-own-app -type d -exec chmod g+s {} + sudo ls -ld /srv/ess-own-app /srv/ess-own-app/old /srv/ess-own-app/plan.txt
drwxrws--- 3 root ess-own-devs 4096 Sep 27 08:33 /srv/ess-own-app drwxrws--- 2 root ess-own-devs 4096 Sep 27 08:33 /srv/ess-own-app/old -rw-rw---- 1 ess-own-alice ess-own-devs 12 Sep 27 08:33 /srv/ess-own-app/plan.txt

Read the group column first: every line now says ess-own-devs, including old. The directories came out drwxrws--- and plan.txt -rw-rw----: the group can read and write it, others get nothing, and the file did not gain an execute bit. Then test as a member instead of trusting the listing:

deploy@web01 · Ubuntu 26.04 LTS
$ sudo -u ess-own-alice touch /srv/ess-own-app/old/notes.txt sudo ls -l /srv/ess-own-app/old
total 0 -rw-r--r-- 1 ess-own-alice ess-own-devs 0 Sep 27 08:33 notes.txt

Alice can now create files in old, and the new file takes the group ess-own-devs from the setgid bit. Without the chgrp -R, the modes would look right while old stayed closed to the whole team, and its setgid bit would even stamp new files with the wrong group.

What ownership tells a defender

Files that belong to no account are a red flag. They appear when an account was deleted but its files were left behind, or when an attacker unpacked an archive carrying raw numbers instead of names. To see what one looks like, give a file a UID and GID that no account has, then hunt for such files with find. -nouser and -nogroup match files whose owner or group number has no name; -o means "or", the parentheses group the two tests (the backslashes stop the shell from treating them as its own syntax), and -ls prints a long listing of each match:

deploy@web01 · Ubuntu 26.04 LTS
$ echo x | sudo tee /srv/ess-own-app/orphan.dat > /dev/null sudo chown 1504:1504 /srv/ess-own-app/orphan.dat sudo find /srv/ess-own-app -xdev \( -nouser -o -nogroup \) -ls
575476 4 -rw-r--r-- 1 1504 1504 2 Sep 27 08:33 /srv/ess-own-app/orphan.dat

The bare 1504 shown where a name should be is the tell that no such account exists on this machine. On a live server that is something to explain before you rest. Containers produce the same raw numbers: a file written by UID 1504 inside a container can appear as UID 1504 on the host, which may be a different person or nobody at all.

The most common ownership bug is not an attack: a service that cannot read its own files. To set one up, create a service account (--system gives it a low UID, no home directory and no login shell; the accounts lesson explains these) and a key file that tee writes as root, so it is owned by root:root:

deploy@web01 · Ubuntu 26.04 LTS
$ sudo useradd --system --no-create-home --shell /usr/sbin/nologin ess-own-svc sudo mkdir -p /etc/ess-own echo "api-key=PLACEHOLDER" | sudo tee /etc/ess-own/secret.key > /dev/null sudo chmod 0600 /etc/ess-own/secret.key

At mode 0600 the service account cannot read its key. sudo -u ess-own-svc runs cat as that account, which shows exactly what the service would see:

deploy@web01 · Ubuntu 26.04 LTS
$ ls -l /etc/ess-own/secret.key sudo -u ess-own-svc cat /etc/ess-own/secret.key
-rw------- 1 root root 20 Sep 27 08:33 /etc/ess-own/secret.key cat: /etc/ess-own/secret.key: Permission denied

The service matches the "others" class, which grants nothing, so the read fails. The fix is ownership, not a wider mode. Least privilege here is root:ess-own-svc at 0640: root owns the file and can rewrite it, the service's group may read it, and nothing else can.

deploy@web01 · Ubuntu 26.04 LTS
$ sudo chown root:ess-own-svc /etc/ess-own/secret.key sudo chmod 0640 /etc/ess-own/secret.key ls -l /etc/ess-own/secret.key sudo -u ess-own-svc cat /etc/ess-own/secret.key
-rw-r----- 1 root ess-own-svc 20 Sep 27 08:33 /etc/ess-own/secret.key api-key=PLACEHOLDER
Fix it with ownership, never with chmod 777
It is tempting to make the error vanish with chmod 777. Do not: that hands the key to every account and every process on the machine, including anything an attacker is already running. Do not give the file to the service account outright either (chown svc:svc), because then the service can rewrite its own key or widen its mode. Keep the file root-owned and let the service's group read it at 0640.

Try this

Create a file you own, set its group to a group you belong to (such as adm) with chgrp, and confirm with ls -l. Give the owner bits r-- while the group has rw-, then try to write the file and read the refusal: you own it, yet the owner class is checked first. Next, repeat the shared-directory steps with your own names: sudo groupadd team, sudo useradd -m -s /bin/bash tester, sudo usermod -aG team tester, then sudo mkdir /srv/team, sudo chgrp team /srv/team and sudo chmod 2770 /srv/team. Run sudo -u tester touch /srv/team/a.txt and confirm with sudo ls -l /srv/team that the file's group is team. Clean up with sudo rm -r /srv/team, sudo userdel -r tester and sudo groupdel team (userdel may warn that tester had no mail spool, which is harmless).

Takeaway

The kernel applies the first class you match (owner, then group, then others) and never combines them, so an owner with r-- cannot write even when the group could. Share through a group and setgid on the directory, not by widening "others", and fix a service that cannot read its file by correcting ownership to root:group at 0640, never with chmod 777.

Quick check
01You own report.csv. The owner bits are r--, but its group has rw- and you are a member of that group. What can you do to the file?
Incorrect — The kernel does not add classes together. It applies one class and stops.
Correct — You are the owner, so the owner bits apply and the check ends there; the group's write is never consulted.
Incorrect — Permissions never cancel. Exactly one class applies to you, here the owner class.
Incorrect — The owner gets only the owner bits, which here are read-only; ownership does not grant automatic write.
02A service running as the user webapp fails to start: "could not open /etc/webapp/tls.key: Permission denied". The file is -rw------- root root. What is the least-privilege fix?
Incorrect — 777 exposes the private key to every account and process on the host, the opposite of least privilege.
Incorrect — Then the service can rewrite its own key or widen its mode; the service should read the key, not own it.
Incorrect — The file's group bits are ---, so group membership grants nothing here, and putting a service in root's group is dangerous.
Correct — root keeps ownership and the ability to change the key, the service's group gains read only, and nothing else can see it.
03As a normal, non-root user who owns a file, which change are you allowed to make?
Incorrect — chgrp only lets you hand a file to a group you yourself belong to, not to any group.
Incorrect — Changing the user owner needs root, even for a file you own, so an ordinary user cannot chown it away.
Correct — A normal user may chgrp within their own group memberships; only root may change the user owner.
Incorrect — chgrp works for a normal user within their own groups; it is chown to a different user that requires root.

Related