Keys, MFA & bastions
Strong auth and a single front door.
If keys are your only authentication, the keys themselves become critical. Generate modern ones — ed25519 is the current best default (small, fast, strong) — protect the private key with a passphrase so a stolen key file is not immediately usable, and keep private keys on the client only, never on the server. The server holds only public keys, in each user’s ~/.ssh/authorized_keys, which is why a compromised server does not leak the ability to log in elsewhere.
$ ssh-keygen -t ed25519 -C "deploy@laptop" # generate a modern keypair (with a passphrase!)$ ssh-copy-id deploy@server # install YOUR public key on the server# on the server, the public key lands here (mode must be tight):$ ls -la ~/.ssh/authorized_keys-rw------- 1 deploy deploy ... authorized_keys # 600, owned by the user — SSH enforces this
Add a second factor
For sensitive systems, a key alone can be strengthened to two factors. Hardware-backed keys (a FIDO2/U2F security key, using ed25519-sk) require the physical token to be present and touched, so a copied key file is useless without the hardware. Alternatively, a PAM TOTP module adds a one-time code on top of the key. The principle is defense in depth on your one remaining auth path: something you have (the key) plus something you physically possess or know.
# a hardware-backed key — the private key cannot be copied off the token$ ssh-keygen -t ed25519-sk -C "deploy@yubikey"# authentication now requires the physical security key to be present and tapped
A bastion: one front door
Rather than exposing SSH on every server to the internet, put one hardened jump host (a bastion) at the edge and reach everything else through it. Internal servers accept SSH only from the bastion’s network, so the attack surface collapses from "every host on port 22" to "one heavily-monitored gateway." SSH’s ProxyJump makes this transparent — you type one command and it hops through the bastion — and the bastion becomes the single place to log, audit, and enforce access.
Host bastionHostName bastion.acme.internalUser deployHost app-*ProxyJump bastion # ssh app-01 transparently hops through the bastionUser deploy# internal hosts’ firewalls allow port 22 ONLY from the bastion’s address