Skip to main content

Linux Firewalls — iptables, nftables, and UFW Explained

· 7 min read
Goel Academy
DevOps & Cloud Learning Hub

Your server is exposed to the internet with zero firewall rules — let's fix that in 10 minutes. Every Linux server connected to the internet gets hit with automated attacks within minutes of going online. A properly configured firewall is your first line of defense.

Firewall Tools — Which One to Use?

Linux has three main firewall tools. Here's when to use each.

ToolBackendComplexityBest For
UFWiptablesSimpleQuick setups, single servers
iptablesnetfilterMediumFine-grained control, legacy systems
nftablesnetfilterMediumModern replacement for iptables

UFW is the simplest — use it for straightforward setups. iptables gives you full control and is still widely used. nftables is the modern replacement that all major distros are migrating to. Learn all three.

UFW — The Quick Way

UFW (Uncomplicated Firewall) is exactly what the name says. Perfect for getting started.

# Install UFW (usually pre-installed on Ubuntu)
sudo apt install ufw

# Check current status
sudo ufw status verbose

# IMPORTANT: Allow SSH BEFORE enabling the firewall (or you'll lock yourself out!)
sudo ufw allow ssh

# Enable the firewall
sudo ufw enable

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

# Allow specific services
sudo ufw allow http # Port 80
sudo ufw allow https # Port 443
sudo ufw allow 3000/tcp # Custom port (TCP only)

# Allow from specific IP only
sudo ufw allow from 10.0.1.50 to any port 22

# Allow a subnet
sudo ufw allow from 10.0.1.0/24 to any port 5432

# Deny a specific IP
sudo ufw deny from 203.0.113.100

# Delete a rule
sudo ufw delete allow 3000/tcp

# Show rules with numbers (for deletion)
sudo ufw status numbered

# Delete by number
sudo ufw delete 5

A practical UFW setup for a web server:

# Reset to clean slate
sudo ufw reset

# Set defaults
sudo ufw default deny incoming
sudo ufw default allow outgoing

# Allow SSH from office IP only
sudo ufw allow from 203.0.113.0/24 to any port 22

# Allow HTTP and HTTPS from anywhere
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp

# Allow internal app port from private network
sudo ufw allow from 10.0.0.0/8 to any port 8080

# Enable rate limiting on SSH (blocks after 6 attempts in 30 seconds)
sudo ufw limit ssh

# Enable
sudo ufw enable
sudo ufw status verbose

iptables — Full Control

iptables gives you granular control over every packet. It works with chains: INPUT (incoming), OUTPUT (outgoing), and FORWARD (routed traffic).

# View current rules with line numbers
sudo iptables -L -n -v --line-numbers

# View NAT table rules
sudo iptables -t nat -L -n -v

Building a Basic iptables Firewall

The order of rules matters — iptables processes them top to bottom and stops at the first match.

# Flush all existing rules (start clean)
sudo iptables -F
sudo iptables -X

# Set default policies — drop everything by default
sudo iptables -P INPUT DROP
sudo iptables -P FORWARD DROP
sudo iptables -P OUTPUT ACCEPT

# Allow loopback traffic (required for local services)
sudo iptables -A INPUT -i lo -j ACCEPT

# Allow established and related connections (responses to our outgoing traffic)
sudo iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT

# Allow SSH (port 22)
sudo iptables -A INPUT -p tcp --dport 22 -j ACCEPT

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

# Allow ping (ICMP)
sudo iptables -A INPUT -p icmp --icmp-type echo-request -j ACCEPT

# Log dropped packets (useful for debugging)
sudo iptables -A INPUT -j LOG --log-prefix "iptables-dropped: " --log-level 4

# Verify rules
sudo iptables -L -n -v

Advanced iptables Rules

# Rate limit SSH to prevent brute force (max 3 new connections per minute)
sudo iptables -A INPUT -p tcp --dport 22 -m conntrack --ctstate NEW \
-m recent --set --name SSH
sudo iptables -A INPUT -p tcp --dport 22 -m conntrack --ctstate NEW \
-m recent --update --seconds 60 --hitcount 4 --name SSH -j DROP

# Block a specific IP
sudo iptables -I INPUT 1 -s 203.0.113.100 -j DROP

# Allow SSH only from a specific subnet
sudo iptables -A INPUT -p tcp -s 10.0.1.0/24 --dport 22 -j ACCEPT

# Port forwarding — redirect external port 8080 to internal port 80
sudo iptables -t nat -A PREROUTING -p tcp --dport 8080 -j REDIRECT --to-port 80

