Create a sudo user with an SSH key and disable password login
A bash script that creates an admin user in the sudo or wheel group, installs their SSH public key and can switch sshd to key-only logins with a config test and rollback.
Drafted with AI from official documentation. Scripts were run in a test environment and repository links verified. Awaiting a human check. Did it work for you? Last edited Outdated?
What this changes
Creates the user or adds it to the sudo or wheel group, appends the key to its ~/.ssh/authorized_keys and may ask for a password. With --harden it writes /etc/ssh/sshd_config.d/10-hardening.conf (no password or root login) and reloads sshd.
How to undo it
Undo --harden: sudo rm /etc/ssh/sshd_config.d/10-hardening.conf (or restore its .bak) and sudo systemctl reload ssh. Remove a new user with sudo userdel -r NAME. Keep a root session open while testing.
A fresh server often has only root with a password, or a cloud default user. The usual hardening is: create a personal admin account, give it sudo, log in with an SSH key, and then turn off password logins and direct root logins so password guessing becomes pointless. This script does those steps in a safe order on Debian, Ubuntu, RHEL, Rocky and AlmaLinux.
Before you start
- On your own computer, create a key pair if you do not have one:
ssh-keygen -t ed25519. Only the.pubfile goes to the server; the private key never leaves your computer. - Copy the public key to the server, for example
scp ~/.ssh/id_ed25519.pub [email protected]:/root/admin.pub. - Keep your current SSH session open the whole time and make sure you have console access (hypervisor, cloud serial console) in case something goes wrong.
The script
Check before you run this
- Runs with administrator rights. Make sure you understand every line.
#!/usr/bin/env bash
set -Eeuo pipefail
usage() {
cat <<USAGE
Usage: $(basename "$0") [--harden] <username> <public-key-file>
Create an admin user who can use sudo and log in with an SSH key.
--harden also turn off SSH password logins and direct root logins
(only after the key is in place and the config test passes)
Example:
$(basename "$0") admin ./admin_ed25519.pub
$(basename "$0") --harden admin ./admin_ed25519.pub
USAGE
}
die() { echo "ERROR: $*" >&2; exit 1; }
log() { echo "==> $*"; }
HARDEN=0
if [[ ${1:-} == "--harden" ]]; then HARDEN=1; shift; fi
[[ ${1:-} == "-h" || ${1:-} == "--help" ]] && { usage; exit 0; }
[[ $# -eq 2 ]] || { usage >&2; exit 2; }
NEW_USER=$1
KEY_FILE=$2
[[ $EUID -eq 0 ]] || die "run as root (sudo)"
[[ $NEW_USER =~ ^[a-z_][a-z0-9_-]{0,31}$ ]] || die "invalid username: $NEW_USER"
[[ -r $KEY_FILE ]] || die "cannot read key file: $KEY_FILE"
ssh-keygen -l -f "$KEY_FILE" >/dev/null 2>&1 || die "$KEY_FILE is not a valid SSH public key"
grep -q 'PRIVATE KEY' "$KEY_FILE" && die "$KEY_FILE looks like a PRIVATE key; pass the .pub file"
if getent group sudo >/dev/null; then
ADMIN_GROUP=sudo
elif getent group wheel >/dev/null; then
ADMIN_GROUP=wheel
else
die "neither a 'sudo' nor a 'wheel' group exists"
fi
CREATED=0
if id "$NEW_USER" >/dev/null 2>&1; then
log "user $NEW_USER exists, adding to $ADMIN_GROUP"
usermod -a -G "$ADMIN_GROUP" "$NEW_USER"
else
log "creating user $NEW_USER in group $ADMIN_GROUP"
useradd -m -s /bin/bash -G "$ADMIN_GROUP" "$NEW_USER"
CREATED=1
fi
HOME_DIR=$(getent passwd "$NEW_USER" | cut -d: -f6)
PRIMARY_GROUP=$(id -gn "$NEW_USER")
SSH_DIR="$HOME_DIR/.ssh"
AUTH_KEYS="$SSH_DIR/authorized_keys"
install -d -m 700 -o "$NEW_USER" -g "$PRIMARY_GROUP" "$SSH_DIR"
touch "$AUTH_KEYS"
if grep -qxF "$(cat "$KEY_FILE")" "$AUTH_KEYS"; then
log "key already present in $AUTH_KEYS"
else
cat "$KEY_FILE" >> "$AUTH_KEYS"
log "key added to $AUTH_KEYS"
fi
chown "$NEW_USER:$PRIMARY_GROUP" "$AUTH_KEYS"
chmod 600 "$AUTH_KEYS"
if command -v restorecon >/dev/null 2>&1; then
restorecon -R "$SSH_DIR" || true
fi
if (( CREATED )); then
if [[ -t 0 ]]; then
log "set a password for $NEW_USER (sudo will ask for it)"
passwd "$NEW_USER"
else
log "remember to run: passwd $NEW_USER (sudo will ask for it)"
fi
fi
if (( HARDEN == 0 )); then
log "done. Test from a NEW terminal: ssh $NEW_USER@<server> then sudo -v"
exit 0
fi
CONF_DIR=/etc/ssh/sshd_config.d
DROPIN="$CONF_DIR/10-hardening.conf"
grep -Eq '^[[:space:]]*Include[[:space:]]+/etc/ssh/sshd_config\.d/\*\.conf' /etc/ssh/sshd_config \
|| die "/etc/ssh/sshd_config does not include $CONF_DIR/*.conf; add the setting by hand"
log "writing $DROPIN"
install -d -m 755 "$CONF_DIR"
BACKUP=""
if [[ -f $DROPIN ]]; then
BACKUP="$DROPIN.bak.$(date +%Y%m%d%H%M%S)"
cp -p "$DROPIN" "$BACKUP"
fi
cat > "$DROPIN" <<CONF
PasswordAuthentication no
KbdInteractiveAuthentication no
PermitRootLogin no
PubkeyAuthentication yes
CONF
chmod 644 "$DROPIN"
SSHD=$(command -v sshd || echo /usr/sbin/sshd)
if ! "$SSHD" -t; then
if [[ -n $BACKUP ]]; then mv "$BACKUP" "$DROPIN"; else rm -f "$DROPIN"; fi
die "sshd config test failed; change rolled back"
fi
log "effective settings:"
"$SSHD" -T 2>/dev/null | grep -Ei '^(passwordauthentication|kbdinteractiveauthentication|permitrootlogin|pubkeyauthentication) ' || true
RELOADED=0
for unit in ssh.service sshd.service; do
if systemctl is-active --quiet "$unit"; then
systemctl reload "$unit"
log "reloaded $unit"
RELOADED=1
break
fi
done
(( RELOADED )) || log "no running ssh.service or sshd.service found; settings apply when sshd next starts"
log "done. KEEP THIS SESSION OPEN and test from a new terminal: ssh $NEW_USER@<server>"
Usage
Run it in two passes so you can test the key before passwords are switched off:
Check before you run this
- Runs with administrator rights. Make sure you understand every line.
# pass 1: create the user and install the key
sudo bash add-admin.sh admin /root/admin.pub
# from your computer, in a NEW terminal:
ssh [email protected]
sudo -v
# pass 2: only after the key login and sudo work
sudo bash add-admin.sh --harden admin /root/admin.pub
After pass 2, keep the old session open and test once more from a new terminal. Also check that passwords are really refused: ssh -o PubkeyAuthentication=no [email protected] should fail with Permission denied (publickey) instead of asking for a password.
What the script does
- Validates the username and that the key file is a readable SSH public key (
ssh-keygen -l), and refuses private keys. - Uses the
sudogroup if it exists (Debian, Ubuntu), otherwisewheel(RHEL family). For an existing user it adds the group withusermod -a -G, keeping other groups. - Creates
~/.sshwith mode 700 andauthorized_keyswith mode 600 owned by the user. sshd ignores keys in files or directories that other users can write. On SELinux systems it restores the correct file labels withrestorecon. - For a new user, asks you to set a password interactively: the key is for logging in, the password is what sudo asks for.
- With
--harden, writes a drop-in file/etc/ssh/sshd_config.d/10-hardening.conf, runssshd -t, rolls the file back if the test fails, prints the effective values fromsshd -Tand reloads the SSH service (sshon Debian/Ubuntu,sshdon RHEL family).
Why a drop-in file numbered 10
Current Debian, Ubuntu and RHEL releases include /etc/ssh/sshd_config.d/*.conf near the top of sshd_config. For most sshd keywords the first value read wins, and the files are read in name order. Some images ship their own drop-in that turns passwords back on; a low number like 10- makes sure this file is read first. Always confirm with sudo sshd -T | grep -i passwordauthentication, which prints the value sshd will really use.
The hardening settings
PasswordAuthentication no: no password logins. The OpenSSH default isyes.KbdInteractiveAuthentication no: closes the keyboard-interactive path, which can also ask for passwords. Older OpenSSH releases call itChallengeResponseAuthentication; ifsshd -trejects the keyword there, the script rolls back and you can use the old name.PermitRootLogin no: no direct root login at all. The OpenSSH default,prohibit-password, still allows root with a key.PubkeyAuthentication yes: the default, stated explicitly.
For further tightening, AllowUsers admin or AllowGroups limits which accounts may log in over SSH at all.
If you lock yourself out
Use the console, remove or fix /etc/ssh/sshd_config.d/10-hardening.conf, run sshd -t and reload the service. That is why the script never touches the main sshd_config.
Official documentation
Did this work for you?
No votes yet. Be the first.
If it did not work, the report form below tells the editors what went wrong.
See also
- 0053 Set up SSH keys: ssh-keygen, ssh-copy-id and ~/.ssh/config SSH keys let you log in to servers and push to Git hosts without typing a password. Create an ed25519 key, install… Beginner · Step by step · Linux, macOS, Windows Article Keys & config 15 Oct 2026
- 0054 Fix: Permission denied (publickey) The server refused every key your SSH client offered. Find out which key was offered, make sure its public half is on… Intermediate · Fixes an error · Linux, macOS, Windows Article Keys & config 15 Oct 2026
- 0177 Users, groups and sudo on Debian, Ubuntu and RHEL How Linux accounts and groups work, how to create users, and how sudo gives admin rights through the sudo group on Debian/Ubuntu… Beginner · Explains an idea · Linux Article Users, sudo & hardening 15 Oct 2026
- 0249 Set up and harden a fresh Linux server in one afternoon Take a new Ubuntu, Debian or Rocky/Alma server from first login to a safer baseline: sudo admin, SSH keys only, firewall, automatic… Beginner · Guided project · Linux Article Networking & firewalls · Packages & updates · Users, sudo & hardening 15 Oct 2026
- 0254 Automate three servers with Ansible: roles, templates, Vault and safe dry runs Build a real Ansible project for two web servers and one database server: inventory groups, SSH keys, roles, templates, handlers, an encrypted… Intermediate · Guided project · Linux, macOS Article Ansible · Keys & config 15 Oct 2026
- 0317 Fix: Permission denied when running a script (chmod +x) bash: ./script.sh: Permission denied means the file exists but you may not execute it. Usually the execute bit is missing; sometimes the… Beginner · Fixes an error · Linux, macOS Article Basics · Users, sudo & hardening 15 Oct 2026
History
Every saved edit is kept. The plot on the sleeve draws one line per edit. Open any edit to see exactly what changed.
- Dominik +8,390 chars