How to Set Up a VPS Firewall with iptables: Advanced Rules and Best Practices

How to Set Up a VPS Firewall with iptables: Advanced Rules and Best Practices

UFW is an excellent beginner-friendly firewall, but understanding iptables — the underlying Linux firewall — gives you granular control that UFW can’t match. From connection-state tracking and rate limiting to port forwarding and custom chains, iptables lets you express precisely what traffic is allowed, logged, or blocked.

This guide covers iptables fundamentals, building a complete production ruleset, persistent configuration, rate limiting, and advanced techniques for VPS security.

How iptables Works

iptables processes network packets through chains of rules. Each rule matches specific packet characteristics and specifies an action (ACCEPT, DROP, REJECT, LOG).

Chain When it’s evaluated Common use
INPUT Packets destined for this server Allow/block inbound traffic
OUTPUT Packets originating from this server Control outbound traffic
FORWARD Packets routed through this server Router/VPN setups

Tables

  • filter — Default table. Accepts, drops, or rejects packets.
  • nat — Network Address Translation. Port forwarding, masquerading.
  • mangle — Packet modification (QoS, TTL changes).

Essential iptables Command Syntax

# Basic syntax
iptables -A CHAIN -options -j TARGET

# Common flags
-A  # Append rule to chain
-I  # Insert rule at position
-D  # Delete rule
-L  # List rules
-F  # Flush (delete all) rules
-n  # Numeric output (no DNS lookups)
-v  # Verbose
--line-numbers  # Show rule numbers

# Examples
iptables -A INPUT -p tcp --dport 22 -j ACCEPT   # Allow SSH
iptables -D INPUT 3                              # Delete rule #3
iptables -L INPUT -n -v --line-numbers           # List INPUT rules

Step 1: View Current Rules

# View all tables
sudo iptables -L -n -v

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

# View with line numbers (needed for deletion)
sudo iptables -L INPUT -n -v --line-numbers

Step 2: Build a Complete Production Ruleset

The safest approach: flush all rules and build from scratch. Run this as a script so you can revert if something goes wrong:

sudo nano /root/setup-iptables.sh
#!/bin/bash
# VPS iptables firewall setup
# IMPORTANT: Run this via SSH. If you get locked out,
# reboot from VPS.DO control panel to restore access.

IPT="sudo iptables"
YOUR_SSH_PORT=2222      # Your actual SSH port
YOUR_HOME_IP=""         # Optional: restrict SSH to your IP

# ── Step 1: Set default policies ─────────────────────────
$IPT -P INPUT   DROP    # Drop all inbound by default
$IPT -P FORWARD DROP    # Drop all forwarding by default
$IPT -P OUTPUT  ACCEPT  # Allow all outbound by default

# ── Step 2: Flush existing rules ─────────────────────────
$IPT -F         # Flush filter table
$IPT -X         # Delete custom chains
$IPT -t nat -F  # Flush NAT table
$IPT -t mangle -F

# ── Step 3: Allow loopback (localhost) ───────────────────
$IPT -A INPUT -i lo -j ACCEPT
$IPT -A OUTPUT -o lo -j ACCEPT

# ── Step 4: Allow established/related connections ────────
# This allows return traffic for connections you initiated
$IPT -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT

# ── Step 5: Drop invalid packets ─────────────────────────
$IPT -A INPUT -m conntrack --ctstate INVALID -j DROP

# ── Step 6: Allow ICMP (ping) ────────────────────────────
$IPT -A INPUT -p icmp --icmp-type echo-request -m limit \
  --limit 1/s --limit-burst 10 -j ACCEPT

# ── Step 7: SSH access ───────────────────────────────────
if [ -n "$YOUR_HOME_IP" ]; then
    # Restrict SSH to specific IP
    $IPT -A INPUT -p tcp --dport $YOUR_SSH_PORT \
      -s $YOUR_HOME_IP -m conntrack --ctstate NEW -j ACCEPT
else
    # Allow SSH from anywhere (with rate limiting)
    $IPT -A INPUT -p tcp --dport $YOUR_SSH_PORT \
      -m conntrack --ctstate NEW \
      -m recent --set --name SSH_NEW --rsource
    $IPT -A INPUT -p tcp --dport $YOUR_SSH_PORT \
      -m conntrack --ctstate NEW \
      -m recent --update --seconds 60 --hitcount 4 --name SSH_NEW -j DROP
    $IPT -A INPUT -p tcp --dport $YOUR_SSH_PORT \
      -m conntrack --ctstate NEW -j ACCEPT
fi

# ── Step 8: Web traffic ──────────────────────────────────
$IPT -A INPUT -p tcp --dport 80  -m conntrack --ctstate NEW -j ACCEPT
$IPT -A INPUT -p tcp --dport 443 -m conntrack --ctstate NEW -j ACCEPT

# ── Step 9: Additional service ports (uncomment as needed) ─
# $IPT -A INPUT -p tcp --dport 25   -j ACCEPT  # SMTP
# $IPT -A INPUT -p tcp --dport 587  -j ACCEPT  # SMTP submission
# $IPT -A INPUT -p tcp --dport 993  -j ACCEPT  # IMAPS
# $IPT -A INPUT -p udp --dport 51820 -j ACCEPT  # WireGuard VPN
# $IPT -A INPUT -p tcp --dport 25565 -j ACCEPT  # Minecraft

