Security intermediate ⏱ 60 minutes

Firewall Hardening: UFW, iptables, and firewalld Guide

Deep dive into Linux firewalls for homelabs. Configure UFW, iptables, and firewalld for maximum security.

By homelabbing.io • 2025-12-17
#firewall#ufw#iptables#firewalld#hardening

A properly configured firewall is your homelab’s first line of defense against unauthorized access. This comprehensive guide covers three major Linux firewall solutions: UFW (Uncomplicated Firewall), iptables, and firewalld. You’ll learn how to implement defense-in-depth strategies, create advanced rules, and harden your network security.

Why Firewall Hardening Matters

Your homelab is constantly probed by automated scanners, bots, and potential attackers. Without proper firewall configuration, exposed services become easy targets. A hardened firewall provides:

  • Network segmentation: Isolate services and limit lateral movement
  • Attack surface reduction: Only expose necessary ports
  • Traffic filtering: Block malicious traffic patterns
  • Logging and monitoring: Track connection attempts
  • Defense in depth: Multiple security layers working together

Understanding Linux Firewall Architecture

All three tools ultimately configure the Linux kernel’s netfilter framework, but they differ in approach:

  • iptables: Low-level, maximum control, steep learning curve
  • UFW: High-level, Ubuntu/Debian friendly, simple syntax
  • firewalld: Zone-based, dynamic updates, RHEL/Fedora default

Choose based on your distribution and complexity needs. Many homelabbers start with UFW and graduate to iptables for advanced scenarios.

UFW: Uncomplicated Firewall

Installation and Initial Setup

Ubuntu/Debian:

sudo apt update
sudo apt install ufw

Initial Configuration:

# Check status (should be inactive initially)
sudo ufw status verbose

# Set default policies (deny incoming, allow outgoing)
sudo ufw default deny incoming
sudo ufw default allow outgoing

# CRITICAL: Allow SSH before enabling to prevent lockout
sudo ufw allow ssh
# Or specific port: sudo ufw allow 22/tcp

# Enable firewall
sudo ufw enable

# Verify status
sudo ufw status numbered

Essential UFW Rules

Allow specific services:

# Web services
sudo ufw allow http
sudo ufw allow https
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp

# Custom application ports
sudo ufw allow 8080/tcp comment 'Home Assistant'
sudo ufw allow 9000/tcp comment 'Portainer'
sudo ufw allow 32400/tcp comment 'Plex'

# Allow from specific IP
sudo ufw allow from 192.168.1.100 to any port 22
sudo ufw allow from 192.168.1.0/24 to any port 445 comment 'SMB local network'

# Allow port range
sudo ufw allow 51000:51999/tcp comment 'Transmission'

Advanced UFW Rules:

# Limit SSH to prevent brute force (max 6 connections per 30 seconds)
sudo ufw limit ssh

# Allow from specific interface
sudo ufw allow in on eth1 to any port 3306 comment 'MySQL on internal network'

# Deny specific IP
sudo ufw deny from 203.0.113.50
sudo ufw reject from 198.51.100.0/24

# Allow specific application profile
sudo ufw app list
sudo ufw allow 'Nginx Full'

# Delete rules by number
sudo ufw status numbered
sudo ufw delete 5

# Delete by specification
sudo ufw delete allow 80/tcp

UFW Application Profiles

Create custom application profiles in /etc/ufw/applications.d/:

Example: /etc/ufw/applications.d/homelab

[Jellyfin]
title=Jellyfin Media Server
description=Media streaming server
ports=8096/tcp

[Home Assistant]
title=Home Assistant
description=Home automation platform
ports=8123/tcp

[Nextcloud]
title=Nextcloud
description=Self-hosted cloud storage
ports=443/tcp

[Minecraft]
title=Minecraft Server
description=Minecraft game server
ports=25565/tcp|25565/udp

Use application profiles:

# Reload application profiles
sudo ufw app update Jellyfin

# Allow application
sudo ufw allow Jellyfin

