Configure Personal Firewall: UFW & Fail2Ban for Linux Homelab
When I first exposed a self-hosted service to the internet, I learned the hard way: a firewall isn't optional. Within hours of opening port 22 without protection, my logs showed thousands of SSH brute-force attempts. That's when I realized I needed both UFW (Uncomplicated Firewall) and Fail2Ban working together. This guide walks you through setting up both, with real commands I use in production.
Why Both UFW and Fail2Ban?
UFW is your perimeter defense—it decides which ports are open and which traffic is allowed. Fail2Ban is your active guard—it watches logs and bans IPs that show malicious behavior. I prefer this two-layer approach because UFW alone won't stop a targeted brute-force attack, and Fail2Ban without UFW leaves unnecessary ports exposed.
Think of it like this: UFW is the locked door, Fail2Ban is the security camera that calls the police when someone tries to pick the lock too many times.
Step 1: Install and Configure UFW
Most Debian and Ubuntu systems come with UFW preinstalled but disabled. Let's enable it and open only what we need.
sudo ufw status
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow 22/tcp
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw enable
This sets the foundation: deny everything coming in by default, allow everything going out (so your system can reach the internet), then explicitly allow SSH, HTTP, and HTTPS. The allow 22/tcp is critical—if you lock yourself out of SSH, you'll need physical access to recover.
If you're running a specific homelab service like Nextcloud or Jellyfin, add those ports too:
sudo ufw allow 8080/tcp
sudo ufw allow 8443/tcp
sudo ufw status numbered
The numbered output shows each rule with a number, making it easy to delete rules later if needed.
Step 2: Install and Configure Fail2Ban
Fail2Ban monitors logs and bans IPs after repeated failed login attempts. Install it first:
sudo apt-get update
sudo apt-get install fail2ban -y
Fail2Ban works by reading log files and applying firewall rules. The default SSH jail is good, but I always customize it. Create a local override file instead of editing the main config:
sudo cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.local
sudo nano /etc/fail2ban/jail.local
Find the [sshd] section and modify these values:
[sshd]
enabled = true
port = ssh
filter = sshd
logpath = /var/log/auth.log
maxretry = 5
findtime = 600
bantime = 3600
What this does: after 5 failed login attempts within 10 minutes (600 seconds), ban that IP for 1 hour (3600 seconds). I prefer being strict here—automated attackers don't care about legitimate timeouts.
Save and exit, then restart Fail2Ban:
sudo systemctl restart fail2ban
sudo fail2ban-client status sshd
You should see output like:
Status for the jail sshd:
|- Filter
| |- Currently failed: 0
| |- Total failed: 0
| `- Journal matches: 0
`- Actions
|- Currently banned: 0
|- Total banned: 0
`- Banned IP list:
sudo fail2ban-client set sshd unbanip 192.168.1.100. Replace the IP with the actual address.Step 3: Add Custom Jails for Your Services
If you're running Nextcloud, Vaultwarden, or other web apps, create jails to protect them. Here's a Nextcloud example:
sudo nano /etc/fail2ban/jail.d/nextcloud.local
Add this:
[DEFAULT]
bantime = 3600
findtime = 600
maxretry = 5
[nextcloud]
enabled = true
port = http,https
filter = nextcloud
logpath = /var/www/nextcloud/data/nextcloud.log
maxretry = 3
Then create the filter file:
sudo nano /etc/fail2ban/filter.d/nextcloud.local
Add:
[Definition]
failregex = ^.*Login attempt with non-existent user.*"uid":"".*$
^.*Auth token attempt.*"uid":"".*$
ignoreregex =
Restart Fail2Ban and verify:
sudo systemctl restart fail2ban
sudo fail2ban-client status
Step 4: Monitor and Maintain
Firewalls need attention. I check my logs weekly. View current bans:
sudo fail2ban-client status
sudo fail2ban-client status sshd
To see what Fail2Ban is catching:
sudo tail -f /var/log/fail2ban.log
This is enlightening—you'll see the attack traffic attempting everything from default usernames to exploit paths. It's a good reminder why this matters.
For UFW, check periodically:
sudo ufw status verbose
Integrating with Homelab Infrastructure
If you're running multiple services behind a reverse proxy like Caddy or Nginx Proxy Manager, your firewall should still be the outermost layer. Keep UFW rules simple at the firewall level, and let your reverse proxy handle application-specific routing.
For a small VPS hosting multiple sites (like a budget RackNerd KVM instance), you might open 80 and 443 globally, then rely on Fail2Ban to protect SSH and any admin interfaces running on unusual ports.
Quick Security Checklist
- Change default SSH port? Optional but useful. If you do, update UFW and Fail2Ban accordingly.
- Disable root SSH login? Edit
/etc/ssh/sshd_config, setPermitRootLogin no, thensudo systemctl restart ssh. - Use SSH keys instead of passwords? Highly recommended. Fail2Ban won't help if you're using weak passwords.
- Log rotation? Fail2Ban log files grow over time. Check
/etc/logrotate.d/fail2banis configured.
Wrapping Up
UFW + Fail2Ban is a proven, lightweight approach to firewall hardening. I've been using this exact setup across a dozen self-hosted machines for three years without a single compromise. The key is that UFW handles the static policy (what's allowed), while Fail2Ban handles dynamic threats (who's trying to abuse what's allowed).
Next steps: enable these services, set them to start on boot (they do by default), and check your logs regularly. Your homelab will be significantly safer for a few minutes of setup.