← Back to all articles

SSH Keys and config: From Generation to Managing Many Hosts

SSHCLI

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

  1. Loose private key permissions: must be 600, or SSH refuses to use it;
  2. known_hosts conflicts: after a rebuild or IP reuse the fingerprint changes, verify it, then remove the old line, never blindly clear the file;
  3. Agent forwarding abuse: ForwardAgent lets root on the jump host borrow your key to move laterally, leave it off unless required;
  4. 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 600 and their directory 700, 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 30 under Host * stops long idle sessions being dropped;
  • ProxyJump bastion reaches internal hosts without a manual hop;
  • Set IdentityFile and IdentitiesOnly yes explicitly 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 ProxyJump and 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_keys without 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, set 600;
  • Too many authentication failures: too many local keys offered, add IdentitiesOnly yes and an explicit IdentityFile;
  • Host key verification failed: the remote host key changed, verify why before updating known_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.