Advanced UFW Configuration

Edit /etc/ufw/before.rules for custom iptables rules:

# Allow all traffic on loopback
-A ufw-before-input -i lo -j ACCEPT
-A ufw-before-output -o lo -j ACCEPT

# Allow established connections
-A ufw-before-input -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT

# Drop invalid packets
-A ufw-before-input -m conntrack --ctstate INVALID -j DROP

# Allow ICMP (ping) with rate limiting
-A ufw-before-input -p icmp --icmp-type echo-request -m limit --limit 1/s -j ACCEPT

# Log dropped packets
-A ufw-before-input -j LOG --log-prefix "[UFW BLOCK] " --log-level 4

Enable logging:

# Enable logging
sudo ufw logging on

# Set log level (low, medium, high, full)
sudo ufw logging medium

# View logs
sudo tail -f /var/log/ufw.log
sudo journalctl -u ufw -f

iptables: Maximum Control

Understanding iptables Tables and Chains

iptables organizes rules into tables, each containing chains:

Tables:

  • filter: Default table for packet filtering (INPUT, OUTPUT, FORWARD)
  • nat: Network Address Translation (PREROUTING, POSTROUTING, OUTPUT)
  • mangle: Packet alteration (all chains)
  • raw: Connection tracking exemption (PREROUTING, OUTPUT)

Chains:

  • INPUT: Incoming packets destined for local system
  • OUTPUT: Locally generated packets going out
  • FORWARD: Packets routed through the system
  • PREROUTING: Packets before routing decision
  • POSTROUTING: Packets after routing decision

Basic iptables Setup

Check current rules:

# List all rules
sudo iptables -L -v -n

# List with line numbers
sudo iptables -L --line-numbers

# List specific table
sudo iptables -t nat -L -v -n

Flush existing rules (CAREFUL - may lock you out):

# Flush all rules
sudo iptables -F

# Flush specific chain
sudo iptables -F INPUT

# Delete all chains
sudo iptables -X

Set default policies:

# Accept loopback traffic
sudo iptables -A INPUT -i lo -j ACCEPT
sudo iptables -A OUTPUT -o lo -j ACCEPT

# Allow established connections
sudo iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT

# Set default policies
sudo iptables -P INPUT DROP
sudo iptables -P FORWARD DROP
sudo iptables -P OUTPUT ACCEPT

Essential iptables Rules

Allow SSH with rate limiting:

# SSH with connection rate limiting
sudo iptables -A INPUT -p tcp --dport 22 -m conntrack --ctstate NEW -m recent --set
sudo iptables -A INPUT -p tcp --dport 22 -m conntrack --ctstate NEW -m recent --update --seconds 60 --hitcount 4 -j DROP
sudo iptables -A INPUT -p tcp --dport 22 -j ACCEPT

Allow web services:

# HTTP and HTTPS
sudo iptables -A INPUT -p tcp --dport 80 -j ACCEPT
sudo iptables -A INPUT -p tcp --dport 443 -j ACCEPT

# Allow from local network only
sudo iptables -A INPUT -p tcp -s 192.168.1.0/24 --dport 8080 -j ACCEPT

Port forwarding (NAT):

# Forward external port 8080 to internal server port 80
sudo iptables -t nat -A PREROUTING -p tcp --dport 8080 -j DNAT --to-destination 192.168.1.100:80

# Enable masquerading for outbound traffic
sudo iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE

Advanced filtering:

# Drop invalid packets
sudo iptables -A INPUT -m conntrack --ctstate INVALID -j DROP

# Block specific IP
sudo iptables -A INPUT -s 203.0.113.50 -j DROP

# Block IP range
sudo iptables -A INPUT -s 198.51.100.0/24 -j DROP

# Allow ping with rate limit
sudo iptables -A INPUT -p icmp --icmp-type echo-request -m limit --limit 1/s -j ACCEPT
sudo iptables -A INPUT -p icmp --icmp-type echo-request -j DROP

