A network without security controls is an open door. Firewalls are the gatekeepers — they inspect traffic and enforce rules about what’s allowed in and out. Combined with NAT (Network Address Translation) for address management and DMZ (Demilitarized Zone) architecture for isolating public services, firewalls form the backbone of network security.
This chapter covers the theory and practical implementation of all three, with hands-on Linux examples.
A firewall examines network packets and decides whether to accept, drop, or reject them based on a set of rules. Rules typically match on:
| Type | Layer | Description |
|---|---|---|
| Packet filter | L3–L4 | Inspects individual packets (IP, port, protocol) |
| Stateful firewall | L3–L4 | Tracks connection state; allows related return traffic |
| Application firewall (WAF) | L7 | Inspects application-layer content (HTTP headers, payloads) |
| Next-gen firewall (NGFW) | L3–L7 | Combines stateful + application inspection + IDS/IPS |
Linux’s built-in firewall is a stateful packet filter operating at Layers 3–4.
iptables is the traditional Linux firewall tool. It’s been the standard since 2001 and remains widely used.
iptables organizes rules into chains within tables:
| Table | Purpose | Key Chains |
|---|---|---|
filter (default) |
Accept/drop packets | INPUT, FORWARD, OUTPUT |
nat |
Network Address Translation | PREROUTING, POSTROUTING, OUTPUT |
mangle |
Packet header modification | All chains |
raw |
Skip connection tracking | PREROUTING, OUTPUT |
The three main chains in the filter table:
# View current rules
sudo iptables -L -n -v
# Set default policies (drop everything, then whitelist)
sudo iptables -P INPUT DROP
sudo iptables -P FORWARD DROP
sudo iptables -P OUTPUT ACCEPT
# Allow loopback traffic
sudo iptables -A INPUT -i lo -j ACCEPT
# Allow established and related connections
sudo iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
# Allow SSH (port 22)
sudo iptables -A INPUT -p tcp --dport 22 -j ACCEPT
# Allow HTTP and HTTPS
sudo iptables -A INPUT -p tcp --dport 80 -j ACCEPT
sudo iptables -A INPUT -p tcp --dport 443 -j ACCEPT
# Allow ping (ICMP)
sudo iptables -A INPUT -p icmp --icmp-type echo-request -j ACCEPT
# Log and drop everything else
sudo iptables -A INPUT -j LOG --log-prefix "DROPPED: "
sudo iptables -A INPUT -j DROP
# Save current rules
sudo iptables-save > /etc/iptables/rules.v4
# Restore rules on boot
sudo iptables-restore < /etc/iptables/rules.v4
# On Debian/Ubuntu, install iptables-persistent for automatic restore
sudo apt install iptables-persistent
nftables replaces iptables with a cleaner syntax, better performance, and unified handling of IPv4, IPv6, and ARP in a single framework.
# List all rules
sudo nft list ruleset
# Create a table and chain
sudo nft add table inet filter
sudo nft add chain inet filter input { type filter hook input priority 0 \; policy drop \; }
# Allow loopback
sudo nft add rule inet filter input iif lo accept
# Allow established connections
sudo nft add rule inet filter input ct state established,related accept
# Allow SSH, HTTP, HTTPS
sudo nft add rule inet filter input tcp dport { 22, 80, 443 } accept
# Allow ICMP
sudo nft add rule inet filter input ip protocol icmp accept
sudo nft add rule inet filter input ip6 nexthdr icmpv6 accept
# Log dropped packets
sudo nft add rule inet filter input log prefix \"DROPPED: \" drop
For persistent rules, write a configuration file:
#!/usr/sbin/nft -f
# /etc/nftables.conf
flush ruleset
table inet filter {
chain input {
type filter hook input priority 0; policy drop;
iif lo accept
ct state established,related accept
tcp dport { 22, 80, 443 } accept
ip protocol icmp accept
ip6 nexthdr icmpv6 accept
log prefix "DROPPED: " drop
}
chain forward {
type filter hook forward priority 0; policy drop;
}
chain output {
type filter hook output priority 0; policy accept;
}
}
sudo systemctl enable nftables
sudo systemctl start nftables
With IPv4 address exhaustion, most organizations have only a few public IP addresses but many internal devices. NAT allows multiple devices on a private network to share a single public IP address by translating addresses at the network boundary.
| Type | Description | Use Case |
|---|---|---|
| SNAT (Source NAT) | Rewrites source IP of outgoing packets | Internal hosts accessing the Internet |
| DNAT (Destination NAT) | Rewrites destination IP of incoming packets | Port forwarding to internal servers |
| Masquerade | Dynamic SNAT using the outgoing interface’s IP | Home routers, dynamic public IPs |
| PAT (Port Address Translation) | Maps multiple internal IPs to one public IP using different ports | Most common form of NAT |
# Enable IP forwarding
sudo sysctl -w net.ipv4.ip_forward=1
# SNAT / Masquerade — internal hosts access the Internet
# eth0 = public interface, eth1 = private interface
sudo iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
# DNAT / Port forwarding — forward port 8080 to internal server
sudo iptables -t nat -A PREROUTING -i eth0 -p tcp --dport 8080 \
-j DNAT --to-destination 192.168.1.100:80
# Allow forwarded traffic
sudo iptables -A FORWARD -i eth1 -o eth0 -j ACCEPT
sudo iptables -A FORWARD -i eth0 -o eth1 -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
table ip nat {
chain prerouting {
type nat hook prerouting priority -100; policy accept;
tcp dport 8080 dnat to 192.168.1.100:80
}
chain postrouting {
type nat hook postrouting priority 100; policy accept;
oifname "eth0" masquerade
}
}
A DMZ is a network segment that sits between the public Internet and your internal (trusted) network. It hosts services that need to be publicly accessible — web servers, mail servers, DNS servers — while keeping the internal network protected.
The key principle: if a DMZ server is compromised, the attacker still cannot directly access the internal network.
The simplest DMZ design uses one firewall with three interfaces:
Internet
│
[Firewall]
/ │ \
[DMZ] [Internal]
Web Server Workstations
Mail Server Database
Firewall rules:
A more secure design uses two firewalls (ideally from different vendors to avoid a single vulnerability compromising both):
Internet
│
[Firewall 1] ← External firewall
│
[DMZ]
│
[Firewall 2] ← Internal firewall
│
[Internal Network]
This provides defense in depth — an attacker must breach two independent firewalls to reach the internal network.
Here’s a practical example with nftables. Assume three interfaces:
eth0 — Internet (public)eth1 — DMZ (172.16.0.0/24)eth2 — Internal (192.168.1.0/24)table inet filter {
chain forward {
type filter hook forward priority 0; policy drop;
# Internet → DMZ: allow HTTP/HTTPS only
iifname "eth0" oifname "eth1" tcp dport { 80, 443 } accept
# DMZ → Internet: allow (for updates, etc.)
iifname "eth1" oifname "eth0" ct state established,related accept
iifname "eth1" oifname "eth0" tcp dport { 80, 443 } accept
# Internal → DMZ: allow (management)
iifname "eth2" oifname "eth1" accept
# DMZ → Internal: deny (critical isolation)
iifname "eth1" oifname "eth2" drop
# Internal → Internet: allow
iifname "eth2" oifname "eth0" accept
# Return traffic for established connections
ct state established,related accept
}
}
Full example: code/dmz_nftables.conf
Beyond DMZ, network segmentation reduces blast radius when a breach occurs:
| Segment | Purpose | Access Policy |
|---|---|---|
| DMZ | Public-facing services | Internet access, no internal access |
| Internal/Corporate | User workstations | Internet via proxy, limited server access |
| Server/Data | Databases, app servers | No direct Internet, accessed from internal/DMZ |
| Management | Network devices, admin tools | Restricted to IT staff |
| IoT/OT | Sensors, cameras, industrial | Isolated, minimal connectivity |
Combine VLANs (Chapter 4) with firewall rules to enforce these boundaries.
You can automate firewall rule management with Python:
import subprocess
def add_allow_rule(port: int, protocol: str = "tcp") -> None:
"""Add an iptables rule to allow incoming traffic."""
cmd = [
"iptables", "-A", "INPUT",
"-p", protocol,
"--dport", str(port),
"-j", "ACCEPT"
]
subprocess.run(["sudo"] + cmd, check=True)
print(f"Allowed {protocol}/{port}")
Full example: code/firewall_manager.py
| ← Previous: Switching, Routing, and VLANs | Table of Contents | Next: TLS, Encryption, and Certificates → |