Security Basics: ufw Firewall, fail2ban and Server Hardening

Advanced
14 min

Security Basics: ufw Firewall, fail2ban and Server Hardening

A server with a public IP receives its first login attempts within minutes of booting. Most attacks are automated and unsophisticated, which means a short list of standard measures stops nearly all of them: a firewall that exposes only the ports you serve, SSH that accepts keys only, a tool that bans repeat offenders, automatic security updates, and services that run as unprivileged users. This lesson walks through each in the order you would apply them to a fresh machine.

Principle: Reduce the Attack Surface

Every listening port, every enabled service and every account is a way in. Start by seeing what you have:

bash
sudo ss -tulpn # listening ports systemctl list-units --type=service --state=running awk -F: '$7 !~ /nologin|false/ {print $1}' /etc/passwd # accounts that can log in

Stop and disable anything the server does not need, and bind internal services (databases, caches, admin dashboards) to 127.0.0.1 so they are unreachable from outside even before the firewall.

ufw: The Uncomplicated Firewall

ufw is a front end for the kernel's netfilter (nftables/iptables) that ships with Ubuntu and Debian. The strategy is deny inbound by default, allow specific services.

bash
sudo ufw default deny incoming sudo ufw default allow outgoing sudo ufw allow OpenSSH # or: sudo ufw allow 22/tcp — do this BEFORE enabling sudo ufw allow 80/tcp sudo ufw allow 443/tcp sudo ufw enable sudo ufw status numbered
bash
Status: active To Action From -- ------ ---- [ 1] OpenSSH ALLOW IN Anywhere [ 2] 80/tcp ALLOW IN Anywhere [ 3] 443/tcp ALLOW IN Anywhere

More precise rules:

bash
sudo ufw allow from 203.0.113.0/24 to any port 22 proto tcp # SSH only from the office sudo ufw allow from 10.0.0.0/8 to any port 5432 # database from the private network sudo ufw limit OpenSSH # rate-limit: 6 connections per 30 s per IP sudo ufw deny from 198.51.100.7 # block one address sudo ufw delete 3 # by number from status numbered

Allow SSH before running ufw enable on a remote machine; enabling a default-deny firewall without it locks you out. On RHEL-family systems the equivalent is firewalld:

bash
sudo firewall-cmd --permanent --add-service=http sudo firewall-cmd --permanent --add-port=443/tcp sudo firewall-cmd --reload sudo firewall-cmd --list-all

fail2ban: Ban Repeat Offenders

fail2ban watches log files for failed logins and adds temporary firewall rules against the source IP. The SSH jail is the essential one; jails exist for nginx, postfix and many others.

bash
sudo apt install fail2ban sudo cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.local # never edit jail.conf directly
bash
# /etc/fail2ban/jail.local (relevant parts) [DEFAULT] bantime = 1h findtime = 10m maxretry = 5 ignoreip = 127.0.0.1/8 203.0.113.0/24 # never ban yourself [sshd] enabled = true [nginx-http-auth] enabled = true
bash
sudo systemctl enable --now fail2ban sudo fail2ban-client status # list jails sudo fail2ban-client status sshd # banned IPs and counts sudo fail2ban-client set sshd unbanip 198.51.100.7 sudo tail -f /var/log/fail2ban.log

On systemd-only systems where /var/log/auth.log does not exist, set backend = systemd in [DEFAULT] so fail2ban reads the journal.

Automatic Security Updates

Unpatched software is the second most common way in after weak credentials. Debian and Ubuntu ship unattended-upgrades; RHEL-family uses dnf-automatic.

bash
sudo apt install unattended-upgrades sudo dpkg-reconfigure -plow unattended-upgrades # answer Yes cat /etc/apt/apt.conf.d/20auto-upgrades # should show both lines set to "1" sudo unattended-upgrade --dry-run --debug # test
bash
sudo dnf install dnf-automatic sudo systemctl enable --now dnf-automatic-install.timer

Kernel updates still need a reboot; needrestart or /var/run/reboot-required tells you when.

Users, sudo and SSH

  • Create a personal account, add it to sudo, and never log in as root: adduser alice && usermod -aG sudo alice.
  • Require keys and disable passwords and root login in sshd_config (previous SSH lesson).
  • Give services their own system users with no shell: useradd --system --shell /usr/sbin/nologin api.
  • Review who can sudo: getent group sudo and sudo -l.
  • Check for password-less sudo entries in /etc/sudoers.d/ that were added for convenience and forgotten.

File and Service Hardening

bash
sudo find / -type f -perm -4000 2>/dev/null # setuid binaries; know what is on the list sudo find / -xdev -type f -perm -o+w 2>/dev/null # world-writable files sudo chmod 600 /etc/app/env # secrets readable by owner only sudo systemd-analyze security api # sandboxing score for a unit

Common Mistakes

  • Enabling ufw over SSH without allowing port 22 first.
  • Editing jail.conf instead of jail.local, then losing changes on the next package update.
  • Forgetting ignoreip, banning your own office, and blaming the server.
  • Trusting the cloud security group alone and leaving ufw inactive.
  • Treating a changed SSH port as security.
Quick Quiz
Question 1 of 3

What must you do before running `sudo ufw enable` on a remote server?

Key Takeaways

  • Minimise exposure: stop unneeded services, bind internal ones to localhost, check ss -tulpn.
  • ufw default deny incoming, allow SSH (with limit) before enable, then only served ports; firewalld on RHEL.
  • fail2ban with the sshd jail bans brute-force sources; configure in jail.local and set ignoreip.
  • Enable unattended-upgrades or dnf-automatic; reboot for kernel updates.
  • Key-only SSH, per-service users, 600 secrets and systemd-analyze security complete the baseline.

Next lesson: Linux for Web Developers: Deploying a Node.js App with Nginx — put everything together to serve a real application over HTTPS.

Security Basics: ufw Firewall, fail2ban and Server Hardening - Linux & Command Line | CodeYourCraft | CodeYourCraft