What the chown command actually does
chown sets which user and group own a file on Linux. Create the account first, use chown user:group and chown -R, and fix root owned files after sudo.
What the chown command changes
The chown command sets which user and which group own a file. Every file on a Linux filesystem carries exactly one owner and one group, held on the inode as two numbers. The permission bits decide nothing on their own: the kernel first works out whether the process is the owner, or a member of the file's group, or neither, and only then reads the matching set of bits.
That order is why ownership is the first thing to check when a program cannot write. If a file belongs to another account, changing the bits will not help. For the bits themselves, read what drwxr-xr-x means in an ls -l listing.
Create the account before you set the ownership
The names in a listing are not stored on disk. The kernel stores a UID (user identifier) and a GID (group identifier), and ls looks those numbers up in /etc/passwd and /etc/group to print a name. So chown rejects a name that does not resolve on this machine. Make the account first.
sudo adduser --disabled-password --gecos "" appuser
id appuseruid=1001(appuser) gid=1001(appuser) groups=1001(appuser)Your numbers will differ, because adduser takes the next free UID and creates a group to match. --disabled-password leaves the account with no usable password, which suits an account that owns files and runs a service but never logs in. --gecos "" skips the prompts for full name and phone number, so the command finishes without asking anything.
For an account that should have no home directory at all, sudo adduser --system --group --no-create-home appsvc is the service form. Ownership behaves the same either way. Keeping each account narrow is the point of running services under a least privilege user.
Ubuntu also ships accounts you can use without creating anything. www-data is the one nginx and Apache drop their worker processes to.
id www-datauid=33(www-data) gid=33(www-data) groups=33(www-data)Reading the current owner with ls -ld and stat
ls -ld on a directory reports the directory itself instead of listing what is inside it.
ls -ld /srv/inventory /srv/inventory/config.ymldrwxr-xr-x 3 root root 4096 Aug 22 15:42 /srv/inventory
-rw-r--r-- 1 root root 318 Aug 22 15:42 /srv/inventory/config.ymlstat puts the numbers beside the names.
stat /srv/inventory/config.yml File: /srv/inventory/config.yml
Size: 318 Blocks: 8 IO Block: 4096 regular file
Device: 254,1 Inode: 1443329 Links: 1
Access: (0644/-rw-r--r--) Uid: ( 0/ root) Gid: ( 0/ root)
Access: 2026-08-22 15:42:10.104728311 +0000
Modify: 2026-08-22 15:42:10.104728311 +0000
Change: 2026-08-22 15:42:10.104728311 +0000
Birth: 2026-08-22 15:42:10.100728290 +0000stat -c prints only the fields you name, which is the form to use inside a script.
stat -c '%U %G %u %g %n' /srv/inventory/config.ymlroot root 0 0 /srv/inventory/config.ymlThe forms of the chown command
Four spellings, each doing a different job.
sudo chown appuser filesets the owner and leaves the group untouched.sudo chown appuser:appuser filesets owner and group in one call.sudo chown :www-data filesets the group only, leaving the owner untouched.sudo chown appuser: filesets the owner, and sets the group to that user's login group.
chgrp is the group half on its own, so sudo chgrp www-data file and sudo chown :www-data file do the same work. Use whichever reads more clearly in your notes.
sudo chown appuser:www-data /srv/inventory/config.yml
ls -ld /srv/inventory/config.yml-rw-r--r-- 1 appuser www-data 318 Aug 22 15:44 /srv/inventory/config.ymlWhy the chown command needs sudo
Changing an owner needs the CAP_CHOWN capability, which on a normal system means root. You cannot give away a file even when you already own it.
chown appuser notes.txtchown: changing ownership of 'notes.txt': Operation not permittedThe restriction exists because disk quotas are counted against the owner. A user who could hand files to somebody else could park unlimited data under a colleague's quota, and could plant a file that looks like that colleague wrote it.
Groups are looser. You may chgrp a file you own to any group you belong to, with no sudo. Naming a group you are not a member of gives chgrp: changing group of 'notes.txt': Operation not permitted.
chown -R, and the two ways it goes wrong
-R walks a directory and applies the same ownership to everything inside it.
sudo chown -R appuser:appuser /srv/inventoryThe first hazard is a stray space. sudo chown -R appuser / srv/inventory is a valid command with two targets, and the first target is the root of the filesystem. It rewrites the owner of every file on the disk, including /usr/bin/sudo, so the next sudo you run answers:
sudo: /usr/bin/sudo must be owned by uid 0 and have the setuid bit setYou can no longer become root over SSH, and recovery means your provider's rescue console. Read the path twice before you press enter on any recursive chown.
The second hazard is quieter. -R flattens a tree that held mixed ownership on purpose. When only part of the tree is wrong, restrict the change with --from, which touches a file only if its current ownership matches.
sudo chown -R --from=root:root appuser:appuser /srv/inventory--reference copies ownership from a file that is already correct, which saves looking up any names.
sudo chown --reference=/srv/inventory/config.yml /srv/inventory/secrets.ymlSymbolic links have their own rule. A plain chown follows a link and changes the file at the other end, while -h changes the link itself. chown -R does not walk through a symbolic link into the directory it points at, and -L is the flag that turns that on.
Case 1: everything is owned by root after a sudo copy
A process running as root creates files owned by root. So sudo cp, sudo tar -x, sudo unzip and sudo git clone all leave a tree you cannot write, even inside your own home directory.
sudo cp -r /srv/backup/inventory ~/inventory
ls -ld ~/inventory ~/inventory/config.ymldrwxr-xr-x 3 root root 4096 Aug 22 15:47 /home/deploy/inventory
-rw-r--r-- 1 root root 318 Aug 22 15:47 /home/deploy/inventory/config.ymlThe first symptom is usually an editor that refuses to write, which is the same cause behind nano refusing to save with permission denied. Git has its own version of the complaint, because it checks who owns the repository directory before it will run:
fatal: detected dubious ownership in repository at '/home/deploy/inventory'Adding a safe.directory exception silences that message and leaves you still unable to write. Fix the ownership instead.
sudo chown -R "$USER:$(id -gn)" ~/inventory
ls -ld ~/inventorydrwxr-xr-x 3 deploy deploy 4096 Aug 22 15:47 /home/deploy/inventory$USER and $(id -gn) are expanded by your own shell before sudo starts, so they hold your name and your login group rather than root's. On Ubuntu every account gets a group of the same name, so "$USER:$USER" usually works as well. $(id -gn) is correct on any box.
Better still, avoid creating the problem. Clone and extract without sudo when the destination is a directory you already own. sudo is needed to write into /srv or /opt, not into your home directory.
Case 2: a service or container cannot write its data directory
Nginx and Apache serve as www-data, so that is the account which has to write an upload or cache directory. Ask the account directly instead of guessing.
sudo -u www-data touch /srv/inventory/uploads/.probetouch: cannot touch '/srv/inventory/uploads/.probe': Permission deniedCreating a file is a write to the directory, so the ownership being checked is the directory's, not that of the files already inside it. Hand the data directory over and probe again.
sudo chown -R www-data:www-data /srv/inventory/uploads
sudo -u www-data touch /srv/inventory/uploads/.probe
ls -ld /srv/inventory/uploadsdrwxr-xr-x 2 www-data www-data 4096 Aug 22 15:50 /srv/inventory/uploadsA touch that prints nothing is a touch that worked. Give the service account the data directory only. Application code and config can stay owned by your deploy account, so a compromised worker cannot rewrite the code it runs.
Containers add one twist. A container carries its own /etc/passwd, so a user called node or abc inside the image means nothing on the host. What crosses a bind mount is the number.
docker run --rm --user 1000:1000 -v /srv/inventory/data:/data alpine:3.21 touch /data/probetouch: /data/probe: Permission deniedThe host directory belongs to root, and UID 1000 inside the container is UID 1000 on the host too. Set the host directory to the number the container runs as.
sudo chown -R 1000:1000 /srv/inventory/data
stat -c '%u:%g %U:%G %n' /srv/inventory/data1000:1000 deploy:deploy /srv/inventory/datachown accepts a bare number even when no account holds it. That is how a listing like this one happens.
sudo chown -R 5000:5000 /srv/inventory/data
ls -ld /srv/inventory/datadrwxr-xr-x 2 5000 5000 4096 Aug 22 15:52 /srv/inventory/dataNothing is broken here. /etc/passwd holds no entry mapping 5000 to a name, so there is no name to print. Many container images do this ownership work at startup from two environment variables, which is what the PUID and PGID variables in container images are for.
Errors and what each one means
chown: invalid user: 'appuser'means the name does not resolve on this host. Check it withid appuser. An account created inside a container does not exist on the host that runs the container.chgrp: invalid group: 'appgroup'is the same failure on the group side.getent group appgroupprints nothing when the group is missing.chown: cannot access '/srv/inventry': No such file or directoryis a path typo.chownnever creates anything.chown: changing ownership of 'x': Operation not permittedwhile running as root points at the filesystem rather than at you. vfat, exfat and most SMB or CIFS mounts store no Unix ownership on disk. They take a fixeduid=andgid=at mount time, so no chown can succeed on them. Change the mount options in/etc/fstabinstead.chown: changing ownership of 'x': Read-only file systemmeans the mount is read only. Remount it read write first.
Ownership answers who a file belongs to. The permission bits answer what may be done with it, and they are set with a different command: see chmod numeric and symbolic modes.
FAQ
Why does chown say "Operation not permitted" when I already own the file?
Owning a file does not let you give it away. Changing an owner needs the CAP_CHOWN capability, which on a normal Linux system means running as root, so sudo chown ... is the answer. If the command already ran under sudo and still failed, look at the filesystem. vfat, exfat and most SMB or CIFS mounts store no Unix ownership and take a fixed uid= and gid= from their mount options, so chown cannot change anything there.
What is the difference between chown and chgrp?
chgrp appgroup file changes the group and nothing else. chown does that same job with chown :appgroup file, and can also change the owner, which chgrp cannot. One practical difference: you may run chgrp without sudo on a file you own, as long as you are a member of the target group.
Why does ls -l show a number instead of a user name?
Because no entry in /etc/passwd maps that UID to a name on this machine. The file is fine and its ownership is exactly what is printed. This is normal after copying files from another host, restoring a backup, or setting ownership for a container user with chown 1000:1000. Run getent passwd 1000 to check whether an account exists, and empty output means there is none.
Does chown -R follow symbolic links?
Not by default. chown -R does not walk through a symbolic link into the directory it points at, and -L is the flag that turns that on. Without -R, a plain chown on a symbolic link changes the file at the other end, while -h makes it change the link itself.
How do I undo a chown -R that hit the wrong directory?
There is no undo, because chown keeps no record of the previous ownership. Take that record yourself before any bulk change: find /srv/inventory -printf '%U:%G %p\n' > ~/owners.txt writes one line per file, and a short script can replay it. For files that came from a package, sudo apt install --reinstall <package> restores the ownership that package shipped. For anything else, a backup is the only route.