Quick Answer:
iptables -A INPUT -p tcp --dport 22 -j ACCEPTallows SSH.iptables -A INPUT -s 1.2.3.4 -j DROPblocks an IP.iptables -A INPUT -j DROPblocks everything else (put this LAST).iptables -L -nlists rules.iptables-save > /etc/iptables.rulessaves them.
Need a VPS? Vultr (free credit), DigitalOcean ($200 free credit), or RackNerd (cheap annual deals).
How iptables Works
Traffic flows through chains:
- INPUT — packets coming TO your server
- OUTPUT — packets leaving FROM your server
- FORWARD — packets passing THROUGH your server (routing)
Each rule says: if a packet matches these conditions, do this target:
- ACCEPT — allow the packet
- DROP — silently discard (sender gets no response)
- REJECT — discard and send error back to sender
- LOG — log the packet then continue to next rule
Rules are checked top to bottom. First match wins.
View Current Rules
# List all rules
iptables -L -n -v
# List with line numbers (for inserting/deleting)
iptables -L -n --line-numbers
# List specific chain
iptables -L INPUT -n -v
# List as iptables commands (easy to copy)
iptables-save
Basic Server Protection
This is a good starting firewall for any server:
# 1. Allow established connections (responses to your outgoing requests)
iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
# 2. Allow loopback (localhost)
iptables -A INPUT -i lo -j ACCEPT
# 3. Allow SSH
iptables -A INPUT -p tcp --dport 22 -j ACCEPT
# 4. Allow HTTP and HTTPS
iptables -A INPUT -p tcp --dport 80 -j ACCEPT
iptables -A INPUT -p tcp --dport 443 -j ACCEPT
# 5. Allow ping
iptables -A INPUT -p icmp --icmp-type echo-request -j ACCEPT
# 6. Drop everything else
iptables -A INPUT -j DROP
Allow and Block
Allow a Port
# Allow single port
iptables -A INPUT -p tcp --dport 3306 -j ACCEPT
# Allow port range
iptables -A INPUT -p tcp --dport 8000:8100 -j ACCEPT
# Allow UDP port
iptables -A INPUT -p udp --dport 51820 -j ACCEPT
# Allow port only from specific IP
iptables -A INPUT -p tcp -s 10.0.0.5 --dport 3306 -j ACCEPT
# Allow port from subnet
iptables -A INPUT -p tcp -s 192.168.1.0/24 --dport 22 -j ACCEPT
Block
# Block an IP
iptables -A INPUT -s 1.2.3.4 -j DROP
# Block a subnet
iptables -A INPUT -s 10.0.0.0/8 -j DROP
# Block a port
iptables -A INPUT -p tcp --dport 23 -j DROP
# Block outgoing to an IP (prevent your server from connecting)
iptables -A OUTPUT -d 1.2.3.4 -j DROP
Rate Limiting
Limit SSH Brute Force
# Allow max 4 new SSH connections per minute per IP
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
Limit HTTP Requests
# Max 25 simultaneous connections per IP
iptables -A INPUT -p tcp --dport 80 -m connlimit --connlimit-above 25 -j DROP
iptables -A INPUT -p tcp --dport 443 -m connlimit --connlimit-above 25 -j DROP
Limit Ping
# Allow 1 ping per second
iptables -A INPUT -p icmp --icmp-type echo-request -m limit --limit 1/s -j ACCEPT
iptables -A INPUT -p icmp --icmp-type echo-request -j DROP
NAT and Port Forwarding
Enable IP Forwarding
# Temporary
echo 1 > /proc/sys/net/ipv4/ip_forward
# Permanent
echo "net.ipv4.ip_forward = 1" >> /etc/sysctl.conf
sysctl -p
Port Forwarding
# Forward port 8080 on public IP to port 80 on internal server 192.168.1.100
iptables -t nat -A PREROUTING -p tcp --dport 8080 -j DNAT --to-destination 192.168.1.100:80
iptables -A FORWARD -p tcp -d 192.168.1.100 --dport 80 -j ACCEPT
# Masquerade outgoing traffic (for NAT gateway)
iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
Redirect Port Locally
# Redirect port 80 to 8080 on same machine
iptables -t nat -A PREROUTING -p tcp --dport 80 -j REDIRECT --to-port 8080
Block Countries (GeoIP)
# Using ipset for efficient country blocking
apt install ipset
# Create set
ipset create blocked-countries hash:net
# Add country IP ranges (download from ipdeny.com)
wget -q https://www.ipdeny.com/ipblocks/data/countries/cn.zone
while read cidr; do ipset add blocked-countries "$cidr"; done < cn.zone
# Block the set
iptables -A INPUT -m set --match-set blocked-countries src -j DROP
Logging
# Log dropped packets (insert BEFORE the final DROP rule)
iptables -I INPUT -j LOG --log-prefix "IPTABLES-DROP: " --log-level 4
# Log specific traffic
iptables -A INPUT -p tcp --dport 22 -j LOG --log-prefix "SSH-ATTEMPT: "
# View logs
journalctl -k | grep "IPTABLES-DROP"
# Or check /var/log/kern.log
Managing Rules
Delete Rules
# Delete by line number
iptables -L INPUT --line-numbers
iptables -D INPUT 3 # Delete rule #3
# Delete by exact match
iptables -D INPUT -s 1.2.3.4 -j DROP
# Flush all rules (careful!)
iptables -F # Flush all chains (WARNING: if default policy is DROP, this locks you out! Run: iptables -P INPUT ACCEPT first)
iptables -F INPUT # Flush specific chain
Insert Rules (at specific position)
# Insert at position 1 (top)
iptables -I INPUT 1 -p tcp --dport 8443 -j ACCEPT
# Insert at position 3
iptables -I INPUT 3 -s 10.0.0.5 -j ACCEPT
Save and Restore
# Save current rules
iptables-save > /etc/iptables.rules
# Restore rules
iptables-restore < /etc/iptables.rules
# Auto-restore on boot (Debian/Ubuntu)
apt install iptables-persistent
netfilter-persistent save
# Rules saved to /etc/iptables/rules.v4
Complete Server Template
Copy-paste this for a fresh server:
# Flush existing rules
iptables -F
iptables -X
# Default policies
# WARNING: On a remote server, add all ACCEPT rules BEFORE setting DROP policy
# Flushing rules then setting DROP will lock you out!
iptables -P INPUT DROP
iptables -P FORWARD DROP
iptables -P OUTPUT ACCEPT
# Allow established connections
iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
# Allow loopback
iptables -A INPUT -i lo -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
# HTTP/HTTPS
iptables -A INPUT -p tcp --dport 80 -j ACCEPT
iptables -A INPUT -p tcp --dport 443 -j ACCEPT
# Ping (limited)
iptables -A INPUT -p icmp --icmp-type echo-request -m limit --limit 1/s -j ACCEPT
# Save
iptables-save > /etc/iptables.rules
iptables vs nftables vs UFW
| iptables | nftables | UFW | |
|---|---|---|---|
| Complexity | Medium | Medium | Easy |
| Status | Legacy (still works) | Replacement for iptables | Frontend for iptables |
| Best for | Experienced admins | New deployments | Quick server setup |
| Command | iptables |
nft |
ufw |
UFW is just a wrapper around iptables. If you know iptables, you can do everything UFW does and more.
See Also
- Server Hardening Guide
- Port Forwarding Guide
- How to Check if a Port Is Open
- Linux Commands Cheat Sheet
- WireGuard Setup Guide
IPv6 (ip6tables)
All rules in this guide are IPv4 only (iptables). If your server has IPv6 enabled (most do by default), attackers can bypass your entire firewall by connecting over IPv6.
Option 1: Apply matching IPv6 rules
ip6tables -A INPUT -i lo -j ACCEPT
ip6tables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
ip6tables -A INPUT -p tcp --dport 22 -j ACCEPT
ip6tables -A INPUT -p tcp --dport 80 -j ACCEPT
ip6tables -A INPUT -p tcp --dport 443 -j ACCEPT
ip6tables -P INPUT DROP
ip6tables -P FORWARD DROP
# Save IPv6 rules
ip6tables-save > /etc/iptables/rules.v6
Option 2: Disable IPv6 entirely
echo "net.ipv6.conf.all.disable_ipv6 = 1" >> /etc/sysctl.conf
echo "net.ipv6.conf.default.disable_ipv6 = 1" >> /etc/sysctl.conf
sysctl -p