# Log dropped packets
sudo iptables -A INPUT -m limit --limit 5/min -j LOG --log-prefix "iptables_INPUT_denied: " --log-level 7
sudo iptables -A INPUT -j DROP

Port Knocking with iptables

Port knocking adds a secret knock sequence before opening ports:

# Create port knocking chains
sudo iptables -N KNOCKING
sudo iptables -N GATE1
sudo iptables -N GATE2
sudo iptables -N GATE3

# Knock sequence: 7000, 8000, 9000
sudo iptables -A INPUT -p tcp --dport 22 -m conntrack --ctstate NEW -j KNOCKING

sudo iptables -A KNOCKING -m recent --name SSH --remove
sudo iptables -A KNOCKING -m recent --name GATE3 --rcheck --seconds 5 -j ACCEPT
sudo iptables -A KNOCKING -m recent --name GATE3 --remove
sudo iptables -A KNOCKING -j DROP

sudo iptables -A INPUT -p tcp --dport 7000 -m conntrack --ctstate NEW -j GATE1
sudo iptables -A GATE1 -m recent --name GATE1 --set
sudo iptables -A GATE1 -j DROP

sudo iptables -A INPUT -p tcp --dport 8000 -m conntrack --ctstate NEW -m recent --name GATE1 --rcheck --seconds 5 -j GATE2
sudo iptables -A GATE2 -m recent --name GATE1 --remove
sudo iptables -A GATE2 -m recent --name GATE2 --set
sudo iptables -A GATE2 -j DROP

sudo iptables -A INPUT -p tcp --dport 9000 -m conntrack --ctstate NEW -m recent --name GATE2 --rcheck --seconds 5 -j GATE3
sudo iptables -A GATE3 -m recent --name GATE2 --remove
sudo iptables -A GATE3 -m recent --name GATE3 --set
sudo iptables -A GATE3 -j DROP

Use port knocking:

# Client side - knock sequence
nc -z YOUR-SERVER-IP 7000
nc -z YOUR-SERVER-IP 8000
nc -z YOUR-SERVER-IP 9000
ssh user@YOUR-SERVER-IP

Persisting iptables Rules

Ubuntu/Debian:

# Install persistence package
sudo apt install iptables-persistent

# Save current rules
sudo netfilter-persistent save

# Or manually
sudo iptables-save | sudo tee /etc/iptables/rules.v4
sudo ip6tables-save | sudo tee /etc/iptables/rules.v6

RHEL/CentOS/Fedora:

# Save rules
sudo service iptables save

# Or
sudo iptables-save > /etc/sysconfig/iptables

Restore on boot with systemd service:

Create /etc/systemd/system/iptables-restore.service:

[Unit]
Description=Restore iptables rules
Before=network-pre.target
Wants=network-pre.target

[Service]
Type=oneshot
ExecStart=/sbin/iptables-restore /etc/iptables/rules.v4
ExecStart=/sbin/ip6tables-restore /etc/iptables/rules.v6

[Install]
WantedBy=multi-user.target

Enable service:

sudo systemctl enable iptables-restore
sudo systemctl start iptables-restore

firewalld: Zone-Based Firewall

Installation and Setup

RHEL/CentOS/Fedora:

sudo dnf install firewalld
sudo systemctl enable firewalld
sudo systemctl start firewalld

Ubuntu/Debian:

sudo apt install firewalld
sudo systemctl enable firewalld
sudo systemctl start firewalld

Check status:

sudo firewall-cmd --state
sudo firewall-cmd --list-all

Understanding Zones

Zones define trust levels for network connections:

  • drop: Drop all incoming, allow outgoing (most restrictive)
  • block: Reject all incoming with icmp-host-prohibited
  • public: Public networks, selective incoming allowed
  • external: External networks with masquerading
  • dmz: DMZ - limited access to internal network
  • work: Work networks, more services allowed
  • home: Home networks, most services allowed
  • internal: Internal networks, trust most traffic
  • trusted: All traffic accepted (least restrictive)

