Mastering iptables & ipset

Learn how to filter traffic, track connections, use stateful firewalls, and manage bulk IP sets in Linux.

Protect your system and control network traffic by mastering netfilter, iptables syntax, and high-performance ipset tables.


Difficulty: Intermediate
Estimated reading time: 40 min


What Is iptables?

Linux kernels include a powerful firewall framework called:

netfilter

The tool we use to interact with this framework is called iptables.

It inspects, modifies, redirects, or drops network packets based on rules you define.

To understand iptables, you must understand its three main pillars:

  • Tables
  • Chains
  • Matches & Targets

The Basic Command Structure

Every iptables rule follows a strict logical structure:

iptables [-t table_name] -COMMAND CHAIN_NAME matches -j TARGET

If you do not specify a table, Linux defaults to the filter table.


Component Breakdown

Here is a bird’s-eye view of everything available inside the firewall:

Table Command CHAIN matches Target/Jump
filter (default) -A (append) INPUT -s source_ip ACCEPT
nat -I (insert) OUTPUT -d dest_ip DROP
mangle -D (delete) FORWARD -p protocol REJECT
raw -R (replace) PREROUTING --sport source_p LOG
-F (flush) POSTROUTING --dport dest_p SNAT
-Z (zero) USER_DEFINED -i incoming_int DNAT
-L (list) -o outgoing_int MASQUERADE
-S (show) -m mac LIMIT
-N -m time RETURN
-X -m quota TEE

Core Command Flags

These are the operational switches you will use every day:

Flag Meaning
-A Append rule to the end of the chain
-I Insert rule at a specific position (default is position 1)
-L List all rules in the selected chain
-F Flush (delete) all rules in the selected chain
-Z Zero out packet and byte counters
-P Set the default policy for a built-in chain
-D Delete a specific rule from a chain
-N Create a new user-defined chain
-X Delete a custom user-defined chain
-v Verbose mode (shows counters and interfaces)
-n Numeric mode (stops slow reverse DNS lookups)

Essential Administrative Commands

Let us look at how you manage the lifecycle of your firewall rules.


Appending vs Inserting

Appending puts the rule at the bottom. Inserting puts it at the top.

# Append to the very end of OUTPUT
iptables -A OUTPUT -p tcp --dport 443 -j DROP
 
# Insert at the 1st position of OUTPUT
iptables -I OUTPUT -p tcp --dport 443 -d ://linux.com -j ACCEPT

Order matters.

Firewalls read rules from top to bottom.

The first rule that matches a packet wins.


Listing and Flushing Rules

To see what is running inside your firewall, use these options:

# List all chains in the default filter table
iptables -t filter -L

# List with counters, interfaces, and numeric IPs (highly recommended)
iptables -vnL

# List just a specific chain
iptables -vnL INPUT

# List rules from a non-default table
iptables -t nat -vnL

To clean up your rules:

# Flush the OUTPUT chain inside the filter table
iptables -t filter -F OUTPUT

# Zero out packet and byte statistics
iptables -t filter -Z

Understanding Default Policies

What happens when a packet does not match any rule in a chain?

It hits the Default Policy.

By default, all built-in chains are set to ACCEPT.

You can change this behavior with the -P option.

# Set default policy to drop everything coming in
iptables -P INPUT DROP

# Set default policy to drop everything passing through
iptables -P FORWARD DROP

# Set default policy to let everything out
iptables -P OUTPUT ACCEPT

Warning: Change default policies with caution! If you set INPUT to DROP via SSH without an explicit allow rule, you will immediately lock yourself out.


How to Completely Reset a Firewall

If your rules become messy and you want a fresh start, use this script sequence to reset everything to an open state:

#!/bin/bash

# 1. Set default policy to ACCEPT everywhere
iptables -P INPUT ACCEPT
iptables -P OUTPUT ACCEPT
iptables -P FORWARD ACCEPT

# 2. Flush all tables completely
iptables -t filter -F
iptables -t nat -F
iptables -t mangle -F

# 3. Delete any custom user-defined chains
iptables -X

Filtering by IP and Address Type

