Hardening Basics
A short, high-value checklist for securing a freshly provisioned Linux machine before exposing it to the internet - not exhaustive, but covers the changes that matter most for the least effort.
This isn't a complete security guide - it's the small set of changes worth making on essentially every internet-facing machine, gathered in one place with links to the fuller explanation of each.
1. Disable password SSH login, use keys instead
Password authentication is exposed to brute-force attempts from anywhere on the internet the moment SSH is reachable. Set up key-based authentication first, confirm it works, then disable passwords:
PasswordAuthentication no
PermitRootLogin no
in /etc/ssh/sshd_config, followed by sudo systemctl reload sshd. See
SSH for the rest of the server config.
2. Keep packages up to date
Most real-world compromises exploit a known, already-patched vulnerability - not a novel attack. Enable automatic security updates rather than relying on remembering to run updates manually:
sudo apt install unattended-upgrades # Debian/Ubuntu
sudo dnf install dnf-automatic # Fedora/RHEL
See APT and dpkg and DNF and RPM for the manual update commands these automate.
3. Run a firewall, default-deny
Only open ports something is actually meant to be listening on:
sudo ufw default deny incoming
sudo ufw allow ssh
sudo ufw allow 443/tcp
sudo ufw enable
See Firewalls: iptables and nftables
for the underlying mechanism and the firewalld equivalent.
4. Rate-limit or ban repeated failed logins
fail2ban watches log files for repeated authentication failures (SSH,
web app logins, anything with a parseable log format) and temporarily
firewalls the offending address:
sudo apt install fail2ban
sudo systemctl enable --now fail2ban
Meaningfully reduces the noise and risk from automated brute-force scanning, on top of - not instead of - disabling password auth entirely.
5. Don't run services as root that don't need to be
Most well-packaged services (nginx, PostgreSQL, etc.) already drop to a dedicated unprivileged user after binding to any low-numbered ports they need - verify this rather than assuming, especially for anything you've built or configured yourself:
ps -eo user,comm | grep nginx
root nginx
www-data nginx
The root line here is normal - it's the master process, which needs
root briefly to bind port 443; the actual worker processes handling
traffic run as www-data. A service where every process runs as root
is worth double-checking.
6. Disable services you're not using
Every running service is attack surface. List what's enabled and turn off anything unnecessary:
systemctl list-unit-files --state=enabled
sudo systemctl disable --now avahi-daemon
See systemd and Services.
7. Check for setuid binaries you didn't expect
Setuid binaries run with their owner's privileges regardless of who executes them - a small, security-relevant set on any system, worth knowing what's actually there:
find / -xdev -perm -4000 -type f 2>/dev/null
See File Permissions and Ownership for what the setuid bit does.
8. Know that SELinux / AppArmor exist
Beyond standard user/group permissions, RHEL/Fedora ship SELinux and
Debian/Ubuntu ship AppArmor - mandatory access control systems that
confine what a process can do even if it's compromised or misconfigured,
beyond what its Unix permissions alone would prevent. Both are enabled by
default on their respective distros; disabling either to "fix" a
permission problem (a common but poor troubleshooting shortcut) removes a
real layer of defense - check sudo sealert or journalctl for denials
and fix the underlying policy instead, where practical.
What this list deliberately leaves out
Full security hardening also covers things like kernel parameter tuning, intrusion detection, TLS configuration, and application-level security - this page is the baseline that applies almost universally, not a complete guide for any specific threat model.