TL;DR — Quick Summary
SSH key management guide for sysadmins: generate Ed25519 keys, use ssh-agent, configure ~/.ssh/config, rotate and revoke keys, and use SSH certificates.
SSH keys are the foundation of secure remote access for every sysadmin and DevOps engineer. This SSH key management guide covers the full lifecycle — generating keys, caching them with ssh-agent, organizing multiple hosts with ~/.ssh/config, deploying public keys, rotating and revoking compromised credentials, scaling with SSH certificates, and hardening with hardware keys — so you can manage fleets confidently and securely.
Prerequisites
- Linux, macOS, or WSL with OpenSSH client (
ssh,ssh-keygen,ssh-agent,ssh-copy-id). - SSH access to at least one remote server for practice.
- Optional:
ssh-audit(pip install ssh-audit) for algorithm auditing. - Optional: a YubiKey or other FIDO2 hardware key for maximum protection.
Generating SSH Keys: Ed25519 vs RSA
The first decision is key type. OpenSSH supports several algorithms, but only two are worth using in 2026.
Ed25519 (recommended)
ssh-keygen -t ed25519 -C "you@example.com"
Ed25519 uses elliptic-curve cryptography on Curve25519. Keys are 256 bits, signatures are fast, and the algorithm is resistant to side-channel attacks. This is the default choice for all new keys.
RSA (legacy compatibility)
ssh-keygen -t rsa -b 4096 -C "you@example.com"
Use RSA-4096 only when the remote system does not support Ed25519 — for example, very old OpenSSH versions (< 6.5) or certain network appliances. RSA-2048 is no longer recommended; use 4096 if you must use RSA at all.
Passphrase best practices
When ssh-keygen asks for a passphrase:
- Always set one. A passphrase encrypts your private key file using AES-128-CBC (or bcrypt with newer OpenSSH). A stolen key file without the passphrase is useless.
- Use at least 20 characters — a random passphrase from your password manager is ideal.
- Store it in your password manager (1Password, Bitwarden, pass).
SSH Agent: Cache Keys for the Session
Typing your passphrase on every connection is impractical. ssh-agent holds decrypted private keys in memory for the duration of your shell session.
# Start the agent (usually auto-started on login)
eval "$(ssh-agent -s)"
# Add your key (prompts for passphrase once)
ssh-add ~/.ssh/id_ed25519
# List loaded keys
ssh-add -l
# Remove all keys (e.g., before locking your screen)
ssh-add -D
On macOS, ssh-add --apple-use-keychain stores the passphrase in the system Keychain so it survives reboots. On Linux, tools like gnome-keyring or KWallet integrate with the desktop session.
Security tip: Pass -t 3600 to ssh-add to automatically expire the key after one hour:
ssh-add -t 3600 ~/.ssh/id_ed25519
SSH Config File: Managing Multiple Hosts
The ~/.ssh/config file eliminates the need to remember usernames, ports, and identity files for every host.
# ~/.ssh/config
# Default settings for all hosts
Host *
AddKeysToAgent yes
IdentityFile ~/.ssh/id_ed25519
ServerAliveInterval 60
ServerAliveCountMax 3
# Production web server
Host prod-web
HostName 203.0.113.10
User deploy
Port 22
IdentityFile ~/.ssh/id_ed25519_prod
# Internal database server via jump host
Host db-internal
HostName 10.0.1.50
User dbadmin
ProxyJump prod-web
# GitHub
Host github.com
User git
IdentityFile ~/.ssh/id_ed25519_github
With this config, ssh prod-web connects with the right user and key, and ssh db-internal automatically tunnels through the jump host — no extra flags needed.
ProxyJump for bastion hosts
ProxyJump (OpenSSH 7.3+) replaces the old ProxyCommand pattern:
# One-liner without config file
ssh -J user@bastion user@internal-host
Deploying Public Keys
ssh-copy-id (easiest)
ssh-copy-id -i ~/.ssh/id_ed25519.pub user@host
This appends your public key to ~/.ssh/authorized_keys on the remote host atomically.
Manual deployment
cat ~/.ssh/id_ed25519.pub | ssh user@host \
"mkdir -p ~/.ssh && chmod 700 ~/.ssh && \
cat >> ~/.ssh/authorized_keys && \
chmod 600 ~/.ssh/authorized_keys"
Ansible at scale
- name: Deploy SSH public key
ansible.posix.authorized_key:
user: "{{ ansible_user }}"
state: present
key: "{{ lookup('file', '~/.ssh/id_ed25519.pub') }}"
File permissions — critical
| Path | Permission | Why |
|---|---|---|
~/.ssh/ | 700 | Only owner can list/enter |
~/.ssh/id_ed25519 | 600 | Private key — owner read/write only |
~/.ssh/id_ed25519.pub | 644 | Public key — readable by all |
~/.ssh/authorized_keys | 600 | Only owner can modify |
~/.ssh/config | 600 | Prevent others reading your host config |
SSH will refuse to use keys with world-readable permissions and silently fail.
Key Rotation Strategies
Rotate SSH keys when:
- A key is older than your organization’s policy (commonly 1–2 years).
- An employee with key access leaves.
- A machine or storage medium that held a private key is decommissioned or lost.
- You suspect compromise.
Safe rotation procedure:
- Generate a new key pair:
ssh-keygen -t ed25519 -f ~/.ssh/id_ed25519_new. - Deploy the new public key to all authorized hosts (without removing the old one yet).
- Verify you can log in with the new key:
ssh -i ~/.ssh/id_ed25519_new user@host. - Update
~/.ssh/configto use the new key. - Remove the old public key from all
authorized_keysfiles. - Securely delete the old private key:
shred -u ~/.ssh/id_ed25519_old.
Revoking Compromised Keys
SSH has no built-in revocation list for public keys, so revocation is a manual process:
- Remove the key immediately from every
authorized_keysfile on every host. - For SSH certificate infrastructures, add the key to a
RevokedKeysfile on the server (RevokedKeys /etc/ssh/revoked_keysinsshd_config) — this works for both certificates and plain public keys. - Audit login logs (
/var/log/auth.log,journalctl -u ssh) for sessions using the compromised key. - Rotate any secrets or credentials that may have been accessed.
SSH Certificates: Fleet Management at Scale
When you have dozens or hundreds of servers, managing authorized_keys on every machine is error-prone. SSH certificates solve this by introducing a Certificate Authority (CA).
Create a CA key pair
ssh-keygen -t ed25519 -f /etc/ssh/ca_key -C "org-ssh-ca"
Keep ca_key private and offline. Distribute ca_key.pub to every server.
Configure servers to trust the CA
# /etc/ssh/sshd_config
TrustedUserCAKeys /etc/ssh/ca_key.pub
Sign a user key
ssh-keygen -s /etc/ssh/ca_key \
-I "alice@corp" \ # key identity (shows in logs)
-n alice,admin \ # valid principals (usernames)
-V +30d \ # valid for 30 days
~/.ssh/id_ed25519.pub
This creates id_ed25519-cert.pub. The user presents this certificate alongside their private key. No entry in authorized_keys is needed — the CA signature is sufficient.
Benefits over authorized_keys
- Automatic expiry — certificates expire without manual cleanup.
- Centralized control — revoke by removing CA trust or using
RevokedKeys. - Principal enforcement — a certificate can only log in as specific usernames.
- Audit trail — the key identity string appears in auth logs.
Hardware Keys: YubiKey and FIDO2
For the highest assurance, store the private key on a hardware security key. The private key never leaves the device.
# Generate a key stored on a FIDO2/YubiKey (OpenSSH 8.2+)
ssh-keygen -t ed25519-sk -O resident -C "yubikey@corp"
# -O resident stores the key slot on the device itself
# (can be re-imported to a new machine without re-registering on servers)
The -sk suffix indicates a “security key” type. During authentication, the device requires a physical touch to authorize the operation, making remote key theft impossible.
Comparison Table: Ed25519 vs RSA vs ECDSA
| Feature | Ed25519 | RSA-4096 | ECDSA P-256 |
|---|---|---|---|
| Key size | 256 bits | 4096 bits | 256 bits |
| Signature speed | Very fast | Slow | Fast |
| Verification speed | Fast | Fast | Fast |
| Key file size | ~400 bytes | ~3.3 KB | ~600 bytes |
| Legacy compatibility | OpenSSH 6.5+ (2014) | Universal | OpenSSH 5.7+ (2011) |
| Side-channel resistance | Excellent | Moderate | Poor (ECDSA has nonce issues) |
| FIDO2/hardware key | Yes (ed25519-sk) | No | Yes (ecdsa-sk) |
| Recommendation | First choice | Legacy only | Avoid |
ECDSA P-256/P-384 is generally not recommended because its implementation in many libraries is vulnerable to nonce reuse attacks (similar to the PS3 signing key compromise). Ed25519 uses a deterministic nonce derived from the message, eliminating this class of vulnerability.
Real-World Scenario
You have a production fleet of 40 Linux servers. Three developers left the company last month. You need to rotate all their SSH access immediately without downtime.
With authorized_keys: You must log into (or Ansible into) all 40 servers, grep for each departed user’s public key fingerprint, remove those lines, and repeat. One missed server is a gap.
With SSH certificates: The CA simply stops signing certificates for those users. Existing certificates expire naturally (if you issued 30-day certs). For immediate revocation, add their certificate fingerprints to the RevokedKeys file pushed via Ansible in seconds:
ssh-keygen -lf departed-user-cert.pub >> /tmp/revoked
ansible all -m copy -a "src=/tmp/revoked dest=/etc/ssh/revoked_keys mode=0644"
ansible all -m service -a "name=sshd state=reloaded"
Done in under two minutes across the entire fleet.
GitHub and GitLab SSH Key Setup
GitHub
# Copy public key
cat ~/.ssh/id_ed25519.pub | xclip -selection clipboard
# Or use GitHub CLI
gh ssh-key add ~/.ssh/id_ed25519.pub --title "workstation-2026"
Add the key at GitHub → Settings → SSH and GPG keys → New SSH key.
Test: ssh -T git@github.com
GitLab
Add the key at GitLab → User Settings → SSH Keys.
Test: ssh -T git@gitlab.com
Add a ~/.ssh/config entry if you use self-hosted GitLab:
Host gitlab.corp.example.com
User git
IdentityFile ~/.ssh/id_ed25519_gitlab
Gotchas and Edge Cases
- StrictHostKeyChecking=no in scripts — Disabling host key checking (
-o StrictHostKeyChecking=no) is common in automation but removes protection against man-in-the-middle attacks. Instead, pre-populate~/.ssh/known_hostswithssh-keyscanduring provisioning. - Shared keys — Never share a single private key between multiple people or machines. Each person and machine should have its own key pair so individual access can be revoked.
- Root login — Disable
PermitRootLogin yesinsshd_config. Use a regular user withsudo. If you must allow root, usePermitRootLogin prohibit-password. - Default key names — If you create a non-default key (e.g.,
id_ed25519_prod), SSH will not automatically use it. Specify it in~/.ssh/configor pass-iexplicitly. - authorized_keys options — You can restrict what a key can do:
command="backup-script.sh",no-pty,no-X11-forwarding ssh-ed25519 AAAA...limits that key to running one command only.
Troubleshooting
| Problem | Solution |
|---|---|
| ”Permission denied (publickey)“ | Check file permissions with ls -la ~/.ssh/. Run ssh -vvv host for verbose output showing which keys are tried |
| Agent not working | Verify with ssh-add -l. If “Could not open connection to agent”, run eval $(ssh-agent -s) |
| Wrong key used for host | Add an explicit IdentityFile and IdentitiesOnly yes in ~/.ssh/config |
| ”Host key verification failed” | The server’s host key changed. Remove the old entry: ssh-keygen -R hostname. Verify the new key fingerprint out-of-band before accepting |
| Certificate rejected | Check expiry with ssh-keygen -L -f cert.pub. Verify the principal matches the login username |
| YubiKey not detected | Ensure pcscd is running (systemctl start pcscd). Try ssh-add -K to load resident keys from the device |
Summary
- Generate Ed25519 keys for all new key pairs; use RSA-4096 only for legacy compatibility.
- Always set a passphrase and use
ssh-agentto cache it for the session. - Use
~/.ssh/configwithProxyJumpto manage multiple hosts and bastion servers cleanly. - Deploy keys with
ssh-copy-id; never share private keys between people or machines. - For fleets of more than ~10 servers, adopt SSH certificates — they provide expiry, principals, and centralized revocation.
- Store keys on FIDO2 hardware (YubiKey) for maximum security.
- Enforce correct file permissions (
700on~/.ssh/,600on keys andauthorized_keys). - Audit your SSH server configuration regularly with
ssh-audit.