The most basic firewall task is blocking or allowing specific systems.


IP and Network Matching

You can specify a single IP or an entire CIDR network block using -s (source) and -d (destination).

# Reset firewall
iptables -F

# Drop all traffic coming from a specific malicious IP
iptables -A INPUT -s 100.0.0.1 -j DROP

# Accept SSH traffic only from a trusted subnet
iptables -A INPUT -s 80.0.0.0/16 -p tcp --dport 22 -j ACCEPT

# Block outgoing traffic to a specific web server
iptables -A OUTPUT -p tcp --dport 443 -d ://ubuntu.com -j DROP

IP Range Matching

Sometimes networks are not organized in neat subnets.

You can use the iprange module to target precise start and end addresses.

# Replaces 9 individual rules with a single range match
iptables -A INPUT -p tcp --dport 25 -m iprange --src-range 10.0.0.10-10.0.0.18 -j DROP

Address Type Matching

You can also filter packets based on how they behave on the network topology using -m addrtype.

# Drop all outgoing multicast traffic
iptables -A OUTPUT -m addrtype --dst-type MULTICAST -j DROP

To see all valid address types available on your system, run:

iptables -m addrtype --help

Filtering by Ports, Protocols, and Interfaces

Fine-tuning your security requires identifying specific applications and hardware interfaces.

# Flush input rules before applying
iptables -F INPUT

Port Restrictions

# Allow SSH connections only from one trusted IP
iptables -A INPUT -p tcp --dport 22 -s 80.0.0.1 -j ACCEPT
 
# Drop all other SSH traffic trying to get in
iptables -A INPUT -p tcp --dport 22 -j DROP
 
# Secure DNS resolution queries
iptables -A OUTPUT -p udp --dport 53 -j ACCEPT
iptables -A INPUT -p udp --sport 53 -j ACCEPT

Protocol Restrictions

You can match against standard protocol names or system numbers using -p.

# Drop incoming GRE tunnel traffic
iptables -A INPUT -p gre -j DROP
 
# Block outgoing ICMP (ping) traffic
iptables -A OUTPUT -p icmp -j DROP

Interface Filtering

Use -i for incoming hardware interfaces and -o for outgoing ones.

# Always allow local loopback traffic (Crucial for system stability!)
iptables -A INPUT -i lo -j ACCEPT
iptables -A OUTPUT -o lo -j ACCEPT
 
# Drop SSH requests arriving via your public interface
iptables -A INPUT -p tcp --dport 22 -i eth0 -j DROP
 
# Allow SSH requests arriving via your internal local network interface
iptables -A INPUT -p tcp --dport 22 -i eth1 -j ACCEPT

Negating Matches

You can use an exclamation mark ! to invert any match criteria.

This means “match everything except this”.

# Drop all incoming SSH except traffic coming from your management station
iptables -A INPUT -p tcp --dport 22 ! -s 100.0.0.1 -j DROP
 
# Drop all outgoing HTTPS except to your favorite website
iptables -A OUTPUT -p tcp --dport 443 ! -d ://linux.com -j DROP
 
# Drop all local network traffic except communication with your default gateway
iptables -A INPUT -m mac ! --mac-source b4:6d:83:77:85:f4 -j DROP

Advanced TCP Flags

Advanced administrators can match specific TCP handshake conditions.

# Drop all incoming TCP packets that have the SYN flag set
iptables -A INPUT -p tcp --syn -j DROP

# Log outgoing packets that contain both SYN and ACK flags
iptables -A OUTPUT -p tcp --tcp-flags syn,ack,rst,fin syn,ack -j LOG

Connection Tracking (Stateful Firewall)

A stateful firewall tracks active conversations.

This is much more secure than checking every packet in isolation.

Netfilter categorizes packets into five distinct states:

State Description
NEW The first packet initiating a brand new connection
ESTABLISHED Packets belonging to an already active connection
RELATED Packets starting a new connection linked to an existing one (e.g., FTP data)
INVALID Packets that cannot be identified or mapped to any known track
UNTRACKED Packets deliberately skipped using the NOTRACK target in the raw table

