Linux User Management — adduser, Groups, sudo and SSH Keys

Admin and service users on Ubuntu/Debian: adduser vs useradd, groups, sudoers drop-ins, SSH keys with correct permissions, and locking users properly.

Share
Linux User Management — adduser, Groups, sudo and SSH Keys

Every fresh server needs the same few things on the user side: one admin account that isn't root, SSH keys instead of passwords, maybe a deploy user with limited sudo, and system users for the services. It's simple, but there are a handful of details that either lock you out or leave a door open — usually permissions on .ssh and the -a in usermod.

TL;DR: adduser (not useradd) for humans, usermod -aG sudo for admin rights, ssh-copy-id for keys, sudoers changes only via visudo -f /etc/sudoers.d/<name>, and to lock someone out use usermod -L -e 1, because a locked password alone doesn't stop SSH keys.

# admin user
sudo adduser alice
sudo usermod -aG sudo alice

# from your workstation
ssh-keygen -t ed25519 -C "alice@laptop"
ssh-copy-id alice@server

# test in a NEW terminal before touching anything else
ssh alice@server 'sudo -v && echo ok'

adduser vs useradd

On Debian and Ubuntu there are two tools, and they do different things:

  • adduser — the friendly Debian wrapper. Creates the user, a matching group, the home directory (copied from /etc/skel), sets the shell to bash, asks for a password. This is what you want for humans.
  • useradd — the low-level tool. Without flags it may create a user with no home directory and /bin/sh as shell, depending on /etc/default/useradd. Fine in scripts where you pass everything explicitly, annoying interactively.
# the useradd equivalent of a sane adduser
sudo useradd -m -s /bin/bash -G sudo alice
sudo passwd alice

For a user who only ever logs in with a key, skip the password:

sudo adduser --disabled-password alice

They can't sudo with a password then, of course — either set one later or give them specific NOPASSWD rules (below).

Groups

id alice                    # uid, gid, all groups
groups alice
sudo usermod -aG docker alice
sudo gpasswd -d alice docker   # remove from a group

Two things to know:

  • Group changes apply at the next login. An existing SSH session keeps the old group list. Log out and back in (or newgrp docker for one shell).
  • Some groups are root in disguise. sudo obviously, but also docker (can mount / into a container), lxd, and disk (raw block device access). Treat membership in those like handing out the root password. More on the Docker side in installing Docker.

Gotcha: usermod -G without -a

sudo usermod -G docker alice      # WRONG

Without -a (append), -G replaces all supplementary groups with the list you give. alice is now in docker and nothing else — no more sudo. If alice is your only admin and root login is disabled, you just locked yourself out of root on that box. Always -aG. If it already happened: rescue console, or any other sudo-capable account, and add sudo back.

sudo

On Ubuntu and Debian, members of the sudo group get full sudo via this line in /etc/sudoers:

%sudo   ALL=(ALL:ALL) ALL

Debian detail: if you set a root password during installation, the installer doesn't install sudo at all and doesn't create an admin user in the sudo group. As root:

apt install sudo
usermod -aG sudo alice

Restricted sudo with drop-ins

Never edit /etc/sudoers directly with a normal editor. A syntax error there means sudo refuses to run at all — for everyone. Use visudo, which checks syntax before saving, and put your rules in a drop-in:

sudo visudo -f /etc/sudoers.d/deploy
# deploy can restart the app, nothing else, no password
deploy ALL=(root) NOPASSWD: /usr/bin/systemctl restart myapp.service, /usr/bin/systemctl status myapp.service

Rules that list arguments match those arguments exactly, so deploy can run precisely these two commands. Use full paths. Avoid giving out commands that can spawn a shell or edit files (vim, less, find, tar, systemctl edit) — any of those is effectively full root.

Check what a user may do:

sudo -l -U deploy
sudo visudo -c        # syntax-check all sudoers files

Gotcha: sudoers.d file names

Files in /etc/sudoers.d/ whose name contains a dot or ends with ~ are silently ignored. deploy.conf does nothing. deploy works. This exists so editor backups and package leftovers don't get loaded — but it means your carefully written rule just doesn't apply, and nothing tells you. visudo -f doesn't warn about it either.

SSH keys

On your machine:

ssh-keygen -t ed25519 -C "alice@laptop"
ssh-copy-id -i ~/.ssh/id_ed25519.pub alice@server

Ed25519 is short, fast and supported everywhere that matters. Use a passphrase on the key, and an agent so you don't type it every time.

If ssh-copy-id isn't an option (password auth already off, or you're setting up a user from root), do it by hand on the server:

sudo install -d -m 700 -o alice -g alice /home/alice/.ssh
echo 'ssh-ed25519 AAAA... alice@laptop' | sudo tee -a /home/alice/.ssh/authorized_keys
sudo chown alice:alice /home/alice/.ssh/authorized_keys
sudo chmod 600 /home/alice/.ssh/authorized_keys

One key per line in authorized_keys. Comment at the end of each line so you know later whose key it is — removing access for a person is then one grep.

Gotcha: wrong permissions = key silently ignored

sshd's StrictModes (on by default) refuses keys if the home directory, .ssh or authorized_keys is writable by anyone except the owner, or owned by the wrong user. The client just falls back to password and you wonder why your key "doesn't work". Typical causes: authorized_keys created as root and never chowned, or a home directory someone made group-writable.

The server log tells you exactly:

sudo journalctl -u ssh -n 50 | grep -i -E 'authentication refused|bad ownership'

And from the client side:

ssh -v alice@server

Look for which keys are offered and whether the server accepted one. Correct state:

ls -ld /home/alice /home/alice/.ssh /home/alice/.ssh/authorized_keys
# drwxr-x---  alice alice  /home/alice           (755 or 750 is fine, not group/world-writable)
# drwx------  alice alice  /home/alice/.ssh
# -rw-------  alice alice  /home/alice/.ssh/authorized_keys

Once keys work for every admin, turn off password authentication and root login — that's in the SSH hardening checklist.

Service users

Services should run as their own user without a login shell or password:

sudo adduser --system --group --home /opt/myapp myapp
# useradd equivalent:
sudo useradd --system --user-group --home-dir /opt/myapp --shell /usr/sbin/nologin myapp

--system gives a UID below 1000 and, with adduser, nologin as shell. The app's files belong to myapp, the systemd unit has User=myapp, and if the app is compromised, the attacker is myapp, not root. How the unit side looks is in systemd service unit files.

Locking and removing users

Someone leaves, a key leaks, a contractor's job is done.

# lock: password locked AND account expired
sudo usermod -L -e 1 alice

# kick existing sessions
sudo loginctl terminate-user alice

# unlock later
sudo usermod -U -e '' alice

Gotcha: usermod -L doesn't stop SSH keys

usermod -L (or passwd -l) only puts a ! in front of the password hash. Password login is dead, but on Ubuntu/Debian with UsePAM yes, SSH key login still works. The usermod man page even says it: to lock the account and not just the password, also set the expiry date to 1. An expired account fails PAM's account check, which applies to key logins too. Also remove their keys from authorized_keys — on every account they had keys on, including shared ones like deploy.

To delete for good:

sudo deluser --remove-home alice
# check for leftovers they owned elsewhere
sudo find / -xdev -user alice 2>/dev/null

Also grep /etc/sudoers.d/ and crontabs for the name. If you build servers from templates, bake the admin user and keys into cloud-init instead of repeating this per VM — see Proxmox cloud-init templates and the initial setup checklist.


Related posts