<aside> 🔐
Module 15 — SSH and Security Hardening
Every server you will ever manage, you reach through SSH. It is the front door — which makes it the single most-attacked service on any internet-facing Linux box. This module teaches SSH from the cryptography up: what actually happens in the handshake, why key-based login is both easier and safer than passwords, and then the concrete, ordered steps to harden a server so that front door holds. This is the module that turns "I can log in" into "I can secure a fleet."
Legend used throughout: 🧠 concept → 🧩 analogy → 🧪 exercise → ✅ expected result (hidden) → 🎯 interview questions (hidden)
</aside>
<aside> 🚦
Before you start
You need Module 4 — Users, Groups, and Permissions (permissions — SSH lives or dies on ~/.ssh being 700 and authorized_keys being 600), Module 11 — systemd and Services (systemd — sshd is a service you reload, and you read its logs with journalctl), and Module 13 — Networking (networking — the firewall that fronts port 22). Tools: an Ubuntu 24.04 machine with OpenSSH (sudo apt install openssh-client openssh-server). Almost everything here is safe and local — we generate keys in a throwaway directory and validate config files without ever applying them. The one rule that matters: never change SSH auth on a remote box without a second session already open.
</aside>
<aside> ℹ️
This page uses a Mermaid diagram block. Notion shows them as code by default — click the block and set it to Preview to see the diagram. This reminder appears once per page.
</aside>
<aside> 📖
Official docs: ssh(1) · OpenSSH project
</aside>
🧠 SSH — Secure Shell — is an encrypted protocol for running a shell (and copying files, and forwarding ports) on a remote machine over an untrusted network. It replaced telnet and rlogin, which sent everything — including your password — as plaintext anyone on the wire could read. SSH gives you three guarantees at once: confidentiality (the session is encrypted), integrity (tampering is detected), and authentication in both directions — the server proves it is the machine you meant to reach (host keys, A2), and you prove you are who you claim (a password or, better, a key, Part B).
🧠 The handshake, in plain terms: your client opens a TCP connection to port 22; the two sides negotiate algorithms they both support; they run a key exchange (Diffie-Hellman) that lets them agree on a shared secret without ever sending it — so even someone recording the whole conversation can't derive it; the server authenticates itself with its host key; and finally you authenticate. From that point the whole session is protected with fast symmetric encryption using the freshly-agreed key.
<aside> 🧠
Counter-intuitive: SSH uses both asymmetric and symmetric encryption — and the slow asymmetric part is only used to bootstrap. People assume "SSH = public-key crypto." Public-key (asymmetric) math is expensive — hundreds of times slower than symmetric ciphers. So SSH uses asymmetric crypto for exactly two jobs at the start: agreeing on a shared secret (key exchange) and proving identities. The instant that's done, it switches to a symmetric cipher (like AES or ChaCha20) with the shared key for every byte of the actual session, because symmetric is fast enough to encrypt a live terminal or a multi-gigabyte file transfer without you noticing. Understanding this split — asymmetric to establish trust, symmetric to do the work — is the single idea behind almost every secure protocol, TLS/HTTPS included.
</aside>
<aside> 🧩
Real-world analogy — meeting a stranger to exchange a one-time codebook
Two spies who've never met need to talk secretly in a crowded room. Using slow, elaborate public-key signatures they first verify each other's identity and agree — out loud, but in a way eavesdroppers can't reuse — on a shared codebook. That setup is fiddly and slow, so they do it once. From then on they just speak using the codebook: fast, and meaningless to everyone listening. The slow ceremony bootstraps trust; the fast codebook carries the conversation.
Where the analogy stops working. Real spies could be fooled by someone impersonating the other spy — the whole hard problem SSH solves with host keys (A2) is making that impersonation detectable. And the "codebook" isn't handed over at all: Diffie-Hellman lets both sides derive the same secret independently from public exchanges, so there's never a moment where the shared key exists on the wire to be stolen. The analogy's single shared object is exactly the thing SSH is careful never to transmit.
</aside>
🧪 Exercise A1.1 — Look at your SSH client and its effective config (read-only, safe anywhere)
ssh -V # which OpenSSH version (and OpenSSL) you have
ls -la /etc/ssh/ # the system SSH files: client config, sshd config, host keys
ssh -G github.com | grep -E '^(user|hostname|port|identityfile)' # key lines of the EFFECTIVE client config
known_hosts — trust on first use<aside> 📖
Official docs: ssh(1) — VERIFYING HOST KEYS · sshd(8)
</aside>
🧠 When a server is installed, it generates host keys — a keypair that identifies the machine itself (separate from any user's key). On your first connection, ssh shows you the server's key fingerprint and asks "are you sure?" You say yes, and ssh records that key in ~/.ssh/known_hosts. On every future connection it checks the server's presented key against that stored copy. If they match, silence — you're talking to the same machine. This model is called TOFU: Trust On First Use. You take one leap of faith at the start, and SSH guarantees consistency ever after.
🧠 If the stored key and the presented key don't match, ssh refuses to connect and prints a loud banner: WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED! with Someone could be eavesdropping on you right now (man-in-the-middle attack)! This means one of two things: something malicious is intercepting your connection, or — far more often — the server was legitimately rebuilt, reinstalled, or replaced (new machine, new host key). Knowing which is a judgement call, and it's exactly the kind of thing an interviewer probes.
<aside> 🚨
Trap — "just turn off the host-key check to make the error go away." The internet is full of advice to set StrictHostKeyChecking=no or to blindly ssh-keygen -R host and reconnect whenever the warning appears. Doing that reflexively throws away the entire point of host keys — you've turned SSH's man-in-the-middle protection off and trained yourself to ignore the one warning that catches an active attack. The correct response is to verify the new fingerprint out of band (from the cloud console, the provisioning logs, or a colleague) and only then remove the stale entry with ssh-keygen -R hostname. StrictHostKeyChecking=accept-new is a defensible middle ground for throwaway hosts; =no on anything you care about is not.
</aside>
<aside> 🧩
Real-world analogy — recognising a friend's voice on the phone
The first time a friend calls you from a new number, you can't be sure it's them — until they say something only they'd know, and you save the number under their name. Every call after that, you recognise the number and the voice instantly; if one day "their" number calls but the voice is a stranger's, alarm bells ring. That's TOFU: one careful first verification, then effortless recognition, with a loud alert the moment something doesn't match.
Where the analogy stops working. A human can be fooled by a good impressionist; a host key cannot be faked without stealing the server's private key, which never leaves the server. And your phone's memory of the voice is fuzzy and forgiving, whereas known_hosts is a byte-exact cryptographic match — it will not "mostly recognise" a key. The human version degrades gracefully; the SSH version is deliberately all-or-nothing, because a partial match is exactly what an attacker would produce.
</aside>