Desktop Stateful Firewall Template

This script shows a secure blueprint for configuring an standard workstation firewall:

#!/bin/bash

# 1. Clear out everything
iptables -F 

# 2. Trust internal loopback traffic
iptables -A INPUT -i lo -j ACCEPT
iptables -A OUTPUT -o lo -j ACCEPT

# 3. Drop all broken or invalid packets immediately
iptables -A INPUT -m state --state INVALID -j DROP
iptables -A OUTPUT -m state --state INVALID -j DROP

# 4. Inbound policy: Only allow packets that you explicitly requested
iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT

# 5. Outbound policy: Allow your apps to open new web connections
iptables -A OUTPUT -m state --state NEW,ESTABLISHED,RELATED -j ACCEPT

# 6. Apply strict fallback policies
iptables -P INPUT DROP
iptables -P OUTPUT DROP

Filtering by MAC, Time, and Quotas

Beyond IP addresses and ports, netfilter provides modules to filter traffic based on physical hardware, real-world scheduling, or data usage limits.


MAC Address Matching

#!/bin/bash
iptables -F INPUT

# Define a list of allowed hardware addresses
PERMITTED_MACS="08:00:27:15:b2:ec 08:00:27:15:b2:df 08:00:27:15:b2:ab"

# Loop through and explicitly allow them
for MAC in $PERMITTED_MACS
do
	iptables -A INPUT -m mac --mac-source $MAC -j ACCEPT
done

# Block everyone else
iptables -P INPUT DROP

Date and Time Constraints

The time module interprets hours in UTC by default. To use your host system clock timezone instead, apply the --kerneltz flag.

# Permit SSH access only during standard working hours (8:00 AM - 6:00 PM UTC)
iptables -A INPUT -p tcp --dport 22 -m time --timestart 8:00 --timestop 18:00 -j ACCEPT
iptables -A INPUT -p tcp --dport 22 -j DROP

Rate Limiting Traffic

Protect your server from floods and Denial of Service (DoS) attacks by enforcing limits on packet frequency.

# Allow only 1 incoming ping per second with an initial burst allowance of 3
iptables -A INPUT -p icmp --icmp-type echo-request -m limit --limit 1/sec --limit-burst 3 -j ACCEPT
iptables -A INPUT -p icmp --icmp-type echo-request -j DROP

To test rate limiting from a remote testing box, simulate a high-frequency ping burst:

ping -i 0.1 192.168.0.1

Dynamic Blacklists (The Recent Match)

You can create dynamic pools of bad actors using the recent module.

# Check if an incoming IP is already blacklisted; drop and renew its 60-second penalty
iptables -A INPUT -m recent --name hackers --update --seconds 60 -j DROP

# Catch attackers trying to hit port 25 during off-hours, add them to the blacklist pool
iptables -A INPUT -p tcp --dport 25 -m time --timestart 8:00 --timestop 10:00 -m recent --name hackers --set -j DROP

You can view your active live database of banned IP locations at any time inside the /proc filesystem:

cat /proc/net/xt_recent/hackers

Data Quota Limits

Enforce hard limits on total data consumption measured in raw bytes.

# Allow downloading up to 1GB of web traffic, then trigger a drop policy
iptables -A FORWARD -o eth0 -p tcp --sport 80 -m quota --quota 1000000000 -j ACCEPT
iptables -A FORWARD -o eth0 -p tcp --sport 80 -j DROP

Scaling Up Performance with ipset

When you need to block thousands of IP addresses, standard iptables rulesets become slow. Every single packet has to traverse thousands of linear lines of rules.

Solution: Use ipset.

ipset stores addresses inside highly optimized framework hash tables. Checking an IP takes a constant fraction of a millisecond, regardless of whether your list contains 5 addresses or 50,000.


Essential ipset Management

# Create a new hash list to hold unique IP addresses
ipset -N myset hash:ip -exist

# Add specific IP targets to your newly generated list
ipset -A myset 1.2.3.4
ipset -A myset 4.3.2.1

# Reference the entire ipset inside a single iptables rule
iptables -A INPUT -m set --match-set myset src -j DROP