View zones:

# List all zones
sudo firewall-cmd --get-zones

# Get default zone
sudo firewall-cmd --get-default-zone

# List active zones
sudo firewall-cmd --get-active-zones

# View zone configuration
sudo firewall-cmd --zone=public --list-all

Basic firewalld Configuration

Set default zone:

sudo firewall-cmd --set-default-zone=public

Add services to zones:

# Add service temporarily
sudo firewall-cmd --zone=public --add-service=http

# Add service permanently
sudo firewall-cmd --zone=public --add-service=http --permanent
sudo firewall-cmd --zone=public --add-service=https --permanent

# Reload to apply permanent rules
sudo firewall-cmd --reload

# Add custom port
sudo firewall-cmd --zone=public --add-port=8080/tcp --permanent
sudo firewall-cmd --zone=public --add-port=51000-51999/tcp --permanent

# Remove service
sudo firewall-cmd --zone=public --remove-service=ssh --permanent

Assign interfaces to zones:

# Assign interface to zone
sudo firewall-cmd --zone=internal --change-interface=eth1 --permanent

# List interfaces in zone
sudo firewall-cmd --zone=internal --list-interfaces

Advanced firewalld Rules

Rich rules for complex scenarios:

# Allow SSH from specific IP
sudo firewall-cmd --zone=public --add-rich-rule='rule family="ipv4" source address="192.168.1.100" service name="ssh" accept' --permanent

# Limit SSH connections (10 per minute)
sudo firewall-cmd --zone=public --add-rich-rule='rule service name="ssh" limit value="10/m" accept' --permanent

# Allow port from subnet
sudo firewall-cmd --zone=public --add-rich-rule='rule family="ipv4" source address="192.168.1.0/24" port port="445" protocol="tcp" accept' --permanent

# Block specific IP
sudo firewall-cmd --zone=public --add-rich-rule='rule family="ipv4" source address="203.0.113.50" reject' --permanent

# Log dropped packets
sudo firewall-cmd --zone=public --add-rich-rule='rule family="ipv4" log prefix="FIREWALLD_DROP: " level="info" limit value="5/m" drop' --permanent

# Allow port with logging
sudo firewall-cmd --zone=public --add-rich-rule='rule family="ipv4" port port="3000" protocol="tcp" log prefix="GRAFANA_ACCESS: " level="info" accept' --permanent

Port forwarding:

# Enable masquerading
sudo firewall-cmd --zone=public --add-masquerade --permanent

# Forward port 8080 to 80
sudo firewall-cmd --zone=public --add-forward-port=port=8080:proto=tcp:toport=80 --permanent

# Forward to different host
sudo firewall-cmd --zone=public --add-forward-port=port=8080:proto=tcp:toport=80:toaddr=192.168.1.100 --permanent

Direct rules (iptables syntax):

# Add direct iptables rule
sudo firewall-cmd --direct --add-rule ipv4 filter INPUT 0 -p tcp --dport 22 -m conntrack --ctstate NEW -m recent --set --permanent
sudo firewall-cmd --direct --add-rule ipv4 filter INPUT 1 -p tcp --dport 22 -m conntrack --ctstate NEW -m recent --update --seconds 60 --hitcount 4 -j DROP --permanent

Custom Services and Zones

Create custom service:

File: /etc/firewalld/services/homeassistant.xml

<?xml version="1.0" encoding="utf-8"?>
<service>
  <short>Home Assistant</short>
  <description>Home automation platform</description>
  <port protocol="tcp" port="8123"/>
</service>

Load and use custom service:

sudo firewall-cmd --reload
sudo firewall-cmd --zone=public --add-service=homeassistant --permanent

Create custom zone:

File: /etc/firewalld/zones/homelab.xml

