On this page
Linux decides what every process may touch from the account it runs as. Getting accounts and permissions right is therefore not housekeeping, it is the difference between a bug in your game server costing you one directory and costing you the whole machine.
This page covers creating and removing users, group membership, what sudo
actually does, the permission bits behind "permission denied", and the reasoning
for running services under their own account. Commands are shown for Debian and
Ubuntu and for RHEL, AlmaLinux and Rocky Linux where the two differ.
Creating a user
On Debian and Ubuntu, adduser is an interactive script that creates the
account, its home directory, its group, and prompts for a password:
adduser deployOn RHEL, AlmaLinux and Rocky Linux, adduser is the low-level useradd under
another name and does none of the prompting, so it takes two commands:
useradd -m -s /bin/bash deploy
passwd deploy-m creates the home directory and -s sets the login shell. Confirm what you
got:
id deployThat prints the numeric user ID, the primary group, and every supplementary group. The numeric ID is what the filesystem actually stores, which matters later when you delete an account.
Accounts for services, not for people
A user that exists to run a program does not need a password, a shell, or a home directory full of dotfiles. Create it so that nobody can sign in as it:
useradd --system --shell /usr/sbin/nologin --home-dir /opt/fivem fivemOn RHEL, AlmaLinux and Rocky the equivalent shell path is /sbin/nologin.
--system allocates an ID from the range reserved for services, which keeps it
out of the way of real people. The account still owns files and still runs
processes, but it cannot be logged into, so a stolen password is not a way in
because there is no password.
Removing a user
Deleting an account is not one action, it is a decision about the account and a separate decision about its files.
First make sure nothing is still running as that user, because processes survive the account they belong to and are then owned by a number with no name:
pgrep -u deploy -lThen remove the account and its home directory:
userdel -r deployuserdel -r works on both families. Debian and Ubuntu also ship
deluser --remove-home deploy, which does the same thing.
To keep the person out without destroying anything, lock the account instead of deleting it. This is usually the right move when the files belong to work still in progress:
usermod -L deploy
usermod -s /usr/sbin/nologin deployLocking the password does not revoke an SSH key. If the account has a key in
/home/deploy/.ssh/authorized_keys, that key still works after a lock, so
remove the file as well.
After deleting a user, files they owned elsewhere on the disk are left behind with a bare numeric owner. Find them before that number gets reused by the next account you create:
find / -xdev -nouser -o -nogroupGroups
A group is a named set of users, and it is how permission is granted to more than one account without editing every file. Membership comes in two kinds: one primary group, recorded against the account, and any number of supplementary groups.
groups deploy
getent group sudoAdding someone to a group:
usermod -aG docker deployRemoving someone from a group:
gpasswd -d deploy dockerGroup membership is read when a session starts. Someone already signed in keeps the groups they had until they log out and back in, which is why "I added them to the group and it still says permission denied" is almost always a stale session rather than a broken group.
What sudo actually does
sudo runs a single command as another user, root unless you say otherwise,
after checking the rules in /etc/sudoers and the files in /etc/sudoers.d/.
It authenticates you with your own password, not root's, it records what was
run, and it applies per-command rules. That is the whole difference from knowing
the root password: sudo is auditable and can be narrowed, a shared root password
is neither.
See what you are allowed to run:
sudo -lGranting it
Both families ship a rule that grants full sudo to a group, so adding someone to
that group is the normal way to grant administrative access. On Debian and
Ubuntu the group is sudo, on RHEL, AlmaLinux and Rocky it is wheel:
usermod -aG sudo deployusermod -aG wheel deployGranting less than everything
Full sudo means the account can become root, so it is worth avoiding for
automation that only needs one command. Write a narrow rule in its own file
under /etc/sudoers.d/, and always use visudo to edit it:
visudo -f /etc/sudoers.d/deploy-restartA rule that lets one account restart one service and nothing else:
deploy ALL=(ALL) NOPASSWD: /usr/bin/systemctl restart fivem.serviceWhere sudo use is recorded
Every invocation is logged. On Debian and Ubuntu that lands in
/var/log/auth.log, on RHEL, AlmaLinux and Rocky in /var/log/secure, and on
both in the journal:
journalctl -t sudo --since "1 hour ago"Reading permissions
Every file has an owner, a group, and three sets of permission bits. ls -l
shows them:
ls -l /opt/fivemThe first column is ten characters. The first is the type, - for a regular
file and d for a directory. The remaining nine are three groups of three:
permissions for the owner, then for the group, then for everyone else. In each
group, r is read, w is write, and x is execute. A dash means that
permission is absent. The two names after the link count are the owning user and
the owning group.
Only one set applies to you. If you are the owner, the owner bits are used and the group bits are never consulted, even if they are more permissive.
Numerically, r is 4, w is 2, and x is 1, added together per set. That is
where the familiar three-digit numbers come from:
| Mode | Owner | Group | Others | Typical use |
|---|---|---|---|---|
600 | read, write | none | none | Private keys, config files with credentials |
644 | read, write | read | read | Ordinary files, static web content |
700 | read, write, execute | none | none | The .ssh directory, a private script |
750 | read, write, execute | read, execute | none | A directory shared with a service group |
755 | read, write, execute | read, execute | read, execute | Directories, executables |
Directories work differently
On a directory the same three bits mean something else, and this is where most confusion starts:
rlets you list the names inside it.wlets you create and delete entries inside it.xlets you traverse into it and reach things by name.
The consequence people trip over: whether you can delete a file is decided by
the permissions on its directory, not on the file. A file you cannot write can
still be deleted by anyone who can write to the directory holding it. That is
also why /tmp carries the sticky bit, shown as t in the last position of
ls -ld /tmp, which restricts deletion inside a world-writable directory to
each file's own owner.
chmod
chmod changes the bits. It takes either a number, which sets all three sets at
once, or a symbolic form, which adjusts one part and leaves the rest alone:
chmod 600 /home/deploy/.ssh/id_ed25519
chmod +x /opt/scripts/backup.sh
chmod g+w /opt/fivem/logs
chmod o-rwx /opt/fivem/server.cfgThe cases that come up in practice:
- A config file with a password or an API token in it.
chmod 600, owned by the account that reads it. Anything world-readable is readable by every process on the machine. - A script that will not run. If it says "permission denied" when you run it
directly, it is missing
x.chmod +xfixes it. - A service that cannot write to its own directory. The directory needs
wandxfor the account the service runs as, which usually means fixing ownership rather than loosening the bits. Seechownbelow. - SSH refusing a key. SSH will not use a private key that others can read
and prints a loud warning about an unprotected private key file. The server
side is quieter:
sshdignoresauthorized_keysif the home directory or.sshis writable by group or others, and gives you a plainPermission deniedwith no explanation.700on.sshand600onauthorized_keysis the fix.
Recursive changes
chmod -R 755 on a tree marks every ordinary file executable, which is untidy
and defeats the point of the bit. Set directories and files separately:
find /opt/fivem -type d -exec chmod 755 {} +
find /opt/fivem -type f -exec chmod 644 {} +chown
chown changes who owns a file, and it is the tool for the single most common
permission problem on a new server: you unpacked something as root, so every
file is owned by root, and the service that runs as its own account cannot write
to any of it.
chown -R fivem:fivem /opt/fivemThe form is user, colon, group. Either half can be omitted: chown fivem file
changes only the user, and chown :fivem file changes only the group, which
chgrp fivem file also does.
Only root can give a file away to another user, so these commands run under
sudo.
A useful pattern for a directory that a service writes to and a person needs to
read: set the owner to the service account, the group to a group you are in, and
the directory to 750. You get to read the logs without the service files being
readable by every account on the machine.
Why running services as root is the mistake
If a program has a bug that lets an attacker run commands, those commands run as whatever the program runs as. That single sentence is the whole argument.
As root, an attacker who found a flaw in your game server, in a resource or plugin you installed, or in a web panel you exposed, can install a persistent backdoor, read every file including your keys and database credentials, modify the logs that would have shown you what happened, and use the machine to attack others. As an unprivileged service account, the same flaw gets them the files that account can touch, and no ability to change the system underneath it. It is the difference between an incident and a rebuild.
The practical version:
- Give each service its own account, as shown above, rather than sharing one. Two services under one account can reach each other's data.
- Run it under that account with systemd.
User=andGroup=in a unit file do this properly, and drop privileges before the program starts. See Managing services with systemd. - Own the data with the same account.
chown -Rthe service's directory to it, and give it write access only where it genuinely writes. - Do not use root to work around a port. Ports below 1024 need privilege to
bind, which is the usual excuse for running as root. systemd grants exactly
that one capability with
AmbientCapabilities=CAP_NET_BIND_SERVICE, and a reverse proxy in front is the other answer.
If the worst has already happened and you think a server has been compromised, changing passwords is not enough, because you cannot trust anything on the disk after the fact. Rebuild it from a known-good state, see Reinstalling your server, and open a ticket at Opening a ticket if you need help working out what happened.