What key-based login actually solves
Password login has two hard problems: it can be brute-forced and it must be typed every time. Key-based login proves identity with asymmetric keys — the private key stays local, the public key goes on the server. As long as the private key is safe, no amount of password guessing gets in.
Generate and deploy
# Prefer ed25519: short, fast, strong
ssh-keygen -t ed25519 -C "you@example.com"
# Copy the public key (or append it to ~/.ssh/authorized_keys)
ssh-copy-id -i ~/.ssh/id_ed25519.pub deploy@host
# If the private key has a passphrase, cache it with an agent
eval "$(ssh-agent -s)" && ssh-add ~/.ssh/id_ed25519
~/.ssh/config makes many hosts manageable
Host prod
HostName 203.0.113.10
User deploy
Port 2222
IdentityFile ~/.ssh/id_ed25519
ServerAliveInterval 30
Host *.internal
User ops
ProxyJump bastion
IdentityFile ~/.ssh/id_ed25519
After that, ssh prod is all you need; * plus ProxyJump also expresses "reach internal hosts through the bastion" in one block.
Hardening on the server side
- Disable password login (after confirming keys work):
PasswordAuthentication no; - No direct root login:
PermitRootLogin no; - A non-default port is not a substitute for keys — it only reduces log noise;
- Restrict who may log in with
AllowUsers.
Four common pitfalls
- Loose private key permissions: must be 600, or SSH refuses to use it;
- known_hosts conflicts: after a rebuild or IP reuse the fingerprint changes — verify it, then remove the old line, never blindly clear the file;
- Agent forwarding abuse:
ForwardAgentlets root on the jump host borrow your key to move laterally — leave it off unless required; - One key everywhere: separate key pairs per purpose so a leak has a smaller blast radius.
Common questions
Why passphrase-protect the private key? It is one more barrier if the device is lost, and with an agent it costs you nothing day to day. Can keys be brute-forced? Not ed25519 or a long RSA key with today's compute — the real risk is the key file leaking. Connections keep dropping? Set ServerAliveInterval on the client and a sensible ClientAliveInterval on the server.
Choosing and generating keys
Use Ed25519 for all new keys, falling back to RSA 4096 only for old devices. Add a comment so keys can be cleaned up later:
ssh-keygen -t ed25519 -C "you@example.com", always with a passphrase on the private key;- Private keys need mode
600and their directory700, or OpenSSH refuses to load them; - One key pair per device so it can be revoked individually — never share one private key across machines.
Practical ~/.ssh/config
Putting jump hosts, ports, users and key paths in config removes a lot of repeated flags:
ServerAliveInterval 30underHost *stops long idle sessions being dropped;ProxyJump bastionreaches internal hosts without a manual hop;- Set
IdentityFileandIdentitiesOnly yesexplicitly so too many public keys are not offered before one is accepted.
Common misconceptions
- Agent forwarding: after hopping to an untrusted host it can borrow your agent to reach other machines — prefer
ProxyJumpand keep forwarding off; - Copying private keys around: generate a new key pair and distribute the public key instead;
- Disabling password auth without testing keys: verify key login works before setting
PasswordAuthentication no, or you may lock yourself out; - Ignoring known_hosts changes: a changed host key may be a rebuild or a MITM — verify before clearing it.
Server hardening checklist
No direct root login, no password authentication, an allow-list of users, failure rate limiting, and sshd logs shipped to central storage so brute-force attempts are visible.
Key rotation and revocation
- One key pair per device: when a device is lost or someone leaves, remove that single line from
authorized_keyswithout affecting anyone else; - The comment is your inventory: put the owner and device in
-C, or nobody will dare delete a line six months later; - Rotate periodically: unchanged private keys accumulate risk — rotate yearly with a short overlap window;
- Certificates beat bare keys: at modest scale, SSH certificates issued by a CA carry expiry and therefore revoke themselves.
Common connection failures
Permissions 0644 ... are too open: private key too permissive — set600;Too many authentication failures: too many local keys offered — addIdentitiesOnly yesand an explicitIdentityFile;Host key verification failed: the remote host key changed — verify why before updatingknown_hosts.
Organising multi-environment config
Split ~/.ssh/config per environment and pull the pieces in with Include so one file does not grow indefinitely. Prefix production aliases clearly (for example prod-) and keep strict host key checking on — naming discipline reduces connecting to the wrong box.