# SNAT — masquerade outgoing traffic (for NAT gateways)
sudo iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE

Saving iptables Rules

iptables rules are lost on reboot unless you save them.

# Debian/Ubuntu — install iptables-persistent
sudo apt install iptables-persistent

# Save current rules
sudo iptables-save > /etc/iptables/rules.v4
sudo ip6tables-save > /etc/iptables/rules.v6

# RHEL/CentOS
sudo iptables-save > /etc/sysconfig/iptables
sudo systemctl enable iptables

nftables — The Modern Approach

nftables replaces iptables with a cleaner syntax and better performance. If you're starting fresh, use nftables.

# Check if nftables is active
sudo nft list ruleset

# Create a complete firewall configuration
sudo tee /etc/nftables.conf << 'EOF'
#!/usr/sbin/nft -f

# Flush existing rules
flush ruleset

table inet filter {
chain input {
type filter hook input priority 0; policy drop;

# Allow loopback
iif "lo" accept

# Allow established/related
ct state established,related accept

# Allow ICMP (ping)
ip protocol icmp accept
ip6 nexthdr icmpv6 accept

# Allow SSH
tcp dport 22 accept

# Allow HTTP and HTTPS
tcp dport { 80, 443 } accept

# Rate limit new SSH connections
tcp dport 22 ct state new limit rate 3/minute accept

# Log and drop everything else
log prefix "nftables-dropped: " drop
}

chain forward {
type filter hook forward priority 0; policy drop;
}

chain output {
type filter hook output priority 0; policy accept;
}
}
EOF
# Apply the configuration
sudo nft -f /etc/nftables.conf

# Verify rules
sudo nft list ruleset

# Enable nftables to load at boot
sudo systemctl enable nftables

# Add a rule dynamically (block an IP)
sudo nft add rule inet filter input ip saddr 203.0.113.100 drop

# List rules with handles (for deletion)
sudo nft list chain inet filter input -a

# Delete a rule by handle number
sudo nft delete rule inet filter input handle 15

nftables vs iptables Syntax Comparison

Taskiptablesnftables
List rulesiptables -L -nnft list ruleset
Allow port 80iptables -A INPUT -p tcp --dport 80 -j ACCEPTnft add rule inet filter input tcp dport 80 accept
Block IPiptables -I INPUT -s 1.2.3.4 -j DROPnft add rule inet filter input ip saddr 1.2.3.4 drop
Multiple portsTwo separate rulestcp dport { 80, 443 } accept
Save rulesiptables-save > filenft list ruleset > file

Practical Security Scenarios

Scenario: Locking Down a Production Web Server

# Using UFW for simplicity
sudo ufw reset
sudo ufw default deny incoming
sudo ufw default allow outgoing

# SSH from bastion host only
sudo ufw allow from 10.0.0.5 to any port 22 proto tcp

# Web traffic from anywhere
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp

# App port from load balancer only
sudo ufw allow from 10.0.1.0/24 to any port 8080 proto tcp

# Monitoring agent (Prometheus node exporter)
sudo ufw allow from 10.0.2.0/24 to any port 9100 proto tcp

# Enable and verify
sudo ufw enable
sudo ufw status verbose

Scenario: Quick IP Block During an Attack

# Block an attacking IP immediately (iptables — inserts at top)
sudo iptables -I INPUT 1 -s 185.220.101.42 -j DROP

# Block an entire subnet
sudo iptables -I INPUT 1 -s 185.220.0.0/16 -j DROP

# Save so it persists after reboot
sudo iptables-save > /etc/iptables/rules.v4

# Verify the block is in place
sudo iptables -L INPUT -n | head -5

Debugging Firewall Issues

When things aren't working and you suspect the firewall, here's how to diagnose.

# Check if a port is listening
sudo ss -tlnp | grep :80

# Test connectivity from another machine
nc -zv server-ip 80

# Watch firewall logs in real-time
sudo journalctl -f | grep -i "iptables\|nftables\|UFW"

# Temporarily disable firewall for testing (re-enable immediately after!)
sudo ufw disable # UFW
# or
sudo iptables -P INPUT ACCEPT && sudo iptables -F # iptables (dangerous!)

# Count dropped packets per rule
sudo iptables -L -n -v # Check the pkts column

Next up: Shell Scripting Series Part 1 — turn your repetitive command-line tasks into reusable scripts with variables, loops, functions, and more.