Listing and Housekeeping Sets

# View all active collections and their metrics
ipset list

# Delete a single member from a collection
ipset -D myset 1.2.3.4

# Flush all contents out of a set without deleting the table structure
ipset -F myset

# Destroy the set entirely
ipset -X myset

Performance Tip: Bulk Loading Sets

Reading lines sequentially using a bash loop is slow. For massive text records containing block lists, pipe your data through sed directly into ipset restore:

sed "s/^/add china /" "bad_hosts.txt" | ipset restore

Understanding Targets: Actions Taken

When a packet satisfies your rule conditions, it triggers a Target.


Terminating Targets

Terminating targets stop any further rule evaluation in that table. The packet’s fate is sealed right there.

  • ACCEPT: Allows the packet through to its destination.
  • DROP: Silently swallows the packet. The sender gets no response and waits until a timeout occurs.
  • REJECT: Blocks the packet and actively transmits an error response back to the sender.
# Reject a packet cleanly by sending a TCP reset signal back
iptables -A INPUT -p tcp --dport 22 -j REJECT --reject-with tcp-reset

Non-Terminating Targets

Non-terminating targets perform an administrative action and then pass the packet down to the next rule in line.

  • LOG: Records detailed telemetry metrics regarding packet headers directly to syslog or dmesg.
  • TEE: Clones a packet and forwards the twin to another server on your subnet (great for traffic monitoring).
# Log matching telemetry with an easily recognizable custom tag prefix
iptables -A INPUT -p tcp --syn --dport 22 -j LOG --log-prefix="##ssh:" --log-level info

Network Address Translation (SNAT & MASQUERADE)

Linux can act as a fully functional network router that shares a public internet connection across an internal private local area network.


Source NAT (SNAT)

Used when your router has a permanent, fixed static public IP address.

# 1. Enable IP routing at the core kernel level
echo "1" > /proc/sys/net/ipv4/ip_forward

# 2. Map private subnets out through your external WAN card
iptables -t nat -A POSTROUTING -s 10.0.0.0/24 -o enp0s3 -j SNAT --to-source 80.0.0.1

MASQUERADE

Used when your public WAN IP changes dynamically (such as a standard home ISP connection assignment via DHCP).

iptables -t nat -A POSTROUTING -s 10.0.0.0/24 -o enp0s3 -j MASQUERADE

Destination NAT: Port Forwarding (DNAT)

Expose servers hidden inside your private internal local network securely out to the public internet interface.

# Forward incoming public traffic hitting port 80 over to an internal web server
iptables -t nat -A PREROUTING -p tcp --dport 80 -j DNAT --to-destination 192.168.0.20

# Redirect alternative external ports to standard service ports internally
iptables -t nat -A PREROUTING -p tcp --dport 8080 -j DNAT --to-destination 192.168.0.20:80

Custom User-Defined Chains

As rulesets grow, management becomes tedious. You can group rules into custom categories (e.g., handling web services separately from database policies) using custom chains.

# 1. Create a custom organizational chain container
iptables -N TCP_TRAFFIC

# 2. Add structural rules directly into your custom chain container
iptables -A TCP_TRAFFIC -p tcp --dport 80 -j ACCEPT
iptables -A TCP_TRAFFIC -p tcp --dport 443 -j ACCEPT

# 3. Direct main traffic into your custom chain from the primary INPUT path
iptables -A INPUT -p tcp -j TCP_TRAFFIC

If a packet traverses your custom chain without matching any rule inside it, control safely loops back to the primary calling chain to continue evaluating remaining policies.


Network Diagnostic Cheat Sheet

When configuring your firewall, you need to verify if ports are open and rules are working. Use these critical diagnostic commands from your testing boxes:

# Monitor all active networking ports and running program handles
netstat -tupan

# Run a secure SYN stealth port discovery map against a remote machine
nmap -sS 192.168.0.1

# Scan every possible available connection port on a target box
nmap -p- 192.168.0.1

# Probe active targets to detect their underlying operating system distribution
nmap -O 192.168.0.1