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.