Back to Learn

Create Default Blocks with IPTables | NOC.org

The Default-Deny Approach

A secure firewall starts with a simple principle: deny everything by default, then explicitly allow only the traffic your server needs. This is known as a "default-deny" or "whitelist" firewall policy. Rather than trying to enumerate every possible threat and block it individually (a "default-allow" or "blacklist" approach), you start with all traffic blocked and selectively open ports and protocols as required.

The default-deny approach is a fundamental Linux security best practice because it protects against unknown threats. If a new service accidentally starts listening on a port, or if an attacker tries to connect to an unexpected service, the firewall blocks it automatically because it was never explicitly allowed. This dramatically reduces your attack surface.

Setting the Default DROP Policy

IPTables has three built-in chains for the filter table: INPUT (incoming traffic), OUTPUT (outgoing traffic), and FORWARD (routed traffic). Each chain has a default policy that determines what happens to packets that do not match any rule in the chain.

# Set the default policy to DROP for all chains
iptables -P INPUT DROP
iptables -P FORWARD DROP
iptables -P OUTPUT DROP

Critical warning: Setting the INPUT policy to DROP without first allowing SSH (or whatever management protocol you use) will lock you out of the server immediately. Always add your SSH allow rule before changing the default policy, or run the commands as a single atomic script. If you are working on a remote server, have out-of-band access (console, IPMI, or KVM) available as a safety net.

DROP vs. REJECT

The DROP target silently discards packets without sending any response to the sender. The REJECT target sends an ICMP error message back to the sender, informing them that the connection was refused. For a default policy:

  • DROP (recommended for default policy): Does not reveal information about your firewall to potential attackers. Scanners cannot easily distinguish between a filtered port and a non-existent host.
  • REJECT (useful for specific rules): Provides faster feedback to legitimate clients that accidentally try a closed port, and prevents timeout delays. Useful in internal networks or for specific application ports where you want clear error messages.

Allowing Established Connections

Before blocking everything, you must allow existing connections to continue functioning. Without this rule, setting a DROP policy would immediately terminate all current connections, including your SSH session:

# Allow established and related connections (INPUT)
iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT

# Allow established and related connections (OUTPUT)
iptables -A OUTPUT -m state --state ESTABLISHED,RELATED -j ACCEPT

The ESTABLISHED state matches packets belonging to an already-established connection (i.e., packets that have been seen going in both directions). The RELATED state matches packets that are related to an existing connection, such as ICMP error messages or FTP data connections.

This is the most important rule in your firewall configuration. It must be placed near the top of the chain for both performance (most packets match this rule, so checking it first avoids unnecessary processing) and correctness (without it, responses to allowed outbound connections would be dropped).

Allowing Loopback Traffic

The loopback interface (lo) handles local inter-process communication. Many applications depend on it, including database connections (MySQL listening on 127.0.0.1), local DNS resolution, and application health checks. Blocking loopback traffic will break many services:

# Allow all traffic on the loopback interface
iptables -A INPUT -i lo -j ACCEPT
iptables -A OUTPUT -o lo -j ACCEPT

This must also be placed near the top of your rules, before any DROP rules that might inadvertently match loopback traffic.

Whitelisting Essential Services

SSH

# Allow incoming SSH connections
iptables -A INPUT -p tcp --dport 22 -j ACCEPT

# Or restrict SSH to specific IP ranges
iptables -A INPUT -p tcp --dport 22 -s 203.0.113.0/24 -j ACCEPT \
  -m comment --comment "SSH: office network"
iptables -A INPUT -p tcp --dport 22 -s 10.8.0.0/24 -j ACCEPT \
  -m comment --comment "SSH: VPN clients"

For production servers, restricting SSH to known IP ranges is strongly recommended. Consider also implementing rate limiting for SSH to protect against brute force attacks.

HTTP and HTTPS

# Allow incoming web traffic
iptables -A INPUT -p tcp --dport 80 -j ACCEPT \
  -m comment --comment "HTTP: public web server"
iptables -A INPUT -p tcp --dport 443 -j ACCEPT \
  -m comment --comment "HTTPS: public web server"

DNS

# Allow outbound DNS queries (needed for the server to resolve domain names)
iptables -A OUTPUT -p udp --dport 53 -j ACCEPT
iptables -A OUTPUT -p tcp --dport 53 -j ACCEPT

# If running a DNS server, allow incoming DNS
iptables -A INPUT -p udp --dport 53 -j ACCEPT
iptables -A INPUT -p tcp --dport 53 -j ACCEPT

NTP (Time Synchronization)

# Allow outbound NTP for time synchronization
iptables -A OUTPUT -p udp --dport 123 -j ACCEPT

ICMP (Ping)