# ── Step 10: Log dropped packets ─────────────────────────
$IPT -A INPUT -m limit --limit 5/min -j LOG \
  --log-prefix "iptables-dropped: " --log-level 7

echo "Firewall rules applied successfully!"
$IPT -L -n -v
chmod +x /root/setup-iptables.sh
sudo bash /root/setup-iptables.sh

Step 3: Save Rules Persistently

iptables rules are lost on reboot without persistence configuration:

sudo apt install iptables-persistent -y

# Save current rules
sudo netfilter-persistent save

# Rules are saved to:
# /etc/iptables/rules.v4  (IPv4)
# /etc/iptables/rules.v6  (IPv6)

# Reload saved rules
sudo netfilter-persistent reload

After any rule change, save again:

sudo iptables-save > /etc/iptables/rules.v4

Step 4: Rate Limiting with iptables

Limit HTTP connections per IP (anti-DDoS)

# Allow max 25 new HTTP connections per minute per IP
sudo iptables -A INPUT -p tcp --dport 80 \
  -m conntrack --ctstate NEW \
  -m limit --limit 25/minute --limit-burst 100 \
  -j ACCEPT

sudo iptables -A INPUT -p tcp --dport 80 \
  -m conntrack --ctstate NEW -j DROP

Block port scanning (SYN flood protection)

# Limit SYN packets
sudo iptables -A INPUT -p tcp --syn \
  -m limit --limit 1/s --limit-burst 3 -j ACCEPT

sudo iptables -A INPUT -p tcp --syn -j DROP

Block brute-force SSH with recent module

# Track IPs that try to connect to SSH
sudo iptables -N SSH_CHECK
sudo iptables -A INPUT -p tcp --dport 2222 -j SSH_CHECK

sudo iptables -A SSH_CHECK -m recent --set --name SSH
sudo iptables -A SSH_CHECK -m recent --update \
  --seconds 60 --hitcount 4 --name SSH -j DROP
sudo iptables -A SSH_CHECK -j ACCEPT

Step 5: Block Specific IPs or Countries

Block a specific IP

# Block single IP
sudo iptables -A INPUT -s 45.83.91.14 -j DROP

# Block entire subnet
sudo iptables -A INPUT -s 45.83.0.0/16 -j DROP

# View blocked IPs
sudo iptables -L INPUT -n -v | grep DROP

Unblock an IP

# Find the rule number
sudo iptables -L INPUT -n --line-numbers | grep 45.83.91.14

# Delete by number (e.g., rule 5)
sudo iptables -D INPUT 5

GeoIP blocking with ipset (block entire countries)

sudo apt install ipset -y

# Create a set for blocked IPs
sudo ipset create blocked_countries hash:net

# Add CIDR ranges for a country (get from ipdeny.com)
# Example: add a range
sudo ipset add blocked_countries 1.2.3.0/24

# Block the entire set
sudo iptables -I INPUT -m set --match-set blocked_countries src -j DROP

Step 6: Port Forwarding with iptables

# Enable IP forwarding
sudo sysctl -w net.ipv4.ip_forward=1
echo "net.ipv4.ip_forward=1" | sudo tee -a /etc/sysctl.conf

# Forward external port 8080 to internal port 80
sudo iptables -t nat -A PREROUTING \
  -p tcp --dport 8080 -j REDIRECT --to-port 80

# Forward to a different internal server
sudo iptables -t nat -A PREROUTING \
  -p tcp --dport 3306 \
  -j DNAT --to-destination 192.168.1.100:3306

sudo iptables -t nat -A POSTROUTING \
  -j MASQUERADE

Step 7: Log and Monitor Dropped Traffic

# Watch firewall drops in real time
sudo tail -f /var/log/kern.log | grep "iptables-dropped"

# Count drops by IP (top offenders)
sudo grep "iptables-dropped" /var/log/kern.log | \
  grep -oP 'SRC=\S+' | sort | uniq -c | sort -rn | head -20

Step 8: IPv6 Rules (ip6tables)

# Apply the same ruleset for IPv6
sudo ip6tables -P INPUT DROP
sudo ip6tables -P FORWARD DROP
sudo ip6tables -P OUTPUT ACCEPT

sudo ip6tables -A INPUT -i lo -j ACCEPT
sudo ip6tables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
sudo ip6tables -A INPUT -p ipv6-icmp -j ACCEPT  # Allow ICMPv6 (required for IPv6)
sudo ip6tables -A INPUT -p tcp --dport 2222 -j ACCEPT
sudo ip6tables -A INPUT -p tcp --dport 80 -j ACCEPT
sudo ip6tables -A INPUT -p tcp --dport 443 -j ACCEPT

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

iptables vs UFW: When to Use Each

iptables UFW
Complexity High Low
Flexibility Complete Limited to common scenarios
Rate limiting ✅ Built-in Basic
Custom chains ✅ Yes No
Best for Advanced production servers Quick setup, basic protection

UFW actually manages iptables rules underneath — you can use both together, but be careful about rule ordering conflicts.


Final Thoughts

A properly configured iptables ruleset gives you complete control over network traffic entering and leaving your VPS. The production ruleset in this guide — default deny, stateful connection tracking, rate limiting on SSH and HTTP, and persistent configuration — provides strong baseline protection against the most common attack vectors.

Fast • Reliable • Affordable VPS - DO It Now!

Get top VPS hosting with VPS.DO’s fast, low-cost plans. Try risk-free with our 7-day no-questions-asked refund and start today!