Lesson 2.3 · Hardening as a Habit
This is the most important lesson in the module, and it introduces a habit you’ll keep for the rest of the curriculum and your career: every server gets hardened on day one. Not “later, once it’s working” — hardening is part of setting it up. A fresh internet-connected server is probed by automated attackers within minutes; the controls in this lesson are what stand between “a box I built” and “a box I’d trust with something real.”
We’ll apply five layers. None is complicated. The discipline is doing all of them, every time, by reflex.
Layer 1 · SSH: keys only, no passwords, no root
Section titled “Layer 1 · SSH: keys only, no passwords, no root”SSH is your server’s front door and the service most relentlessly attacked. You already log in with a key (Lesson 0.2); now you’ll disable password login entirely, so brute-forcing a password becomes impossible — there’s nothing to brute-force.
Edit the SSH server config:
sudo vim /etc/ssh/sshd_config # your Lesson 0.3 vim skills, on a real configSet these directives (they may exist commented-out; uncomment and set them):
PermitRootLogin noPasswordAuthentication noPubkeyAuthentication yesPasswordAuthentication no— the big one. Only key holders can log in.PermitRootLogin no— nobody logs in directly as root; they log in as a user and usesudo(Lesson 2.2). Attackers always tryrootfirst; this closes it.
Apply the change by restarting SSH, and test in a new terminal before closing your safety session:
sudo systemctl restart ssh# In a NEW terminal, confirm you can still get in:ssh homelab# And confirm passwords are refused (this should FAIL for a password-only attempt):ssh -o PubkeyAuthentication=no homelab # expect: Permission deniedChanging the SSH port (e.g. off 22) is sometimes suggested. It’s mild “security through obscurity” — it cuts log noise from dumb bots but stops no real attacker. Key-only auth is the control that matters. Move the port if you like the quieter logs; don’t mistake it for security.
Layer 2 · A firewall that default-denies
Section titled “Layer 2 · A firewall that default-denies”A firewall controls which network connections the server accepts. The right posture for a server is default deny inbound: block everything, then explicitly allow only the ports for services you actually run. This is the same segmentation logic you’ll apply to your whole network in Module 3, here at the single-host level.
Debian’s friendly firewall front-end is ufw (Uncomplicated Firewall):
sudo apt install ufwsudo ufw default deny incoming # block all inbound by defaultsudo ufw default allow outgoing # let the server reach out (updates, etc.)sudo ufw allow ssh # allow SSH — or you'll lock yourself out!sudo ufw enable # turn it onsudo ufw status verbose # see the rulesAs you add services in later modules, you allow their ports the same way — ufw allow 80,
ufw allow 443 for a web server — and only those. Everything else stays denied. When you run
nmap against the server later, the only open ports should be ones you can name and justify.
Layer 3 · fail2ban: mute the noise
Section titled “Layer 3 · fail2ban: mute the noise”Even with password auth disabled, your logs will fill with attempted logins from bots. fail2ban watches the logs and temporarily bans IP addresses that misbehave (e.g. many failed SSH attempts):
sudo apt install fail2bansudo systemctl enable --now fail2bansudo fail2ban-client status # see active jailssudo fail2ban-client status sshd # see banned IPs for SSHWith key-only auth, fail2ban is less about stopping a breach (there’s no password to guess) and more about cutting log noise and slowing broad scans. It’s cheap insurance and a good habit. You’ll get real value from watching its logs — seeing thousands of blocked attempts is a visceral lesson in why internet-facing hardening matters.
Layer 4 · Automatic security updates
Section titled “Layer 4 · Automatic security updates”The single most effective security control is staying patched — most breaches exploit known
vulnerabilities that already had fixes available. You update manually with apt (Lesson 2.2),
but you shouldn’t rely on remembering. Set security updates to install themselves:
sudo apt install unattended-upgradessudo dpkg-reconfigure --priority=low unattended-upgrades # answer "Yes" to enableThis installs security patches automatically. You’ll still run apt upgrade yourself for
everything else, but the critical security fixes land without you thinking about it. Combined
with the package manager’s coverage (Lesson 2.2), this closes the most common door attackers
walk through.
Layer 5 · A locked-down admin account
Section titled “Layer 5 · A locked-down admin account”You already did most of this at install time; confirm it’s right:
- root’s password login is locked — you use a named user +
sudo, not direct root (Lesson 2.2). Verify no one canssh root@...(Layer 1 handles this). - Your admin user has a strong password for
sudo(the key logs you in; the password authorizessudo). - Only intended users are in the
sudogroup (getent group sudolists them).
That’s least privilege at the account level, reinforcing the SSH and firewall layers above.
Verify your work — hardening you don’t test isn’t real
Section titled “Verify your work — hardening you don’t test isn’t real”Hardening is only real if you confirm it. From your laptop (not the server), scan the server
the way an outsider would, using nmap (install it with your package manager if needed):
nmap -Pn homelab # what ports are open to me?You should see only the ports you deliberately allowed — for now, just SSH (22). If you see anything else, you have a service listening you didn’t intend, or a firewall rule missing. Track it down. This scan is the proof, and it’s part of your Lab 3 deliverable. In Module 8 you’ll scan far more aggressively and against your whole network — this is the gentle first version of a habit that becomes central to the security track.
The hardening checklist (your day-one reflex)
Section titled “The hardening checklist (your day-one reflex)”Copy this into your build log and run it against every server you build from now on:
- System fully updated (
apt update && apt upgrade) - SSH:
PasswordAuthentication no,PermitRootLogin no, key login confirmed working - Firewall: default deny inbound, SSH explicitly allowed, enabled
- fail2ban installed and running
- Automatic security updates enabled
- root login locked; admin via named user +
sudo; sudo group membership reviewed -
nmapfrom another machine shows only intended ports
Quick self-check
Section titled “Quick self-check”- What’s the single most important reason to disable password authentication for SSH?
- What must you verify before setting
PasswordAuthentication no, and why? - In what order do you configure ufw’s rules to avoid locking yourself out?
- With key-only auth already in place, what does fail2ban actually buy you?
- Why are automatic security updates worth enabling even though you also update by hand?
- How do you prove your hardening worked, and from where do you run that check?