<?xml version="1.0" encoding="utf-8"?>
<zone target="default">
  <short>Homelab</short>
  <description>Trusted homelab network</description>
  <service name="ssh"/>
  <service name="http"/>
  <service name="https"/>
  <service name="samba"/>
  <port protocol="tcp" port="8080"/>
  <port protocol="tcp" port="9000"/>
  <source address="192.168.1.0/24"/>
</zone>

Load custom zone:

sudo firewall-cmd --reload
sudo firewall-cmd --get-zones
sudo firewall-cmd --zone=homelab --change-interface=eth1 --permanent

Docker and Firewall Integration

Docker manipulates iptables directly, which can bypass firewall rules. Here’s how to maintain control:

UFW and Docker

Docker bypasses UFW by default. Fix this:

Edit /etc/ufw/after.rules:

# Add at the end
*filter
:ufw-user-forward - [0:0]
:DOCKER-USER - [0:0]

-A DOCKER-USER -j ufw-user-forward
-A DOCKER-USER -j RETURN

COMMIT

Restart UFW:

sudo ufw reload

Now UFW rules apply to Docker:

# Allow Docker container port from local network only
sudo ufw allow from 192.168.1.0/24 to any port 8080

firewalld and Docker

Add Docker to trusted zone:

sudo firewall-cmd --zone=trusted --add-interface=docker0 --permanent
sudo firewall-cmd --reload

Control Docker published ports:

# Use Docker's published port with firewalld rich rules
sudo firewall-cmd --zone=public --add-rich-rule='rule family="ipv4" source address="192.168.1.0/24" port port="8080" protocol="tcp" accept' --permanent

Network Segmentation Strategies

VLAN-Based Segmentation

Separate networks by trust level:

# Public-facing services (DMZ) - VLAN 10
sudo firewall-cmd --zone=dmz --change-interface=eth0.10 --permanent
sudo firewall-cmd --zone=dmz --add-service=http --permanent
sudo firewall-cmd --zone=dmz --add-service=https --permanent

# Internal services - VLAN 20
sudo firewall-cmd --zone=internal --change-interface=eth0.20 --permanent
sudo firewall-cmd --zone=internal --add-service=samba --permanent
sudo firewall-cmd --zone=internal --add-service=nfs --permanent

# IoT devices (restricted) - VLAN 30
sudo firewall-cmd --zone=block --change-interface=eth0.30 --permanent
# Only allow specific outbound, deny all inbound

Interface-Based Segmentation

# External interface (WAN)
sudo firewall-cmd --zone=external --change-interface=eth0 --permanent
sudo firewall-cmd --zone=external --add-masquerade --permanent

# Internal interface (LAN)
sudo firewall-cmd --zone=internal --change-interface=eth1 --permanent

# Management interface
sudo firewall-cmd --zone=trusted --change-interface=eth2 --permanent

Security Best Practices

Principle of Least Privilege

Only open what you absolutely need:

# Start restrictive
sudo ufw default deny incoming

# Add services incrementally
sudo ufw allow from 192.168.1.0/24 to any port 445 comment 'SMB - local only'
sudo ufw allow from 192.168.1.0/24 to any port 2049 comment 'NFS - local only'

# Never expose everything
# BAD: sudo ufw allow from 192.168.1.0/24
# GOOD: Specify exact ports needed

Regular Audit and Review

Monthly firewall audit script:

File: /usr/local/bin/firewall-audit.sh

#!/bin/bash

echo "=== Firewall Audit Report ==="
echo "Date: $(date)"
echo ""

echo "=== UFW Status ==="
sudo ufw status numbered
echo ""

echo "=== Active Connections ==="
sudo ss -tulpn | grep LISTEN
echo ""

echo "=== Recent Blocked Attempts ==="
sudo tail -100 /var/log/ufw.log | grep BLOCK | tail -20
echo ""

echo "=== Open Ports ==="
sudo nmap -sT localhost
echo ""

echo "=== End Report ==="

Make executable and run:

sudo chmod +x /usr/local/bin/firewall-audit.sh
sudo /usr/local/bin/firewall-audit.sh

Defense in Depth

