Quick Answer: For quick setup:
sudo ufw allow 22 && sudo ufw allow 80,443/tcp && sudo ufw enable. For advanced control: use iptables. This guide covers both from scratch, plus rate limiting, geo-blocking, NAT, and production-ready templates.
Need a VPS? Vultr (free credit), DigitalOcean ($200 free credit), or RackNerd (cheap annual deals).
Why You Need a Firewall
A server with no firewall is an open door. Within minutes of going online, bots will:
- Try to brute-force SSH (port 22)
- Scan for open databases (3306, 5432, 27017)
- Probe for vulnerable services
- Attempt to use your server as a relay
A firewall blocks all incoming traffic except the ports you explicitly allow.
# See how many failed SSH attempts you've had
grep "Failed password" /var/log/auth.log | wc -l
Part 1: Understanding Firewall Basics
How Traffic Flows
Internet → Your Server's Network Interface → Firewall Rules → Application
Three chains:
INPUT → Traffic coming TO your server
OUTPUT → Traffic leaving FROM your server
FORWARD → Traffic passing THROUGH your server (routing/NAT)
Actions
| Action | What Happens |
|---|---|
| ACCEPT | Allow the packet through |
| DROP | Silently discard (sender gets no response) |
| REJECT | Discard and send error back to sender |
| LOG | Log the packet then continue to next rule |
Rule Order Matters
Rules are checked top to bottom. First match wins. If no rule matches, the default policy applies (usually DROP or ACCEPT).
Rule 1: Allow SSH (port 22) ← Matches SSH traffic
Rule 2: Allow HTTP (port 80) ← Matches HTTP traffic
Rule 3: DROP everything else ← Catches everything else
Part 2: UFW (Uncomplicated Firewall)
UFW is the easiest way to manage a firewall on Ubuntu/Debian. It's a frontend for iptables.
Install and Enable
sudo apt install ufw -y
# IMPORTANT: Allow SSH before enabling!
sudo ufw allow 22/tcp
# Enable
sudo ufw enable
# Check status
sudo ufw status verbose
Essential Commands
# Allow
sudo ufw allow 80/tcp # HTTP
sudo ufw allow 443/tcp # HTTPS
sudo ufw allow 80,443/tcp # Both
sudo ufw allow 8000:9000/tcp # Port range
sudo ufw allow from 10.0.0.5 # Specific IP
sudo ufw allow from 10.0.0.0/24 to any port 22 # Subnet to SSH
# Deny
sudo ufw deny 3306 # Block MySQL
sudo ufw deny from 1.2.3.4 # Block IP
# Delete rules
sudo ufw status numbered
sudo ufw delete 3 # By number
sudo ufw delete allow 80 # By rule
# Rate limit SSH (6 attempts per 30 seconds)
sudo ufw limit 22/tcp
# Reset everything
sudo ufw reset
Default Policies
sudo ufw default deny incoming # Block all incoming
sudo ufw default allow outgoing # Allow all outgoing
Server Templates
Web Server:
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
Proxy Server (3X-UI):
sudo ufw allow 22/tcp
sudo ufw allow 443/tcp # VLESS-WS
sudo ufw allow 8443/tcp # Reality
sudo ufw allow 2053/tcp # Panel
sudo ufw allow 2082,8880/tcp # CDN HTTP
sudo ufw allow 2083,2087/tcp # CDN HTTPS
sudo ufw enable
VPN Server (WireGuard):
sudo ufw allow 22/tcp
sudo ufw allow 51820/udp
sudo ufw enable
Full reference: UFW Cheat Sheet
Part 3: iptables (Power Users)
iptables gives you complete control over every packet. More complex than UFW but infinitely more flexible.
View Rules
sudo iptables -L -n -v # All rules
sudo iptables -L INPUT -n --line-numbers # Input chain with line numbers
sudo iptables-save # Dump rules in save format
Basic Server Protection
# Flush existing rules
sudo iptables -F
# Default policies
sudo iptables -P INPUT DROP
sudo iptables -P FORWARD DROP
sudo iptables -P OUTPUT ACCEPT
# Allow established connections
sudo iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
# Allow loopback
sudo iptables -A INPUT -i lo -j ACCEPT
# Allow SSH
sudo iptables -A INPUT -p tcp --dport 22 -j ACCEPT
# Allow HTTP/HTTPS
sudo iptables -A INPUT -p tcp --dport 80 -j ACCEPT
sudo iptables -A INPUT -p tcp --dport 443 -j ACCEPT
# Allow ping
sudo iptables -A INPUT -p icmp --icmp-type echo-request -m limit --limit 1/s -j ACCEPT
Rate Limiting
# SSH brute-force protection (4 attempts 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
sudo iptables -A INPUT -p tcp --dport 22 -j ACCEPT
# Limit connections per IP (25 simultaneous)
sudo iptables -A INPUT -p tcp --dport 80 -m connlimit --connlimit-above 25 -j DROP
Block and Allow
# Block an IP
sudo iptables -A INPUT -s 1.2.3.4 -j DROP
# Block a subnet
sudo iptables -A INPUT -s 10.0.0.0/8 -j DROP
# Allow only from specific IP to a port
sudo iptables -A INPUT -p tcp -s 10.0.0.5 --dport 3306 -j ACCEPT
sudo iptables -A INPUT -p tcp --dport 3306 -j DROP
Save Rules
# Save
sudo iptables-save > /etc/iptables.rules
# Make persistent (Debian/Ubuntu)
sudo apt install iptables-persistent -y
sudo netfilter-persistent save
WARNING: Running iptables -F when default policy is DROP will lock you out of SSH. Always reset default policy to ACCEPT first: iptables -P INPUT ACCEPT
Full reference: iptables Cheat Sheet
Part 4: Geo-Blocking
Block traffic from entire countries using IP range databases.
Using ipset (Efficient)
sudo apt install ipset -y
# Create a set for blocked countries
sudo ipset create blocked-countries hash:net
# Download and add country IP ranges
wget -q https://www.ipdeny.com/ipblocks/data/countries/cn.zone
while read cidr; do sudo ipset add blocked-countries "$cidr"; done < cn.zone
# Block with iptables
sudo iptables -A INPUT -m set --match-set blocked-countries src -j DROP
Block Multiple Countries
for country in cn ru kp; do
wget -q "https://www.ipdeny.com/ipblocks/data/countries/${country}.zone"
while read cidr; do sudo ipset add blocked-countries "$cidr" 2>/dev/null; done < "${country}.zone"
done
Part 5: NAT and Port Forwarding
Enable IP Forwarding
echo "net.ipv4.ip_forward = 1" | sudo tee -a /etc/sysctl.conf
sudo sysctl -p
Masquerade (NAT for VPN)
# All traffic from VPN subnet goes out through your server's IP
sudo iptables -t nat -A POSTROUTING -s 10.66.66.0/24 -o eth0 -j MASQUERADE
Port Forwarding
# Forward external port 8080 to internal server 192.168.1.100:80
sudo iptables -t nat -A PREROUTING -p tcp --dport 8080 -j DNAT --to-destination 192.168.1.100:80
sudo iptables -A FORWARD -p tcp -d 192.168.1.100 --dport 80 -j ACCEPT
Full guide: Port Forwarding Guide
Part 6: Fail2ban (Automatic Banning)
Fail2ban monitors logs and automatically bans IPs that show malicious behavior.
sudo apt install fail2ban -y
sudo systemctl enable --now fail2ban
It works immediately for SSH with default settings — bans IPs for 10 minutes after 5 failed login attempts.
Custom Jail
# /etc/fail2ban/jail.local
[sshd]
enabled = true
maxretry = 3
bantime = 3600
findtime = 600
Manage Bans
sudo fail2ban-client status sshd # See banned IPs
sudo fail2ban-client set sshd unbanip IP # Unban
Full guide: Fail2ban Setup
Part 7: Logging
Log Dropped Packets
# Add before the final DROP rule
sudo iptables -A INPUT -j LOG --log-prefix "IPTABLES-DROP: " --log-level 4
sudo iptables -A INPUT -j DROP
View Logs
# iptables
grep "IPTABLES-DROP" /var/log/kern.log | tail -20
# UFW
grep "UFW" /var/log/syslog | tail -20
# Fail2ban
sudo tail -f /var/log/fail2ban.log
Part 8: Production Template
Complete firewall for a production server running a web app + proxy:
#!/bin/bash
# production-firewall.sh
# Safety: reset to ACCEPT before flushing (prevents lockout on re-run)
iptables -P INPUT ACCEPT
iptables -P FORWARD ACCEPT
# Reset
iptables -F
iptables -X
iptables -t nat -F
# Default policies
iptables -P INPUT DROP
iptables -P FORWARD DROP
iptables -P OUTPUT ACCEPT
# Loopback
iptables -A INPUT -i lo -j ACCEPT
# Established connections
iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
# SSH (rate limited)
iptables -A INPUT -p tcp --dport 22 -m conntrack --ctstate NEW -m recent --set --name SSH
iptables -A INPUT -p tcp --dport 22 -m conntrack --ctstate NEW -m recent --update --seconds 60 --hitcount 4 --name SSH -j DROP
iptables -A INPUT -p tcp --dport 22 -j ACCEPT
# Connection limits per IP (must be BEFORE accept rules)
iptables -A INPUT -p tcp --dport 80 -m connlimit --connlimit-above 50 -j DROP
iptables -A INPUT -p tcp --dport 443 -m connlimit --connlimit-above 50 -j DROP
# Web
iptables -A INPUT -p tcp --dport 80 -j ACCEPT
iptables -A INPUT -p tcp --dport 443 -j ACCEPT
# Proxy ports (3X-UI)
iptables -A INPUT -p tcp --dport 8443 -j ACCEPT # Reality
iptables -A INPUT -p tcp --dport 2082 -j ACCEPT # CDN HTTP
iptables -A INPUT -p tcp --dport 2083 -j ACCEPT # CDN HTTPS
iptables -A INPUT -p tcp --dport 8880 -j ACCEPT # CDN HTTP alt
# WireGuard
iptables -A INPUT -p udp --dport 51820 -j ACCEPT
# Ping (limited)
iptables -A INPUT -p icmp --icmp-type echo-request -m limit --limit 1/s -j ACCEPT
# Log dropped
iptables -A INPUT -j LOG --log-prefix "FW-DROP: " --log-level 4
# Save
iptables-save > /etc/iptables.rules
echo "Firewall configured"
chmod +x production-firewall.sh
sudo ./production-firewall.sh
Part 9: UFW vs iptables vs nftables
| UFW | iptables | nftables | |
|---|---|---|---|
| Difficulty | Easy | Medium | Medium |
| Best for | Quick server setup | Complex rules, NAT | New deployments |
| Rate limiting | Basic (limit) |
Advanced (recent, connlimit) | Advanced |
| Geo-blocking | Not built-in | Via ipset | Native sets |
| Status | Frontend for iptables | Legacy (still works everywhere) | Modern replacement |
| Ubuntu default | Yes | Underlying | Replacing iptables |
Recommendation:
- 80% of servers: UFW is enough
- Complex setups: iptables (proxy servers, NAT, VPN)
- New infrastructure: nftables (if you're starting fresh)
Security Checklist
echo "=== Firewall Security Check ==="
echo "UFW status: $(sudo ufw status | head -1)"
echo "iptables rules: $(sudo iptables -L -n | grep -c "ACCEPT\|DROP")"
echo "Fail2ban: $(systemctl is-active fail2ban)"
echo "SSH port: $(grep "^Port" /etc/ssh/sshd_config || echo "22 (default)")"
echo "Open ports:"
sudo ss -tlnp | grep -v "127.0.0" | awk '{print $4}' | sort -u