User and sudo management on a VPS — adding users, scoping privileges, auditing access
Logging into a fresh VPS as root via password is fine for the first 10 minutes. After that you want named user accounts with SSH keys and sudo scoped to what each person actually needs to do. This article covers the patterns for a single-operator VPS (you're the only admin), a small team VPS (you + 1-3 others), and a setup where some users have restricted access.
Add a user
useradd -m -s /bin/bash alice # create user with home dir, bash shell
passwd alice # set a password (or skip for key-only)
usermod -aG sudo alice # Debian/Ubuntu: add to sudo group
# OR
usermod -aG wheel alice # AlmaLinux: add to wheel group
Set up their SSH key:
mkdir -p /home/alice/.ssh
echo "ssh-ed25519 AAAA... alice@laptop" > /home/alice/.ssh/authorized_keys
chmod 700 /home/alice/.ssh
chmod 600 /home/alice/.ssh/authorized_keys
chown -R alice:alice /home/alice/.ssh
Test from outside before disabling password login: ssh alice@your-vps. If key login works, lock down passwords:
# /etc/ssh/sshd_config.d/00-no-password.conf
PasswordAuthentication no
PermitRootLogin no
systemctl reload sshd
Now SSH-as-root is gone, password auth is gone, named-user-with-key is the only way in.
Sudo: passwordless vs password-required
Default behavior on most distros: members of sudo (Debian/Ubuntu) or wheel (Alma) can run any command via sudo, prompted for their own password.
To skip the password prompt (convenient, slightly less safe):
# /etc/sudoers.d/alice
alice ALL=(ALL) NOPASSWD: ALL
Always edit sudoers files via visudo (or visudo -f /etc/sudoers.d/alice) — it syntax-checks before saving. A bad sudoers file can lock you out of admin access until you boot rescue mode.
Scoping sudo to specific commands
For users who should be able to do specific operational tasks but not be full root, scope by command:
# /etc/sudoers.d/deploy-user
# Can restart the web app and tail its logs, nothing else
deploy ALL=(root) NOPASSWD: /usr/bin/systemctl restart myapp.service
deploy ALL=(root) NOPASSWD: /usr/bin/journalctl -u myapp.service *
Tighten with absolute paths (always — relative paths can be subverted by PATH manipulation), and use the wildcard sparingly (the journalctl line above lets the user run journalctl with any args after -u myapp.service, but they can't run it against other services).
Audit who has access
# Who's in the sudo/wheel group
getent group sudo wheel
# Who has authorized_keys for SSH
for u in $(awk -F: '$3 >= 1000 && $1 != "nobody" {print $1}' /etc/passwd) root; do
f="$(eval echo ~$u)/.ssh/authorized_keys"
if [ -f "$f" ]; then
echo "=== $u ==="
grep -c '^[^#]' "$f"
# Print key comments only (last field)
awk '{print " "$NF}' "$f"
fi
done
The "key comments" are the trailing fields after the key body — usually "user@hostname" identifying who the key belongs to. If a key has no comment or one you don't recognize, that's a flag.
Removing a user
# Lock first (preserves home + audit trail)
usermod -L alice
passwd -l alice
# Or fully remove
userdel -r alice # -r removes home directory + mail spool
Don't forget the corresponding sudoers entry — rm /etc/sudoers.d/alice. Forgetting that leaves a sudoers entry referencing a missing user, which most sudo versions handle gracefully but is still cruft.
SSH access patterns for teams
Pattern A: one account per person, shared sudo. Each team member gets their own login (alice, bob). Each sudo'd command is logged with the calling user. Easiest to audit; most expensive in account management.
Pattern B: shared "ops" account, individual SSH keys. One account (ops) with multiple keys in authorized_keys — one per team member, identified by the key comment. Faster setup; can't tell after the fact which person did which command. Don't use this for compliance-sensitive systems.
Pattern C: bastion host. SSH only via a hardened jump box; the actual VPSes accept SSH only from the bastion's IP. Higher operational overhead; appropriate for fleets of more than ~5 VPSes.
SSH session logging (audit beyond sudo)
Sudo logs the commands it ran. It doesn't log what people did inside interactive shells or via shell escapes. For tighter audit:
auditdwith appropriate rules can log every command execution at the syscall level.tlog/scriptreplaycan record entire SSH sessions for later playback.- Shipping bash history to syslog via
PROMPT_COMMAND='history -a; logger -t bash-history -- "$(history 1)"'in/etc/bash.bashrc.
These add operational noise. Don't enable them unless you have a real compliance need; if you do, plan for the log volume.
Common pitfalls
- Editing
/etc/sudoersdirectly with vim and saving syntactically invalid content. Usevisudo. - Putting
%group ALL=NOPASSWD:ALLin sudoers — gives passwordless root to anyone in the group, easy to forget. Use named users for elevated access. - Forgetting to remove the
ubuntu/debian/almalinuxdefault cloud-user after creating real accounts. Those exist mostly for first-boot setup; lock them once you have your own. - Leaving
PermitRootLogin yesafter creating named users. There's no scenario where SSH-as-root improves security; turn it off once you have an alternative path in.
Also Read
Powered by WHMCompleteSolution