I sell a server hardening checklist, so last week I did something most checklist authors never do: I re-ran every single command against a real Ubuntu 24.04 box instead of trusting the docs.
Two of the commands were wrong. Not "suboptimal" — wrong. One of them fails silently, which is worse. Here's the interesting one.
The setup
Most SSH hardening guides tell you the same thing: don't edit /etc/ssh/sshd_config directly. Drop your settings into /etc/ssh/sshd_config.d/, conventionally in a file named something like 99-hardening.conf. High number, loads last, wins. Clean, package-manager-safe advice.
It's wrong on cloud images.
What actually happens
Ubuntu cloud images (AWS, DigitalOcean, Hetzner, all of them) ship a file at:
/etc/ssh/sshd_config.d/60-cloudimg-settings.conf
And here's the part the guides skip: sshd reads the drop-in directory with a glob, in alphabetical order — and for most keywords, sshd uses first-value-wins semantics. The first setting it encounters is the one that sticks.
60 sorts before 99. So your carefully written 99-hardening.conf loses to the cloud image's file for every keyword they both set — and it loses silently. No error, no warning. Your hardening setting just... doesn't apply.
I proved it the honest way: wrote a test value into 99-hardening.conf on a real 24.04 box and ran:
sudo sshd -T | grep -i <keyword>
The effective config showed the cloud image's value, not mine. My file might as well not have existed.
The fix (30 seconds)
Name your file so it sorts before the cloud image's file:
sudo mv /etc/ssh/sshd_config.d/99-hardening.conf \
/etc/ssh/sshd_config.d/10-hardening.conf
Then verify — don't trust, verify:
sudo sshd -T | grep -i <keyword>
sshd -T prints the effective configuration after all includes are resolved. If your value shows up there, it won. If it doesn't, something earlier in the sort order is beating you, and now you know how to find it.
This also means the generic advice "put it in the main sshd_config" is doubly broken: the main file Includes the drop-in directory at the top, so drop-ins beat the main file too. The drop-in directory is the right place — you just have to win the sort order.
Bonus bug: systemctl restart sshd fails on Ubuntu
While I was at it: half the guides say systemctl restart sshd after editing. On Ubuntu there is no sshd.service — the unit is ssh. The command fails, and if you're following a guide at 2 AM you get to debug that instead of sleeping:
sudo systemctl restart ssh # not sshd, on Ubuntu/Debian
(RHEL-family distros do use sshd. Know your box.)
The actual lesson
Doc-based audits miss ordering and naming subtleties. The only audit I trust anymore is running the real binary and reading what it says: sshd -T for SSH, nginx -t for nginx, and so on. If your hardening checklist wasn't verified against a live system, it's a wish list.
I turned the full verified checklist into a 46-checkpoint playbook (every command tested like this one), but the 7-step starter version is free — Emergency Server Lockdown Checklist. Lock down a VPS tonight with copy-paste commands.
Top comments (0)