"Hardening" is the catch-all term for making a fresh system harder to break into. Most of the work is small, well-known steps — none of it is wizardry. This guide walks through them in plain language, with what each one buys you and why.
The minimal Debian server checklist covers the basics (sudo user, key-only SSH, firewall, automatic updates). This guide goes one step deeper: lock down SSH further, add intrusion protection, audit what's exposed, and tighten a couple of kernel defaults.
Why bother?
If your machine is on the internet, automated scanners are trying to break in right now — guessing passwords, probing for vulnerable services, looking for known exploits. Hardening doesn't make a server invincible; it raises the bar high enough that the casual attackers move on.
Step 1: tighten SSH a little further
Already moved from password to key login (see the minimal checklist)? Two more things worth doing:
Move SSH off port 22 (optional, mostly stops log noise)
sudo nano /etc/ssh/sshd_config
Find/uncomment the Port line and pick something high:
Port 49222
Open the new port in the firewall before reloading SSH, or you'll lock yourself out:
sudo ufw allow 49222/tcp
sudo systemctl reload sshd
Test in a new terminal: ssh -p 49222 alice@server. If that works, drop the old rule: sudo ufw delete allow OpenSSH.
Note: changing the port doesn't stop a targeted attacker — port scans are cheap. It does cut the noise in your auth log dramatically.
Restrict who can SSH in at all
In sshd_config:
AllowUsers alice
# or
AllowGroups ssh-users
Then only alice (or members of ssh-users) can log in via SSH. Service accounts and other system users are silently refused.
Step 2: install fail2ban
fail2ban watches the auth log, and when it sees too many failures from one IP it blocks that IP for a while.
sudo apt install fail2ban
Defaults out of the box already protect SSH. To verify it's active:
sudo fail2ban-client status
sudo fail2ban-client status sshd
Want to tweak the policy? Don't edit /etc/fail2ban/jail.conf directly (it'll be overwritten on update). Create an override:
sudo nano /etc/fail2ban/jail.local
[DEFAULT]
bantime = 1h
findtime = 10m
maxretry = 5
[sshd]
enabled = true
"5 failures in 10 minutes = banned for 1 hour." Restart fail2ban to apply:
sudo systemctl restart fail2ban
Step 3: audit what's listening
The fewer services you expose, the smaller the attack surface. Look at what's listening:
sudo ss -tulpn
Each line shows: protocol, address it's bound to, port, and the program. Anything you don't recognise deserves a closer look. The address column is important:
0.0.0.0:portor*:port— listening on every interface (reachable from the internet).127.0.0.1:port— listening only on localhost (safe — only the local machine can reach it).[::]:port— same as above but IPv6.
If you find something exposed to 0.0.0.0 that should only be local (a database, a debug interface), bind it to 127.0.0.1 in its config — usually a one-line change like listen 127.0.0.1:5432.
Step 4: a small set of kernel hardening tweaks
A handful of sysctl values are widely-recommended and have basically no downside:
sudo nano /etc/sysctl.d/99-hardening.conf
# ignore broadcast pings (don't amplify smurf attacks)
net.ipv4.icmp_echo_ignore_broadcasts = 1
# don't accept source-routed packets
net.ipv4.conf.all.accept_source_route = 0
net.ipv6.conf.all.accept_source_route = 0
# log packets with impossible source addresses
net.ipv4.conf.all.log_martians = 1
# disable IPv6 router advertisements on a server (you set static routes anyway)
net.ipv6.conf.all.accept_ra = 0
# stronger reverse-path filter
net.ipv4.conf.all.rp_filter = 1
# restrict the kernel log (dmesg) to root
kernel.dmesg_restrict = 1
Apply without rebooting:
sudo sysctl --system
Step 5: keep a check on what changed
Install debsums to verify that no installed package's files have been quietly modified:
sudo apt install debsums
sudo debsums -s
-s means "silent" — only prints anything if a file differs from what the package shipped. Run it occasionally; non-empty output is worth investigating.
For deeper file-integrity monitoring (which can email you when anything in /etc changes), AIDE is the classic tool:
sudo apt install aide
sudo aideinit # build the initial database
# then schedule `aide --check` from cron and compare reports
AIDE is more involved — only set it up if you're ready to live with the daily report email.
Step 6: optional, real benefit — disable USB/Firewire if you don't use them
On a server, you almost never need USB storage or Firewire. Stop the kernel from loading those modules at all:
sudo nano /etc/modprobe.d/disable-usb-storage.conf
blacklist usb-storage
blacklist firewire-core
Reboot. A "rubber ducky" plugged into the back of the server now does nothing.
Step 7: AppArmor (already on, leave it on)
Debian ships AppArmor enabled. It's a kernel module that restricts what individual programs can do (which files they can read, which network they can use), based on profiles shipped by packages. You don't need to do anything — just verify it's active:
sudo aa-status
Should report "apparmor module is loaded" and list any active profiles. If you ever see a guide telling you to disable AppArmor to "fix" a problem, ignore it — fix the profile, don't remove the protection.
What this gives you
After this checklist, your Debian box:
- only accepts SSH from known keys, only from a small set of users, ideally on a non-default port
- bans IPs that hammer SSH
- exposes only the services you intend to expose
- has a small set of kernel knobs flipped for safer networking
- lets you spot if any installed file has been tampered with
- keeps AppArmor sandboxing in place
None of this is sufficient on its own. But together, it makes a server significantly less interesting to opportunistic attackers — and that's most of what hardening is.
What this does NOT cover
Application-level security (your web app, your database config), full-disk encryption on a remote server (see changing the LUKS passphrase), 2FA for SSH, web-application firewalls, and intrusion detection at scale. All worth learning about when you're ready.