Skip to content

SSH key-only login ​

Ubuntu ships with PasswordAuthentication yes. Bots brute-force port 22 non-stop, and any user created later with adduser becomes password-reachable from the internet immediately.

Check current state ​

fish
sudo sshd -T | grep -Ei 'passwordauthentication|kbdinteractive|pubkeyauthentication|permitrootlogin'

sshd -T resolves all Include drop-ins and prints what sshd actually runs with. Grepping /etc/ssh/sshd_config alone is misleading - most lines there are commented examples showing the default, not settings.

Apply ​

Create a drop-in, do not edit /etc/ssh/sshd_config. First value wins and drop-ins load first, so 10- overrides the 50-cloud-init.conf that cloud images ship.

fish
sudo vim /etc/ssh/sshd_config.d/10-hardening.conf
PasswordAuthentication no
KbdInteractiveAuthentication no
PubkeyAuthentication yes
PermitRootLogin prohibit-password

KbdInteractiveAuthentication is the one people miss - with UsePAM yes it lets PAM prompt for a password even when PasswordAuthentication no.

Keep the current session open while reloading.

fish
sudo sshd -t && sudo systemctl restart ssh.socket ssh.service

sudo sshd -T | grep -Ei 'passwordauthentication|kbdinteractive|permitrootlogin'

sshd -t validates syntax first, && aborts the restart on a typo.

Verify ​

From your laptop, in a NEW terminal:

fish
ssh -o PreferredAuthentications=password -o PubkeyAuthentication=no root@your.server.ip
# expected: Permission denied (publickey).

No password prompt means it worked. Open one normal ssh session to confirm the key still works before closing the original.

Audit ​

How much is hitting you, which usernames, which IPs:

fish
sudo grep -cE 'Failed password|Invalid user' /var/log/auth.log
sudo grep -oP 'Invalid user \K\w+' /var/log/auth.log | sort | uniq -c | sort -rn | head -20
sudo grep -oP 'from \K[\d.]+' /var/log/auth.log | sort | uniq -c | sort -rn | head -10

Identical hit counts across several IPs means one operator splitting a wordlist over a rented pool. fail2ban bans per-IP and barely bites that, PasswordAuthentication no kills it outright.

Did anyone get in:

fish
sudo grep -h 'Accepted' /var/log/auth.log; sudo zgrep -h 'Accepted' /var/log/auth.log.*.gz

Every line should be Accepted publickey from an IP you recognize. Any Accepted password needs investigating.

Accounts with a usable password (L = locked, nothing can match it):

fish
sudo passwd -S root
sudo awk -F: '($2 ~ /^\$/) {print $1}' /etc/shadow   # empty is good

Persistence and payload checks:

fish
# only keys you recognize
sudo cat /root/.ssh/authorized_keys

# only 22/80/443
sudo ss -tulpn | grep -v '127.0.0.1\|::1'

# cryptominers pin the CPU
top -bn1 | head -15

# only your IPs
last -20

If any of those come back dirty, rebuild the box rather than cleaning it - a compromised host cannot be trusted to report on itself.

Note on hourly pubkey failures ​

userauth_pubkey: signature algorithm ssh-rsa not in PubkeyAcceptedAlgorithms [preauth]

OpenSSH 8.8+ rejects RSA keys signed with SHA-1. Repeating on a precise schedule means it is your own automation (CI, backup, monitoring) with an old key, not a bot. Trace it by PID to find the source:

fish
sudo grep 'sshd\[114034\]' /var/log/auth.log

Fix the client to use Ed25519 or rsa-sha2-256 rather than re-enabling ssh-rsa on the server.