Mastering Linux Netfilter: How to Build Custom iptables Rules and NAT
This guide explains Linux's built‑in netfilter firewall framework, its five hook points, the relationship between tables and chains, and how to use iptables (and firewalld) to define, view, modify, and persist traffic‑filtering rules, implement NAT, and create custom chains for advanced network security.
netfilter introduction
Firewalls are software or hardware that filter traffic between internal and external networks according to defined policies. Linux kernels include the netfilter firewall module, which enables packet filtering, NAT, and connection tracking.
User‑space tools such as iptables and firewalld allow administrators to define rules that are passed to the netfilter framework for packet processing.
netfilter five hooks
Rules defined in user‑space are associated with one of netfilter's five hook functions. When a packet traverses the network stack and reaches a hook point, netfilter processes the packet according to the rules linked to that hook.
These five hook functions are located at different points in the network stack:
1. PREROUTING : the first point a packet reaches, before routing decisions.
2. INPUT : handles packets destined for the local system.
3. FORWARD : handles packets that will be routed onward (neither destined for nor originating from the local system).
4. OUTPUT : handles packets generated by the local system before they leave.
5. POSTROUTING : captures packets after routing, just before they exit the system.
iptables
The most common user‑space firewall management tool is iptables ; alternatives include firewalld and nettable .
Before writing filtering rules with iptables, understand three core concepts: rules , chains , and tables .
Rule : a matching strategy composed of match conditions plus an action.
Chain : an ordered list of rules. Packets are examined sequentially until a match is found or the chain ends.
Table : groups chains by functionality (e.g., filtering, NAT).
A packet first enters a specific table, then traverses the chains within that table in order until a matching rule is found or the end is reached.
Default hook‑chain mapping : each of netfilter's five hooks has a default chain with the same name (e.g., the INPUT hook uses the INPUT chain).
filter table : primary packet‑filtering table, containing chains like INPUT, OUTPUT, and FORWARD.
nat table : handles network address translation, associated with PREROUTING and POSTROUTING chains.
mangle table : used for specialized packet modifications; contains all five standard chains.
raw table : configures exceptions to bypass connection tracking; linked to PREROUTING and OUTPUT.
Implementing traffic filtering
Define rules
<code>iptables -t <table> -I| -A <chain> <match conditions> -j <action></code>Explanation:
-Iinserts the rule at the beginning of the chain (matched first).
-Aappends the rule to the end of the chain (matched later).
Example: drop all packets from host
10.0.0.11:
<code>iptables -t filter -I INPUT -s 10.0.0.11 -j DROP</code>Example: drop packets whose destination is
10.0.0.11:
<code>iptables -A INPUT -d 10.0.0.11 -j DROP</code>Match conditions are divided into basic matches (address, interface, protocol) and extended matches (requiring additional modules via
-m).
Basic address match:
-sfor source,
-dfor destination.
Interface match:
-ifor incoming interface,
-ofor outgoing.
Extended matches include port matching (
multiportmodule) and protocol matching (
tcp,
udp,
icmpmodules).
Example: block ports 22, 80, 1884, 1883 for source
10.0.0.29:
<code>iptables -t filter -I INPUT -s 10.0.0.29 -p tcp -m multiport --ports 22,80,1884,1883 -j DROP</code>Supported actions: ACCEPT (allow), DROP (silently discard), REJECT (reject with notification).
View rules
<code>iptables -t <table> -vnL</code>Delete rules
Use
-Dwith either the rule number or the exact rule specification.
<code>iptables -t filter -D INPUT 1</code> <code>iptables -t filter -D INPUT -s 10.0.0.11 -j DROP</code>Flush a chain
Remove all rules from a chain with
-F(defaults to the
filtertable if none is specified).
<code>iptables -t filter -F INPUT</code>Change default policy
<code>iptables -P <chain> <policy></code>Example: set the default policy of the INPUT chain in the filter table to DROP:
<code>iptables -t filter -P INPUT DROP</code>Implement black/white lists
Black list: set default policy to ACCEPT and add DROP/REJECT rules for unwanted traffic. White list: set default policy to DROP and add ACCEPT rules for allowed traffic.
Example: allow only ports 22, 80, 8000, 8001, 1883, 1884, 9001, 9100, 9802 for both TCP and UDP:
<code># Ensure current remote connection stays alive
iptables -t filter -I INPUT -s 10.0.0.1 -j ACCEPT
# Change default INPUT policy to DROP
iptables -t filter -P INPUT DROP
# Allow specified ports for TCP
iptables -t filter -A INPUT -m multiport -p tcp --dports 22,8000,8001,80,1883,1884,9001,9100,9802 -j ACCEPT
# Allow specified ports for UDP
iptables -t filter -A INPUT -m multiport -p udp --dports 22,8000,8001,80,1883,1884,9001,9100,9802 -j ACCEPT</code>Persist rules
Rules added with iptables are lost after a reboot. Save them with
iptables-saveand restore with
iptables-restore, or create a systemd service.
<code>sudo iptables-save > /path/to/iptables.rules</code> <code>sudo iptables-restore < /path/to/iptables.rules</code> <code>[Unit]
Description=Restore iptables rules
After=network.target
[Service]
Type=oneshot
ExecStart=/sbin/iptables-restore /path/to/iptables.rules
RemainAfterExit=yes
[Install]
WantedBy=multi-user.target</code>Implement NAT
NAT (Network Address Translation) translates private IP addresses to public ones and vice versa. It is typically performed by routers, firewalls, or dedicated NAT devices.
Private address ranges:
A class: 10.0.0.0/8
B class: 172.16.0.0/12
C class: 192.168.0.0/16
NAT types:
SNAT : changes the source address (used when internal hosts access the Internet).
DNAT : changes the destination address (used to forward incoming traffic to an internal host).
PNAT/PAT : port‑address translation; modifies both source address and source port, allowing many internal hosts to share a single public IP.
SNAT example
<code># Replace source address for packets leaving the LAN
iptables -t nat -A POSTROUTING -s 10.0.0.0/24 ! -d 10.0.0.0/24 -j SNAT --to-source 172.18.1.6-172.18.1.9</code>When the public IP is dynamic, use MASQUERADE:
<code>iptables -t nat -A POSTROUTING -s $LOCALNET ! -d $LOCALNET -j MASQUERADE</code>DNAT example
<code>iptables -t nat -A PREROUTING -d 10.0.0.100 -p tcp --dport 80 -j DNAT --to-destination 192.168.1.100:80</code>PNAT (port redirection) example
<code># Method 1: DNAT to a different port
iptables -t nat -I PREROUTING -d 10.0.0.100 -p tcp --dport 80 -j DNAT --to-destination 192.168.1.100:8080
# Method 2: REDIRECT within the same host
iptables -t nat -A PREROUTING -d 172.16.100.10 -p tcp --dport 80 -j REDIRECT --to-ports 8080</code>Custom chains
Netfilter provides default chains for each hook, but you can create custom chains and reference them from default chains.
Create a custom chain
<code>iptables -t filter -N IN_WEB</code>Add rules to the custom chain (e.g., reject ICMP echo requests):
<code>iptables -t filter -I IN_WEB -p icmp --icmp-type 8/0 -j REJECT</code>Reference the custom chain from a default chain:
<code>iptables -t filter -I INPUT -j IN_WEB</code>Delete a custom chain
First flush the chain, then remove references, and finally delete it:
<code>iptables -F IN_WEB
iptables -t filter -D INPUT -j IN_WEB
iptables -X IN_WEB</code>Raymond Ops
Linux ops automation, cloud-native, Kubernetes, SRE, DevOps, Python, Golang and related tech discussions.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.