# Allow incoming ping (useful for monitoring)
iptables -A INPUT -p icmp --icmp-type echo-request -j ACCEPT
iptables -A OUTPUT -p icmp --icmp-type echo-reply -j ACCEPT

# Allow outbound ping
iptables -A OUTPUT -p icmp --icmp-type echo-request -j ACCEPT
iptables -A INPUT -p icmp --icmp-type echo-reply -j ACCEPT

Complete Default Ruleset: Web Server

Here is a complete default-deny ruleset for a standard web server. This example includes comments for documentation:

#!/bin/bash
# Flush existing rules and chains
iptables -F
iptables -X

# Set default policies to DROP
iptables -P INPUT DROP
iptables -P FORWARD DROP
iptables -P OUTPUT DROP

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

# Allow established and related connections
iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
iptables -A OUTPUT -m state --state ESTABLISHED,RELATED -j ACCEPT

# Allow incoming SSH (restrict to your IP range in production)
iptables -A INPUT -p tcp --dport 22 -j ACCEPT \
  -m comment --comment "SSH: remote management"

# Allow incoming HTTP and HTTPS
iptables -A INPUT -p tcp --dport 80 -j ACCEPT \
  -m comment --comment "HTTP: public web server"
iptables -A INPUT -p tcp --dport 443 -j ACCEPT \
  -m comment --comment "HTTPS: public web server"

# Allow outbound DNS
iptables -A OUTPUT -p udp --dport 53 -j ACCEPT \
  -m comment --comment "DNS: outbound queries (UDP)"
iptables -A OUTPUT -p tcp --dport 53 -j ACCEPT \
  -m comment --comment "DNS: outbound queries (TCP)"

# Allow outbound NTP
iptables -A OUTPUT -p udp --dport 123 -j ACCEPT \
  -m comment --comment "NTP: time synchronization"

# Allow outbound HTTP/HTTPS (for package updates, APIs, etc.)
iptables -A OUTPUT -p tcp --dport 80 -j ACCEPT \
  -m comment --comment "HTTP: outbound (updates, APIs)"
iptables -A OUTPUT -p tcp --dport 443 -j ACCEPT \
  -m comment --comment "HTTPS: outbound (updates, APIs)"

# Allow outbound SMTP (if the server sends email)
iptables -A OUTPUT -p tcp --dport 25 -j ACCEPT \
  -m comment --comment "SMTP: outbound mail"
iptables -A OUTPUT -p tcp --dport 587 -j ACCEPT \
  -m comment --comment "SMTP submission: outbound mail"

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

# Log dropped packets (optional but recommended)
iptables -A INPUT -j LOG --log-prefix "IPT-INPUT-DROP: " --log-level 4 \
  -m comment --comment "Log dropped input packets"
iptables -A OUTPUT -j LOG --log-prefix "IPT-OUTPUT-DROP: " --log-level 4 \
  -m comment --comment "Log dropped output packets"

Complete Default Ruleset: Database Server

#!/bin/bash
# Flush existing rules
iptables -F
iptables -X

# Default DROP policies
iptables -P INPUT DROP
iptables -P FORWARD DROP
iptables -P OUTPUT DROP

# Loopback
iptables -A INPUT -i lo -j ACCEPT
iptables -A OUTPUT -o lo -j ACCEPT

# Established connections
iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
iptables -A OUTPUT -m state --state ESTABLISHED,RELATED -j ACCEPT

# SSH (restricted to management network)
iptables -A INPUT -p tcp --dport 22 -s 10.0.0.0/24 -j ACCEPT \
  -m comment --comment "SSH: management network only"

# MySQL (restricted to application servers)
iptables -A INPUT -p tcp --dport 3306 -s 10.0.1.0/24 -j ACCEPT \
  -m comment --comment "MySQL: app server subnet"

# DNS and NTP outbound
iptables -A OUTPUT -p udp --dport 53 -j ACCEPT
iptables -A OUTPUT -p tcp --dport 53 -j ACCEPT
iptables -A OUTPUT -p udp --dport 123 -j ACCEPT

# Package updates
iptables -A OUTPUT -p tcp --dport 80 -j ACCEPT
iptables -A OUTPUT -p tcp --dport 443 -j ACCEPT

# ICMP
iptables -A INPUT -p icmp --icmp-type echo-request -j ACCEPT
iptables -A OUTPUT -p icmp -j ACCEPT

# Log dropped packets
iptables -A INPUT -j LOG --log-prefix "IPT-INPUT-DROP: " --log-level 4
iptables -A OUTPUT -j LOG --log-prefix "IPT-OUTPUT-DROP: " --log-level 4

Saving Rules with iptables-save

IPTables rules exist only in kernel memory and are lost when the system reboots. You must save them to a file for persistence:

# Save current rules to a file
iptables-save > /etc/iptables/rules.v4

# View the saved rules
cat /etc/iptables/rules.v4