Layer multiple security controls:

  1. Firewall: Block unauthorized network access
  2. Fail2ban: Ban IPs after failed attempts
  3. Application-level: Configure service security
  4. Monitoring: Alert on suspicious activity
  5. Updates: Keep systems patched

Logging and Monitoring

Enable comprehensive logging:

UFW:

sudo ufw logging high

iptables:

# Log all dropped packets
sudo iptables -A INPUT -m limit --limit 5/min -j LOG --log-prefix "iptables_DROP: " --log-level 7
sudo iptables -A INPUT -j DROP

firewalld:

# Enable logging in default zone
sudo firewall-cmd --zone=public --add-rich-rule='rule family="ipv4" log prefix="FIREWALLD: " level="info" limit value="5/m"' --permanent

Monitor logs:

# UFW logs
sudo tail -f /var/log/ufw.log

# iptables logs (syslog)
sudo journalctl -k | grep iptables

# firewalld logs
sudo journalctl -u firewalld -f

Troubleshooting

Connection Issues After Enabling Firewall

Symptom: Can’t connect to services after enabling firewall.

Solution:

# Check if service is listening
sudo ss -tulpn | grep :PORT

# Check firewall rules
sudo ufw status numbered  # UFW
sudo iptables -L -v -n    # iptables
sudo firewall-cmd --list-all  # firewalld

# Test from localhost
curl http://localhost:PORT

# Check logs for blocks
sudo tail -f /var/log/ufw.log | grep BLOCK

SSH Lockout

Prevention:

# ALWAYS allow SSH before enabling firewall
sudo ufw allow ssh
sudo ufw enable

# Or use console access to fix

Recovery (requires console/physical access):

# Disable firewall temporarily
sudo ufw disable

# Or reset rules
sudo iptables -F
sudo iptables -P INPUT ACCEPT
sudo iptables -P FORWARD ACCEPT
sudo iptables -P OUTPUT ACCEPT

Docker Containers Not Accessible

Symptom: Published Docker ports blocked by firewall.

Solution for UFW:

# Edit /etc/ufw/after.rules as shown in Docker section
# Then reload
sudo ufw reload

Solution for firewalld:

# Add docker0 to trusted zone
sudo firewall-cmd --zone=trusted --add-interface=docker0 --permanent
sudo firewall-cmd --reload

Rules Not Persisting After Reboot

UFW:

# Check if UFW is enabled
sudo systemctl status ufw
sudo systemctl enable ufw

iptables:

# Install persistence
sudo apt install iptables-persistent

# Save rules
sudo netfilter-persistent save

firewalld:

# Make sure using --permanent flag
sudo firewall-cmd --zone=public --add-service=http --permanent
sudo firewall-cmd --reload

Checking Rule Order

Rules are processed top-down. Order matters:

# UFW - numbered list
sudo ufw status numbered

# iptables - line numbers
sudo iptables -L INPUT --line-numbers

# Insert rule at specific position
sudo iptables -I INPUT 3 -p tcp --dport 22 -j ACCEPT

Real-World Use Cases

Case 1: Home Media Server

Requirements: Plex accessible remotely, SMB only on LAN.

# UFW approach
sudo ufw allow 32400/tcp comment 'Plex remote access'
sudo ufw allow from 192.168.1.0/24 to any port 445 comment 'SMB LAN only'
sudo ufw allow from 192.168.1.0/24 to any port 139 comment 'NetBIOS LAN only'

Case 2: Multi-Tenant Homelab

Requirements: Separate zones for production, development, IoT.

# firewalld approach
# Production zone - eth0.10
sudo firewall-cmd --new-zone=production --permanent
sudo firewall-cmd --zone=production --add-source=10.0.10.0/24 --permanent
sudo firewall-cmd --zone=production --add-service=http --permanent
sudo firewall-cmd --zone=production --add-service=https --permanent

