I know that dead sshd is infuriating — you're locked out of your own box and the error gives you almost nothing to go on. Let's get it running.
The fix that works 90% of the time
Run this on the machine that's refusing SSH connections:
sudo ssh-keygen -A
sudo systemctl restart sshd
On Debian and Ubuntu the service is called ssh, not sshd:
sudo ssh-keygen -A
sudo systemctl restart ssh
ssh-keygen -A generates any host key types that are missing based on what your sshd_config expects. It's idempotent — it won't touch keys that already exist. If the original key was deleted, corrupted, or never created (this happens constantly on minimal Docker images and freshly-imaged cloud boxes), this single command rebuilds them.
Check that it worked:
ls -l /etc/ssh/ssh_host_*
You should see something like:
-rw------- 1 root root 505 Feb 14 10:22 ssh_host_ed25519_key
-rw-r--r-- 1 root root 100 Feb 14 10:22 ssh_host_ed25519_key.pub
-rw------- 1 root root 1675 Feb 14 10:22 ssh_host_rsa_key
-rw-r--r-- 1 root root 388 Feb 14 10:22 ssh_host_rsa_key.pub
If sshd still won't start
Permissions are the next suspect. sshd refuses to use a private key readable by anyone but root, and it will fail silently-ish with the same hostkeys message. Set them explicitly:
sudo chmod 600 /etc/ssh/ssh_host_*_key
sudo chmod 644 /etc/ssh/ssh_host_*_key.pub
sudo chown root:root /etc/ssh/ssh_host_*
Then check what the daemon actually complains about using debug mode, which prints far more than the systemd unit does:
sudo /usr/sbin/sshd -d -e
You'll see the real reason in plain text — a bad HostKey path, a config typo, an SELinux denial, or a mismatch between which key types your sshd_config lists and which files exist.
Why this happens
SSH doesn't use passwords to prove itself to you. Every server has a private host key, and that's how your client knows it's talking to the same machine it talked to last time. Without a host key, sshd has nothing to identify itself with, so it bails out before accepting a single connection.
The error text — sshd: no hostkeys available -- exiting. — appears when sshd starts up and finds zero usable private keys. Either the files are gone, unreadable, or your sshd_config points to a path where nothing exists. Common triggers I've seen in the wild: someone ran rm /etc/ssh/ssh_host_* while troubleshooting something unrelated, a container image was built with keys stripped to keep the image small, or a distro upgrade regenerated the config with a HostKey line pointing at /etc/ssh/keys/ instead of /etc/ssh/.
The other classic: a chef, ansible, or cloud-init provisioning step deleted and recreated /etc/ssh/ but never re-generated the keys. The directory exists, the config looks fine, and sshd is dead on arrival.
Less common variations
sshd_config points at keys that don't exist
Open /etc/ssh/sshd_config and look for lines starting with HostKey:
grep -i '^HostKey' /etc/ssh/sshd_config
If it says something like HostKey /etc/ssh/keys/ssh_host_rsa_key but that directory doesn't exist, ssh-keygen -A won't help — it generates into the default /etc/ssh/ location. Either create the directory and regenerate there, or fix the config to point at the standard path. I'd just comment the line out; the defaults are sane.
Running inside a container
Alpine, Debian slim, and most "minimal" base images ship sshd without host keys on purpose. You have to run ssh-keygen -A in the Dockerfile at build time, or as part of the entrypoint. Doing it at runtime is fine too, but then every container restart creates new keys and every client gets the scary "REMOTE HOST IDENTIFICATION HAS CHANGED" warning. Build them once, bake them in, and mount real keys if this is production.
FIPS mode or restricted crypto policies
RHEL 9 and Fedora in FIPS mode won't generate or accept certain key types. If ssh-keygen -A runs but only produces ed25519 and then sshd still dies, check your crypto policy:
update-crypto-policies --show
On FIPS or FIPS:OSPP, RSA with short key sizes gets skipped. Generate an explicit key if you need one:
sudo ssh-keygen -t rsa -b 3072 -f /etc/ssh/ssh_host_rsa_key -N ''
sudo ssh-keygen -t ed25519 -f /etc/ssh/ssh_host_ed25519_key -N ''
SELinux blocking the key files
On RHEL-family systems with SELinux enforcing, a restored-from-backup /etc/ssh/ can have wrong labels. Check with:
ls -Z /etc/ssh/ssh_host_rsa_key
You want system_u:object_r:sshd_key_t:s0. If not:
sudo restorecon -Rv /etc/ssh/
Prevention
- Back up
/etc/ssh/before any upgrade or config change. It's tiny. Just tar it somewhere. - Never delete host keys unless you mean it. Regenerating them triggers a host key changed warning for every client that's ever connected, and you'll spend the next week clearing
known_hostsentries. - Bake keys into your golden images or cloud-init. Use the same keys across a fleet if you're load-balancing SSH, otherwise clients will see different fingerprints on each node.
- Monitor it. A one-line check in your monitoring system —
sshd -treturns 0 or it doesn't — catches this before your on-call does.
The whole thing usually comes down to missing files and a two-second command. Run ssh-keygen -A, fix permissions if needed, and you're back.