Scope: Architecture, authentication, key management, client/server configuration, certificate-based auth, hardening, and troubleshooting for OpenSSH 8.x/9.x.
This is the conceptual/reference doc — architecture, algorithm choices, certificate design. For working command references and day-to-day notes, see the companion docs:
- openssh-client-config.md —
~/.ssh/configpatterns, agent setup - openssh-key-management.md — generating/deploying/rotating keys
- openssh-server-hardening.md —
sshd_configwalkthrough + deprecated-advice notes - openssh-port-forwarding.md —
-D/-L/-Rexamples, sshuttle - openssh-file-transfer.md — scp, sshfs, tar-over-SSH, remote commands
- openssh-troubleshooting.md — recurring issues and fixes
Table of Contents¶
- What OpenSSH Is
- Protocol Architecture
- Authentication Methods
- Key Management
- Client Configuration (
~/.ssh/config) - Server Configuration (
sshd_config) - Certificate-Based Authentication
- Port Forwarding & Tunneling
- Connection Multiplexing
- Hardening Checklist
- Fail2ban / Rate Limiting Integration
- Logging & Auditing
- Algorithm Reference Tables
- Troubleshooting
- Common Pitfalls
- Further Reading
1. What OpenSSH Is¶
OpenSSH is the dominant open-source implementation of the SSH (Secure Shell) protocol, developed by the OpenBSD project. It provides:
ssh— remote shell / command execution clientsshd— the server daemonscp/sftp— file transfer (scp is legacy; prefer sftp)ssh-keygen— key and certificate generationssh-agent/ssh-add— in-memory key managementssh-copy-id— public key deployment helper
It replaced Telnet, rlogin, and rsh, all of which transmitted credentials and data in plaintext. Everything in SSH — authentication, commands, file transfers, forwarded traffic — is encrypted and integrity-checked.
2. Protocol Architecture¶
SSH-2 (the only protocol version in modern use — SSH-1 is deprecated and insecure) is layered into three sub-protocols defined by RFC 4251–4254:
| Layer | RFC | Responsibility |
|---|---|---|
| Transport Layer Protocol | RFC 4253 | Server authentication, key exchange, encryption, integrity (MAC), compression |
| User Authentication Protocol | RFC 4252 | Authenticates the client to the server (pubkey, password, keyboard-interactive, GSSAPI) |
| Connection Protocol | RFC 4254 | Multiplexes the encrypted tunnel into channels: shell sessions, port forwards, X11, sftp subsystem |
Connection sequence:
- TCP handshake to port 22 (or configured port).
- Protocol/version banner exchange.
- Key exchange (KEX) — negotiates a shared secret via Diffie-Hellman variants (see §13); this also derives session keys for symmetric encryption.
- Server host key verification — client checks the server's host key against
~/.ssh/known_hosts. This is what "Trust On First Connect" warnings refer to. - User authentication — one or more auth methods attempted in the order configured.
- Channel(s) opened over the now-encrypted transport — a shell, a command, a forwarded port, or an sftp subsystem.
Every subsequent packet is encrypted with the negotiated cipher and authenticated with the negotiated MAC (or an AEAD cipher, which combines both — see aes256-gcm and chacha20-poly1305).
3. Authentication Methods¶
Configured server-side via AuthenticationMethods and individual *Authentication directives, attempted client-side in the order listed in PreferredAuthentications (client) or as permitted by the server.
| Method | Directive | Notes |
|---|---|---|
| Public key | PubkeyAuthentication |
Default and recommended. Private key never leaves the client. |
| Password | PasswordAuthentication |
Should be no on any server exposed beyond a trusted LAN. |
| Keyboard-interactive | KbdInteractiveAuthentication |
Used for OTP/PAM prompts (e.g. TOTP via pam_google_authenticator). |
| Host-based | HostbasedAuthentication |
Trusts the client machine's key, not the user. Rare, legacy, fragile. |
| GSSAPI/Kerberos | GSSAPIAuthentication |
Enterprise SSO integration. |
| Certificate | Uses PubkeyAuthentication + TrustedUserCAKeys |
See §7 — scales far better than distributing raw keys. |
Public key auth, mechanically:
- Client sends a signature over the session identifier, made with its private key.
- Server checks whether the corresponding public key is listed in the target user's
~/.ssh/authorized_keys(or matches a trusted CA — certificates). - Server verifies the signature. No secret ever crosses the wire.
4. Key Management¶
4.1 Key types¶
| Type | Recommendation |
|---|---|
ed25519 |
Preferred. Small (68 bytes), fast, resistant to implementation weaknesses that have historically affected RSA/DSA. |
rsa (4096-bit minimum) |
Use only when a legacy system requires it. rsa-sha2-256/512 signature algorithms, not the deprecated ssh-rsa (SHA-1). |
ecdsa |
Avoid — NIST curve concerns and generally superseded by ed25519. |
dsa |
Removed from modern OpenSSH entirely. |
4.2 Generating keys¶
ssh-keygen -t ed25519 -C "amercer@lab-$(date +%Y%m%d)" -f ~/.ssh/id_ed25519_lab
# RSA fallback for legacy targets only
ssh-keygen -t rsa -b 4096 -C "amercer@legacy-host" -f ~/.ssh/id_rsa_legacy
Always set a passphrase on private keys at rest. Combine with ssh-agent so you're only prompted once per session rather than per connection.
4.3 ssh-agent¶
eval "$(ssh-agent -s)"
ssh-add ~/.ssh/id_ed25519_lab
ssh-add -l # list loaded keys
ssh-add -t 3600 ~/.ssh/id_ed25519_lab # auto-expire after 1h
Agent forwarding (ssh -A / ForwardAgent yes) is a real risk: a root user on the intermediate host can use your forwarded agent socket to authenticate elsewhere as you, for as long as the connection is open. Prefer ProxyJump (§5.3), which never exposes your agent socket to the intermediate host, over agent forwarding.
4.4 Deploying public keys¶
ssh-copy-id -i ~/.ssh/id_ed25519_lab.pub amercer@lab
# or manually:
cat ~/.ssh/id_ed25519_lab.pub | ssh amercer@lab 'mkdir -p ~/.ssh && chmod 700 ~/.ssh && cat >> ~/.ssh/authorized_keys && chmod 600 ~/.ssh/authorized_keys'
authorized_keys entries can be restricted per-key:
command="/usr/local/bin/backup-only.sh",no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty ssh-ed25519 AAAA... backup-key
This is how you hand out narrowly-scoped automation keys (e.g. a CI runner that can only trigger one script) without giving a full shell.
5. Client Configuration (~/.ssh/config)¶
Per-host overrides live in ~/.ssh/config (mode 600). Example, structured for a homelab with a jump host:
# Global defaults
Host *
AddKeysToAgent yes
HashKnownHosts yes
ServerAliveInterval 60
ServerAliveCountMax 3
IdentitiesOnly yes
# Jump host
Host bastion
HostName bastion.umoswg.net
User amercer
Port 22
IdentityFile ~/.ssh/id_ed25519_bastion
# Internal host reached via the bastion
Host lab
HostName 10.0.10.5
User amercer
IdentityFile ~/.ssh/id_ed25519_lab
ProxyJump bastion
ForwardAgent no
# Wildcard for a whole internal subnet
Host 10.0.10.*
User amercer
ProxyJump bastion
IdentityFile ~/.ssh/id_ed25519_lab
5.1 Key directives¶
| Directive | Purpose |
|---|---|
IdentitiesOnly yes |
Only try the explicitly listed IdentityFile(s) — prevents offering every key in the agent to every host (avoids auth-attempt lockouts and key fingerprinting by the server). |
IdentityFile |
Path to the private key for this host. |
ProxyJump (-J) |
Route through one or more intermediate hosts using native multiplexed tunneling — replaces the old ProxyCommand ... nc %h %p pattern. |
HashKnownHosts |
Stores hashed hostnames in known_hosts so a leaked file doesn't reveal your infrastructure map. |
StrictHostKeyChecking |
yes (default-safe), accept-new (auto-trust new hosts, reject changed ones — good middle ground for automation), no (dangerous, disables MITM protection). |
5.2 ProxyJump vs agent forwarding¶
ssh -J bastion lab
is functionally equivalent to nesting connections through the bastion, but the bastion never sees your private key or your agent socket — it only relays encrypted bytes. This is the correct way to reach hosts behind a jump box; avoid ForwardAgent yes unless you specifically trust the intermediate host with impersonation rights.
6. Server Configuration (sshd_config)¶
See the hardened config delivered separately. Key directive groups:
6.1 Authentication restriction¶
PermitRootLogin no
PasswordAuthentication no
KbdInteractiveAuthentication no
AuthenticationMethods publickey
MaxAuthTries 3
6.2 Access scoping¶
AllowGroups ssh-users
# or
AllowUsers amercer@10.0.0.0/24 deploy@203.0.113.10
Match blocks allow conditional overrides — a common pattern is restricting sftp-only accounts:
Match Group sftp-only
ChrootDirectory /srv/sftp/%u
ForceCommand internal-sftp
AllowTcpForwarding no
X11Forwarding no
6.3 Crypto policy¶
Ciphers, KexAlgorithms, MACs, HostKeyAlgorithms, PubkeyAcceptedAlgorithms — restrict to modern AEAD ciphers and -etm (encrypt-then-MAC) MACs only. See §13 for current recommendations.
6.4 Validating and applying changes¶
sudo sshd -t # syntax check — do this before every reload
sudo sshd -T # dump the fully-resolved effective config
sudo systemctl reload sshd
Never restart blind over your only session — reload re-reads config without dropping existing connections; keep a second session open regardless.
7. Certificate-Based Authentication¶
Raw public key distribution doesn't scale — revoking access means editing authorized_keys on every host. SSH certificates solve this the same way TLS certs do: a trusted CA signs short-lived credentials.
7.1 Generate a CA¶
ssh-keygen -t ed25519 -f ssh_user_ca -C "umoswg user CA"
ssh-keygen -t ed25519 -f ssh_host_ca -C "umoswg host CA"
7.2 Sign a user key (issued to a person, time-limited)¶
ssh-keygen -s ssh_user_ca -I "amercer-20260819" \
-n amercer,root \
-V +12h \
-O force-command="none" \
~/.ssh/id_ed25519_lab.pub
This produces id_ed25519_lab-cert.pub. -n lists valid principals (usernames the cert may log in as); -V sets an expiry window — short-lived certs remove the need for manual revocation in most cases.
7.3 Sign a host key (so clients trust hosts by CA, not TOFU)¶
ssh-keygen -s ssh_host_ca -I "lab-host-cert" -h -n lab.umoswg.net,10.0.10.5 -V +52w /etc/ssh/ssh_host_ed25519_key.pub
7.4 Server trusts the user CA¶
# sshd_config
TrustedUserCAKeys /etc/ssh/ssh_user_ca.pub
7.5 Clients trust the host CA¶
# ~/.ssh/known_hosts
@cert-authority *.umoswg.net ssh-ed25519 AAAA...hostca...
Result: no more known_hosts prompts per new host, and no more distributing individual public keys per user per host — access is granted/revoked centrally by issuing or expiring certificates.
8. Port Forwarding & Tunneling¶
| Mode | Flag | Direction |
|---|---|---|
| Local forward | ssh -L 8080:internal-host:80 bastion |
Local port → remote-reachable target, via the SSH server |
| Remote forward | ssh -R 9000:localhost:3000 public-host |
Exposes a local service on the remote side |
| Dynamic (SOCKS proxy) | ssh -D 1080 bastion |
Turns the SSH client into a SOCKS5 proxy — arbitrary outbound traffic tunneled through the server |
Server-side, restrict what's permitted:
AllowTcpForwarding local # local only, no remote/dynamic
GatewayPorts no # remote forwards bind to loopback only, not 0.0.0.0
PermitTunnel no # disable layer-2/3 tun/tap tunneling entirely
9. Connection Multiplexing¶
Reuses a single authenticated TCP connection for multiple sessions — avoids repeated handshake/auth latency:
Host *
ControlMaster auto
ControlPath ~/.ssh/sockets/%r@%h-%p
ControlPersist 10m
mkdir -p ~/.ssh/sockets && chmod 700 ~/.ssh/sockets
Subsequent ssh/scp/sftp calls to the same host reuse the existing tunnel — noticeably faster for scripted/repeated access (e.g. CI, config management, or a CLI tool making many short-lived connections).
10. Hardening Checklist¶
- [ ]
PermitRootLogin no - [ ]
PasswordAuthentication no,KbdInteractiveAuthentication no(unless intentionally using OTP via PAM) - [ ] Modern-only
Ciphers/KexAlgorithms/MACs(drop CBC ciphers, drop non-ETM MACs, dropdiffie-hellman-group1/14-sha1) - [ ]
ed25519host keys; remove DSA/ECDSA host key files - [ ]
AllowGroups/AllowUsersscoping — don't rely on "everyone with an account" implicitly having SSH access - [ ]
MaxAuthTries 3,LoginGraceTimeshort (15–30s) - [ ]
X11Forwarding nounless actually needed - [ ]
AllowAgentForwarding noby default; useProxyJumpinstead - [ ]
ClientAliveInterval/ClientAliveCountMaxset to reap dead sessions - [ ]
LogLevel VERBOSE(logs the key fingerprint used for each login — essential for audit trails) - [ ] fail2ban or equivalent in front of the daemon (see §11)
- [ ] Consider a non-default port only as noise reduction, never as a security control — it's obscurity, not defense
- [ ] Certificates (§7) instead of raw key sprawl once you're managing more than a handful of hosts/users
11. Fail2ban / Rate Limiting Integration¶
LogLevel VERBOSE (or at least INFO) is required for fail2ban's sshd jail to have anything to match against.
# /etc/fail2ban/jail.local
[sshd]
enabled = true
port = 22
filter = sshd
logpath = /var/log/auth.log
maxretry = 3
findtime = 10m
bantime = 1h
bantime.increment = true
Complement, don't replace, with sshd_config's own MaxStartups (limits concurrent unauthenticated connections — mitigates connection-flood DoS independent of fail2ban's log-based banning).
12. Logging & Auditing¶
- Auth events land in
journalctl -u sshd//var/log/auth.log(Debian/Ubuntu) or/var/log/secure(RHEL family). sshd -Tdumps the fully-resolved effective configuration — invaluable whenMatchblocks make the applied config non-obvious.- Key fingerprint per login:
LogLevel VERBOSElogs which specific key authenticated a session — critical for tracing access after a key is suspected compromised. - For centralized logging (relevant to an ELK/Vector pipeline): ship
auth.log/journalsshdunit logs via Vector or Filebeat; grep/alert onFailed publickey,Accepted publickey, andInvalid userpatterns.
13. Algorithm Reference Tables¶
Key exchange (KEX) — prefer top-down:
| Algorithm | Status |
|---|---|
[email protected] |
Post-quantum hybrid, OpenSSH 9.0+ — preferred where supported |
curve25519-sha256 / [email protected] |
Strong, fast, widely compatible |
diffie-hellman-group16-sha512 |
Acceptable fallback for legacy peers |
diffie-hellman-group14-sha256 |
Legacy-compatible minimum |
diffie-hellman-group1-sha1, group14-sha1 |
Deprecated — disable |
Ciphers — AEAD only where possible:
| Algorithm | Status |
|---|---|
[email protected] |
Preferred — software-fast, no timing side channels |
[email protected] |
Preferred, especially with AES-NI hardware |
[email protected] |
Acceptable |
aes256-ctr, aes128-ctr |
Legacy-compatible, requires separate MAC |
*-cbc (any) |
Deprecated — disable |
MACs (only relevant for non-AEAD ciphers):
| Algorithm | Status |
|---|---|
[email protected] |
Preferred |
[email protected] |
Preferred |
hmac-sha1, non--etm variants |
Deprecated — disable |
Host/public key algorithms:
| Algorithm | Status |
|---|---|
ssh-ed25519 |
Preferred |
rsa-sha2-512, rsa-sha2-256 |
Acceptable (SHA-2 RSA signatures) |
ssh-rsa (SHA-1) |
Deprecated — disable |
ssh-dss |
Removed from modern OpenSSH |
14. Troubleshooting¶
Verbose client debugging (stack up to -vvv for full KEX/auth detail):
ssh -vvv amercer@lab
Common diagnostics:
| Symptom | Likely cause |
|---|---|
Permission denied (publickey) |
Wrong key offered (IdentitiesOnly yes + explicit IdentityFile fixes ambiguity), or key not in server's authorized_keys, or wrong permissions on ~/.ssh (must be 700) / authorized_keys (600) |
Too many authentication failures |
Agent is offering every loaded key before the correct one, exceeding MaxAuthTries — set IdentitiesOnly yes |
Hangs at Connecting to... |
Firewall/security group blocking the port, or sshd not listening on the expected ListenAddress |
REMOTE HOST IDENTIFICATION HAS CHANGED |
Host key rotated (legitimate re-key, OS reinstall) or a real MITM — verify out-of-band before clearing known_hosts |
sshd won't start after config edit |
Run sudo sshd -t — it prints the exact line number of the syntax error |
| Certificate not accepted | Check -V expiry window and that -n principals match the login username; verify TrustedUserCAKeys points at the right CA pubkey |
15. Common Pitfalls¶
- Editing
sshd_configand restarting without testing first. Alwayssshd -t, always keep a second session open. - Relying on a non-standard port as a security measure. It reduces log noise from opportunistic scanners; it does nothing against a targeted attacker.
ForwardAgent yesby default in~/.ssh/configwithHost *. Scope it explicitly per-host, or avoid it entirely in favor ofProxyJump.- Reusing one key everywhere. Compromise of one host/service compromises every relationship that key participates in. Prefer per-purpose keys or certificates with scoped principals.
- Forgetting
chmod.sshdsilently ignoresauthorized_keysif directory/file permissions are too permissive (~/.sshmust not be group/world-writable). - Long-lived, unscoped certificates. Defeats the point of moving to certificate auth — keep
-Vwindows short and principals narrow.
16. Further Reading¶
man 5 sshd_config/man 5 ssh_config— authoritative, versioned to your installed OpenSSHman 1 ssh-keygen— certificate signing options (-s,-I,-n,-V,-O)- RFC 4251–4254 — SSH-2 protocol architecture, authentication, transport, connection layers
- OpenSSH release notes (
https://www.openssh.com/releasenotes.html) — track algorithm deprecations across upgrades