# Development zone - eth0.20
sudo firewall-cmd --new-zone=development --permanent
sudo firewall-cmd --zone=development --add-source=10.0.20.0/24 --permanent
sudo firewall-cmd --zone=development --add-port=3000-4000/tcp --permanent

# IoT zone - eth0.30 (restrictive)
sudo firewall-cmd --new-zone=iot --permanent
sudo firewall-cmd --zone=iot --set-target=DROP --permanent
sudo firewall-cmd --zone=iot --add-source=10.0.30.0/24 --permanent
# Only allow specific services out, block all incoming

sudo firewall-cmd --reload

Case 3: Reverse Proxy with Backend Protection

Requirements: Nginx reverse proxy exposed, backends isolated.

# iptables approach
# Allow HTTP/HTTPS on reverse proxy
sudo iptables -A INPUT -p tcp --dport 80 -j ACCEPT
sudo iptables -A INPUT -p tcp --dport 443 -j ACCEPT

# Allow backends only from reverse proxy IP
sudo iptables -A INPUT -p tcp -s 192.168.1.50 --dport 8080 -j ACCEPT
sudo iptables -A INPUT -p tcp -s 192.168.1.50 --dport 8081 -j ACCEPT

# Block backends from internet
sudo iptables -A INPUT -p tcp --dport 8080 -j DROP
sudo iptables -A INPUT -p tcp --dport 8081 -j DROP

Automation and Scripts

Automated Firewall Setup Script

File: /usr/local/bin/setup-firewall.sh

#!/bin/bash
set -e

echo "Setting up UFW firewall for homelab..."

# Reset UFW
sudo ufw --force reset

# Default policies
sudo ufw default deny incoming
sudo ufw default allow outgoing

# Essential services
sudo ufw allow ssh comment 'SSH access'
sudo ufw limit ssh

# Web services
sudo ufw allow 80/tcp comment 'HTTP'
sudo ufw allow 443/tcp comment 'HTTPS'

# Homelab services (LAN only)
sudo ufw allow from 192.168.1.0/24 to any port 445 comment 'Samba'
sudo ufw allow from 192.168.1.0/24 to any port 2049 comment 'NFS'
sudo ufw allow from 192.168.1.0/24 to any port 9000 comment 'Portainer'

# Media services
sudo ufw allow 32400/tcp comment 'Plex'
sudo ufw allow 8096/tcp comment 'Jellyfin'

# Enable logging
sudo ufw logging medium

# Enable firewall
sudo ufw --force enable

echo "Firewall setup complete!"
sudo ufw status verbose

Backup Firewall Rules

#!/bin/bash
BACKUP_DIR="/backup/firewall"
DATE=$(date +%Y%m%d_%H%M%S)

mkdir -p $BACKUP_DIR

# Backup UFW
if command -v ufw &> /dev/null; then
    sudo cp /etc/ufw/*.rules "$BACKUP_DIR/ufw_$DATE/"
fi

# Backup iptables
if command -v iptables &> /dev/null; then
    sudo iptables-save > "$BACKUP_DIR/iptables_$DATE.rules"
    sudo ip6tables-save > "$BACKUP_DIR/ip6tables_$DATE.rules"
fi

# Backup firewalld
if command -v firewall-cmd &> /dev/null; then
    sudo tar -czf "$BACKUP_DIR/firewalld_$DATE.tar.gz" /etc/firewalld/
fi

echo "Firewall rules backed up to $BACKUP_DIR"

Conclusion

A properly hardened firewall is essential for homelab security. Start with UFW for simplicity, graduate to iptables for advanced scenarios, or use firewalld for zone-based management. Key takeaways:

  • Default deny: Block everything, allow only what’s needed
  • Layer security: Combine firewall with fail2ban, monitoring, and good application configuration
  • Regular audits: Review rules monthly, remove unused ports
  • Test thoroughly: Verify rules work as expected before deploying
  • Document everything: Comment your rules, maintain a network diagram

Your homelab’s security is only as strong as your weakest link. A hardened firewall provides a strong foundation for building a secure, resilient infrastructure.