Every file on a Linux system belongs to exactly one user and one group, and chown is the tool that changes that. I’ve used it constantly in server administration — fixing broken permissions after a deploy runs as the wrong user, handing files over to a service account, or correcting ownership after restoring a backup. It looks like a one-line command, but understanding why ownership matters as much as permission bits, and how the two interact, makes a real difference when you’re troubleshooting “permission denied” errors that turn out to have nothing to do with chmod at all.
What Is the chown Command?
chown stands for “change owner.” It changes the user and/or group ownership of files and directories:
$ chown --version
chown (GNU coreutils) 9.4
Copyright (C) 2023 Free Software Foundation, Inc.
Basic Syntax
chown [OPTION]... [OWNER][:[GROUP]] FILE...
chown [OPTION]... --reference=RFILE FILE...
How Ownership Works Internally
Every inode stores two numeric IDs: a UID (user ID) and a GID (group ID). The names you see in ls -l — like root or appuser — are just a lookup against /etc/passwd and /etc/group mapping those numbers to human-readable names. The kernel itself only ever deals with the numbers; if a UID has no matching entry in /etc/passwd, ls -l just shows the raw number instead of a name.
I confirmed the basic effect directly:
$ chown root:root src.txt
$ ls -l src.txt
-rwxr-xr-x 2 root root 6 Jul 31 01:36 src.txt
The two columns after the permission string are the owner (root) and group (root).
Ownership interacts directly with the permission bits shown by ls -l: the first triad of rwx applies to the owner, the second to the group, and the third to everyone else. Whether a given process is treated as “owner,” “group,” or “other” for a file depends entirely on matching its effective UID/GID against the file’s stored UID/GID — which is exactly why changing ownership can instantly change what a process is or isn’t allowed to do to a file, without touching a single permission bit.
Only the superuser (root) can change a file’s owner to another user, by design — otherwise a user could give away a disk-quota-consuming file to someone else without their consent, or fake ownership to bypass restrictions. Regular users can change a file’s group, but only to a group they themselves belong to.
Full List of Parameters
| Option | Long form | Description |
|---|---|---|
-c | --changes | Like verbose, but report only when a change is actually made |
--dereference | Follow symbolic links (the default) | |
-h | --no-dereference | Affect symbolic links themselves, not their referents |
--from=CURRENT_OWNER:CURRENT_GROUP | Only change if current owner/group matches | |
-f | --silent, --quiet | Suppress most error messages |
--reference=RFILE | Use RFILE’s owner and group instead of specifying OWNER:GROUP | |
-R | --recursive | Operate on files and directories recursively |
--preserve-root | Refuse to operate recursively on / | |
--no-preserve-root | Do not treat / specially (dangerous) | |
-v | --verbose | Output a diagnostic for every file processed |
--help | Display help and exit | |
--version | Output version information and exit |
Recursive traversal control (used with -R):
| Option | Description |
|---|---|
-H | If argument is a symlink to a directory, traverse it |
-L | Traverse every symlink to a directory encountered |
-P | Never traverse symlinks (default) |
Practical Examples with Output
Changing owner only:
$ chown appuser file.txt
Changing owner and group together:
$ chown appuser:appgroup file.txt
$ ls -l file.txt
-rw-r--r-- 1 appuser appgroup 0 Jul 31 01:36 file.txt
Changing only the group, leaving owner untouched (colon with no name after it before the group, or use chgrp):
$ chown :appgroup file.txt
Recursively changing ownership of a directory tree:
$ chown -R appuser:appgroup /srv/app/data
Verbose recursive change, reporting only actual changes:
$ chown -Rc appuser:appgroup /srv/app/data
changed ownership of '/srv/app/data/config.yml' from root:root to appuser:appgroup
Copying ownership from a reference file:
$ chown --reference=/etc/passwd newfile.txt
Conditional change — only apply if current ownership matches, useful for safe automation:
$ chown --from=root:root appuser:appgroup /srv/app/data/file.txt
If the file wasn’t already root:root, nothing happens — a nice safety valve in scripts touching many files where you don’t want to blindly reassign ownership of things that were already correctly set.
Common Use Cases
- Handing files over to a service account after deployment (
chown -R appuser:appgroup /opt/app) - Fixing ownership after restoring files from a backup or tarball, which often preserves the original UID/GID that no longer matches on the new system
- Correcting ownership after a build process run as
rootleaves root-owned artifacts a non-root service can’t read - Setting up shared group directories where multiple users need collaborative write access
- Preparing web server document roots so the web server user can read (and sometimes write) content
Shell Scripting and Automation
A standard post-deploy ownership fix:
#!/usr/bin/env bash
set -euo pipefail
APP_USER="appuser"
APP_GROUP="appgroup"
APP_DIR="/opt/myapp"
chown -R "${APP_USER}:${APP_GROUP}" -- "$APP_DIR"
Restoring correct ownership after extracting a tarball that was created on a different system with different UIDs:
tar xzf backup.tar.gz -C /restore/target
chown -R www-data:www-data /restore/target
Using --from to safely re-home only files that still belong to a decommissioned account, during account cleanup:
find / -xdev -user olduser 2>/dev/null | xargs -r chown --from=olduser newuser
Real-World System Administration Workflows
- Web server deployments: document roots typically need to be owned by (or at least group-writable by) the web server’s user (
www-data,nginx,apache) so the server process can read static assets and, for some frameworks, write to cache/upload directories. - Database data directories: PostgreSQL and MySQL both refuse to start if their data directory isn’t owned by the correct service user —
chown -R postgres:postgres /var/lib/postgresql/datais a routine fix after a manual restore. - Docker volumes: files created inside a container by a process running as UID 0 (root) or an arbitrary UID often need
chownon the host afterward to match the UID a host-side process expects to read them with. - Multi-user shared directories: setting group ownership plus the setgid bit (
chmod g+s) on a shared directory so new files automatically inherit the right group for collaborative teams.
Comparing chown to Related Commands
chownvschgrp:chgrponly changes group ownership and is essentially a narrower, dedicated version of whatchown user:group(orchown :group) can already do — most administrators usechownfor both since it coverschgrp‘s entire job.chownvschmod: they solve different problems —chowndecides who the owner/group is,chmoddecides what each category (owner/group/other) is allowed to do. Both frequently need adjusting together after a deploy or restore.chownvs ACLs (setfacl): standardchown/chmodonly support one owner and one group per file; when you need multiple distinct users or groups to have different access levels on the same file, POSIX ACLs viasetfaclare the tool, layered on top of the base ownershipchownestablishes.
Troubleshooting Common chown Issues
“chown: changing ownership of ‘file’: Operation not permitted”: only root can change a file’s owner to a different user; as a regular user you can only change group ownership, and only to a group you belong to. Use sudo chown if you have the privilege.
Ownership looks wrong after copying files from another machine or archive: UIDs/GIDs aren’t portable between systems unless /etc/passwd//etc/group match exactly — a UID that was appuser (1001) on the source system might map to a completely different (or nonexistent) user on the destination, showing as a raw number in ls -l. Run chown -R explicitly after any cross-system restore.
A service refuses to start citing permission errors even though chmod looks correct: check ownership, not just mode — many daemons (databases especially) explicitly verify the owning user of their data/config directories and refuse to run if it doesn’t match, regardless of how permissive the mode bits are.
Recursive chown seems to skip symlinked subdirectories: that’s the default -P behavior (never traverse symlinks) working as intended, to avoid accidentally changing ownership of files outside the intended tree; use -H or -L deliberately if you do want symlinks followed, and understand the security implications first.
Performance Considerations
chown -R on a directory with millions of files can take a meaningful amount of time since it’s one chown() syscall per inode. For very large trees, find /path -exec chown user:group {} + batches arguments similarly to chown -R internally and isn’t meaningfully faster, but running it in parallel with tools like xargs -P can help on multi-core systems with fast storage, if the ownership change is on the critical path of a deploy.
Security Implications
Because ownership determines which permission triad applies, incorrect chown recursion is a common way to accidentally over-expose files — for instance, recursively chown-ing a directory to a less-trusted service account that then inherits access to files that account shouldn’t be able to touch. Always scope -R operations precisely, avoid running it against top-level system directories, and never disable --preserve-root protection. Also be cautious with setuid/setgid binaries: changing ownership of a setuid root binary to a non-root user silently strips the setuid bit on most systems as a security measure — verify with ls -l afterward if that binary’s setuid behavior matters.
Best Practices
- Always pair a
chownfix with achmodreview — ownership and permission bits both need to be correct together. - Use
--fromfor safer, conditional bulk reassignment in scripts. - Scope
-Rnarrowly; never run it against/or other broad system paths. - After any cross-system restore or tarball extraction, explicitly re-run
chown -Rrather than trusting preserved UIDs to still mean the same thing. - Use
chown --reference=fileto copy known-good ownership rather than retyping user:group by hand.
Compatibility Across Distributions
GNU chown behaves identically across Debian/Ubuntu, RHEL/Fedora, Arch, and openSUSE, since they all use the same UID/GID model and GNU coreutils implementation. BusyBox chown (Alpine, minimal containers) supports the core user:group syntax and -R but has a reduced option set — verify with chown --help before relying on --from or --reference in minimal images.
Advanced Scenarios I’ve Run Into
Ownership inside containers vs the host: a UID that maps cleanly to appuser inside a Docker container might correspond to a completely different (or no) user on the host filesystem when a volume is bind-mounted, because the kernel only ever tracks numeric UID/GID — the container’s /etc/passwd mapping to names is a per-namespace convenience layer, not something the kernel enforces consistently across the host/container boundary. This is exactly why files created by a containerized process often show up owned by an unfamiliar numeric UID when inspected from the host with ls -l, and why chown on the host side is frequently needed after container-based builds write output into a bind-mounted directory.
User namespaces and UID remapping: with rootless containers or user namespace remapping enabled, a process that believes it’s running as UID 0 (root) inside its namespace might actually map to an unprivileged UID like 100000 on the host — chown run from inside such a namespace is constrained to the UID range the namespace has been granted, and attempts to chown a file to a UID outside that range will fail even for the “root” user inside the container.
Recursively fixing ownership while excluding specific subdirectories, combining find with -prune since chown -R has no exclusion mechanism of its own:
$ find /srv/app -path /srv/app/vendor -prune -o -exec chown appuser:appgroup {} +
Checking who owns what across a filesystem quickly, useful during a security audit or before decommissioning an account:
$ find / -xdev -user olduser -o -group oldgroup 2>/dev/null
Numeric UIDs vs Names in Practice
It’s worth internalizing that chown appuser:appgroup file is really just a convenience — bash and chown itself resolve appuser to a UID and appgroup to a GID by consulting /etc/passwd and /etc/group (or NSS-configured backends like LDAP in enterprise environments) before the actual chown() syscall ever runs, which only ever receives the two raw numbers. You can bypass name resolution entirely and specify UID/GID numerically, which is occasionally the only reliable option when a name doesn’t exist locally (for instance, restoring files from a system where the account was defined in a directory service you’re not currently connected to):
$ chown 1001:1001 orphaned_file.txt
Summary
chown looks like a small administrative detail, but ownership is one of the two pillars (alongside permission mode) that the entire Unix access-control model rests on. Every inode carries a UID and GID, and the kernel’s access decisions boil down to matching a process’s UID/GID against those numbers — names in ls -l are just a convenience layer on top. Getting ownership right after deployments, restores, and account changes is often the actual fix behind permission errors that look at first glance like a chmod problem.
References
- GNU Coreutils Manual —
chowninvocation: https://www.gnu.org/software/coreutils/manual/html_node/chown-invocation.html - Linux man-pages project —
chown(1): https://man7.org/linux/man-pages/man1/chown.1.html chown(2)system call documentation: https://man7.org/linux/man-pages/man2/chown.2.html- Ubuntu Manpage Repository: https://manpages.ubuntu.com/manpages/noble/en/man1/chown.1.html