The output of iptables-save is a text file in a specific format that includes all tables, chains, rules, and counters. It looks like:

# Generated by iptables-save
*filter
:INPUT DROP [0:0]
:FORWARD DROP [0:0]
:OUTPUT DROP [0:0]
-A INPUT -i lo -j ACCEPT
-A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT
-A INPUT -p tcp -m tcp --dport 22 -m comment --comment "SSH: remote management" -j ACCEPT
-A INPUT -p tcp -m tcp --dport 80 -m comment --comment "HTTP: public web server" -j ACCEPT
-A INPUT -p tcp -m tcp --dport 443 -m comment --comment "HTTPS: public web server" -j ACCEPT
COMMIT

Restoring Rules with iptables-restore

To load rules from a saved file:

# Restore rules from file
iptables-restore < /etc/iptables/rules.v4

# Test restore without applying (check for syntax errors)
iptables-restore --test < /etc/iptables/rules.v4

Automatic Restore on Boot

Different Linux distributions handle automatic rule restoration differently:

# Debian/Ubuntu: install iptables-persistent
apt install iptables-persistent
# Rules in /etc/iptables/rules.v4 are loaded automatically on boot
# Save changes with:
netfilter-persistent save

# RHEL/CentOS 7+: use the iptables service
systemctl enable iptables
service iptables save
# Rules saved in /etc/sysconfig/iptables

# Any distro: create a systemd service or use a cron @reboot job
# /etc/systemd/system/iptables-restore.service
[Unit]
Description=Restore iptables rules
Before=network-pre.target
[Service]
Type=oneshot
ExecStart=/sbin/iptables-restore /etc/iptables/rules.v4
[Install]
WantedBy=multi-user.target

Order of Rules Matters

IPTables processes rules in order from top to bottom within each chain. The first matching rule determines the packet's fate. This means rule ordering is critical:

# CORRECT: Specific allow rules before general drop
iptables -A INPUT -p tcp --dport 22 -s 203.0.113.10 -j ACCEPT    # Rule 1: Allow SSH from admin
iptables -A INPUT -p tcp --dport 22 -j DROP                       # Rule 2: Block all other SSH

# WRONG: General drop before specific allow (admin IP is blocked!)
iptables -A INPUT -p tcp --dport 22 -j DROP                       # Rule 1: Block ALL SSH
iptables -A INPUT -p tcp --dport 22 -s 203.0.113.10 -j ACCEPT    # Rule 2: Never reached!

Best practice ordering for the INPUT chain:

  1. Loopback allow — Always first, handles local traffic
  2. Established/related allow — Handles existing connections efficiently
  3. Rate limiting rules — Applied before accept rules for rate-limited services
  4. Service-specific allow rules — SSH, HTTP, HTTPS, etc.
  5. ICMP rules — Ping and other ICMP types
  6. Logging rules — Log before the default drop
  7. Default policy (DROP) — Catches everything else

Testing Your Firewall

After implementing your ruleset, verify it thoroughly:

# List all rules with line numbers and counters
iptables -L -n -v --line-numbers

# Test from an external machine
# Test SSH access
ssh user@server-ip

# Test HTTP/HTTPS
curl -I http://server-ip
curl -I https://server-ip

# Test that blocked ports are actually blocked
nc -zv server-ip 3306   # Should timeout or be refused
nc -zv server-ip 6379   # Should timeout or be refused

# Check specific chain
iptables -L INPUT -n -v --line-numbers
iptables -L OUTPUT -n -v --line-numbers

Emergency Recovery

If you lock yourself out by setting a DROP policy without allowing SSH:

  • Console access: Use IPMI, iDRAC, iLO, KVM, or your hosting provider's console to access the server directly and fix the rules.
  • Reboot: If you have not saved the rules with iptables-save, rebooting the server will clear all IPTables rules and restore the default ACCEPT policy.
  • Cron safety net: Before making changes, add a cron job that flushes IPTables rules after a few minutes: echo "*/5 * * * * root /sbin/iptables -F" >> /etc/crontab. Remove it once you confirm the rules work correctly.
  • at command: Schedule a rule flush with at: echo "/sbin/iptables -F" | at now + 5 minutes

Summary

A default-deny IPTables firewall is a fundamental security control for any Linux server. Start by allowing loopback and established connections, whitelist the specific services your server needs, set the default policy to DROP, and save your rules for persistence across reboots. Always test thoroughly and have a recovery plan before changing firewall policies on remote servers. Combined with rate limiting, proper commenting, and regular audits, a well-configured default-deny firewall significantly reduces your server's attack surface and forms a critical part of your Linux security checklist.

Need help securing your server infrastructure? Explore NOC.org plans to get started.

Improve Your Websites Speed and Security

14 days free trial. No credit card required.