A DMZ is one of the most important network-security patterns to understand when turning a Linux system into a real router or firewall.
The basic idea is simple:
Internet-facing services should not live inside the same trusted network as internal users, databases, management systems, and administrative interfaces.
Instead, we create a separate security zone:
INTERNET
|
|
[ WAN ]
|
+---------------+
| Linux Router |
| nftables |
+---------------+
/ | \
/ | \
LAN DMZ MANAGEMENT
| | |
Users Web/DNS Admin Hosts But building a useful DMZ requires much more than creating another subnet.
The Linux router must correctly handle:
This guide builds that architecture step by step.
It extends the CorgiNetwork Linux nftables Inter-VLAN Firewall architecture and applies it to a realistic perimeter-security scenario.
A DMZ—demilitarized zone—is a network segment positioned between less-trusted networks and protected internal infrastructure.
For example:
Internet
|
v
+----------+
| Firewall |
+----------+
|
+-------- DMZ
|
+-------- Internal LAN The DMZ may contain services that need to receive connections originating outside the trusted LAN.
Examples include:
Web server
Reverse proxy
Public DNS server
Mail gateway
VPN gateway
Application gateway The important security principle is that compromising a DMZ server should not automatically provide unrestricted access to the internal network.
Consider:
INTERNET
|
v
WEB SERVER
|
v
INTERNAL LAN If the web server is compromised and has broad access to internal infrastructure, an attacker may gain a path toward:
Database servers
File servers
Backups
Management interfaces
Directory services
Monitoring systems
Administrator workstations A DMZ changes the trust relationship:
INTERNET
|
v
FIREWALL
|
v
DMZ
|
X
|
INTERNAL LAN Communication between zones must be explicitly permitted.
This follows the same default-deny principle used in our nftables Inter-VLAN Firewall guide.
We will use four security zones:
INTERNET
|
|
eth0/WAN
|
+----------------+
| |
| Linux Firewall |
| nftables |
| |
+----------------+
/ | \
/ | \
/ | \
VLAN 10 VLAN 20 VLAN 30
LAN DMZ MANAGEMENT Addressing:
LAN
192.168.10.0/24
DMZ
192.168.20.0/24
MANAGEMENT
192.168.30.0/24 Example DMZ web server:
192.168.20.10 Administrator workstation:
192.168.30.10 Linux router interfaces:
LAN gateway:
192.168.10.1
DMZ gateway:
192.168.20.1
Management gateway:
192.168.30.1 Before writing nftables commands, define the policy.
A reasonable starting point is:
| Source | Destination | Service | Action |
|---|---|---|---|
| Internet | DMZ Web | HTTP/HTTPS | Allow |
| Internet | LAN | Any | Deny |
| Internet | Management | Any | Deny |
| LAN | Internet | Required outbound | Allow |
| LAN | DMZ Web | HTTP/HTTPS | Allow |
| DMZ | LAN | Any | Deny |
| DMZ | Management | Any | Deny |
| Management | DMZ | SSH | Allow |
| Management | LAN Servers | Admin services | Allow |
This table is more important than the commands.
The firewall should implement an intentional security policy—not become the policy itself.
If the LAN, DMZ, and management networks share physical infrastructure, VLANs can provide Layer 2 separation.
Conceptually:
VLAN 10 = LAN
VLAN 20 = DMZ
VLAN 30 = Management If you need to build this layer from scratch, the CorgiNetwork Linux Bridge VLAN Filtering guide explains tagged traffic, untagged traffic, PVIDs, trunks, and access ports.
Our architecture then becomes:
Physical NIC
|
Trunk
|
Linux Bridge
|
VLAN Filtering
|
+----+----+----+
| | |
10 20 30
LAN DMZ MGMT VLANs isolate the networks.
Routing allows controlled communication between them.
That distinction is fundamental:
VLAN
=
Layer 2 segmentation
Routing
=
Layer 3 reachability
Firewall
=
Security policy The Linux Inter-VLAN Routing with Network Namespaces guide covers the routing layer in detail.
For our DMZ firewall, Linux becomes the Layer 3 gateway.
Linux must be allowed to forward packets between interfaces.
Check:
sysctl net.ipv4.ip_forward If the output is:
net.ipv4.ip_forward = 0 routing is disabled.
Enable it temporarily:
sudo sysctl -w net.ipv4.ip_forward=1 The Linux kernel documentation defines ip_forward as the boolean control for forwarding packets between interfaces and notes that it is disabled by default. See the official Linux IP sysctl documentation.
For persistence, configure the appropriate sysctl file for your Linux distribution rather than relying only on the temporary command.
Verify again:
sysctl net.ipv4.ip_forward Expected:
net.ipv4.ip_forward = 1 Check:
ip route The Linux router should know how to reach:
192.168.10.0/24
192.168.20.0/24
192.168.30.0/24 as directly connected networks.
Also confirm the default route:
ip route show default For example:
default via ISP_GATEWAY dev eth0 Test a specific routing decision:
ip route get 192.168.20.10 and:
ip route get 1.1.1.1 This distinction matters because nftables cannot repair an incorrect routing table.
If Linux selects the wrong egress NIC, use the CorgiNetwork Linux Traffic Uses the Wrong Interface troubleshooting guide before changing firewall rules.
Create:
sudo nft add table inet firewall Now create the forwarding chain:
sudo nft 'add chain inet firewall forward { type filter hook forward priority 0; policy drop; }' The important security decision is:
policy drop Therefore:
Unmatched forwarded traffic
|
v
DROP This is our default-deny foundation.
Netfilter provides both stateless and stateful filtering capabilities, and nftables reuses the existing Netfilter connection-tracking infrastructure. The official Netfilter nftables project provides the upstream project documentation.
Add:
sudo nft add rule inet firewall forward \
ct state invalid drop Then allow traffic belonging to permitted existing connections:
sudo nft add rule inet firewall forward \
ct state established,related accept Our stateful foundation becomes:
INVALID
|
v
DROP
ESTABLISHED / RELATED
|
v
ACCEPT
NEW
|
v
Evaluate explicit policy This prevents us from needing broad reverse-direction rules for every permitted connection.
The most important rule is conceptual:
Internet
|
X
|
Internal LAN Because our forwarding chain already has:
policy drop we do not need to create an allow rule for unsolicited Internet-to-LAN connections.
No matching allow rule means the packet is dropped.
Likewise:
Internet
|
X
|
Management remains blocked.
Suppose the LAN enters the router through:
lan0 and the WAN leaves through:
eth0 A simplified forwarding rule is:
sudo nft add rule inet firewall forward \
iifname "lan0" \
oifname "eth0" \
ip saddr 192.168.10.0/24 \
ct state new \
accept Return traffic is already handled by:
ct state established,related accept Conceptually:
LAN
|
| NEW
v
Internet
Internet
|
| ESTABLISHED
v
LAN Do not confuse firewall filtering with NAT.
Filtering asks:
Should this packet pass? Routing asks:
Where should this packet go? NAT asks:
Should an address or port be translated? Netfilter supports network and port address translation in addition to stateful packet filtering. The official Netfilter project documentation describes NAT and masquerading among its core capabilities.
For IPv4 NAT:
sudo nft add table ip nat Create prerouting:
sudo nft 'add chain ip nat prerouting { type nat hook prerouting priority -100; }' Create postrouting:
sudo nft 'add chain ip nat postrouting { type nat hook postrouting priority 100; }' Now we can implement:
DNAT
SNAT
Masquerading depending on the traffic direction.
Suppose the firewall’s public address is:
203.0.113.10 and the DMZ web server is:
192.168.20.10 We want:
Internet
|
203.0.113.10:443
|
v
192.168.20.10:443 Create DNAT:
sudo nft add rule ip nat prerouting \
iifname "eth0" \
tcp dport 443 \
dnat to 192.168.20.10:443 For HTTP:
sudo nft add rule ip nat prerouting \
iifname "eth0" \
tcp dport 80 \
dnat to 192.168.20.10:80 But DNAT alone does not mean the forwarding firewall should allow the connection.
We still need explicit filter policy.
Add:
sudo nft add rule inet firewall forward \
iifname "eth0" \
ip daddr 192.168.20.10 \
tcp dport { 80, 443 } \
ct state new \
counter \
accept Now the packet path becomes:
Internet Client
|
| 203.0.113.10:443
v
PREROUTING
|
DNAT
|
v
192.168.20.10:443
|
FORWARD
|
Firewall Policy
|
v
DMZ Web Server This separation is extremely important.
NAT determines translation. The firewall determines permission.
A tempting configuration is:
Internet
|
TCP/22
|
v
DMZ Server Avoid exposing administrative services unnecessarily.
Instead, use the management network:
Management VLAN
192.168.30.0/24
|
| SSH
v
DMZ For example:
sudo nft add rule inet firewall forward \
ip saddr 192.168.30.10 \
ip daddr 192.168.20.10 \
tcp dport 22 \
ct state new \
counter \
accept This provides:
ADMIN HOST
192.168.30.10
|
SSH
|
v
DMZ SERVER
192.168.20.10 while Internet-originated SSH remains blocked.
This is one of the central purposes of the architecture.
We want:
DMZ
|
X
|
LAN Because the chain policy is already drop, arbitrary DMZ-to-LAN traffic receives no permission.
For visibility, you may optionally add a controlled logging rule before the default verdict:
sudo nft add rule inet firewall forward \
ip saddr 192.168.20.0/24 \
ip daddr 192.168.10.0/24 \
limit rate 5/second \
log prefix "DMZ_TO_LAN_BLOCKED: " \
counter \
drop Rate limiting matters because uncontrolled logging can create excessive log volume during scans or attacks.
Real applications often require controlled internal communication.
Suppose:
Web Application:
192.168.20.10
Database:
192.168.10.50
Database service:
PostgreSQL TCP/5432 Do not allow:
DMZ → LAN → ANY Instead create:
sudo nft add rule inet firewall forward \
ip saddr 192.168.20.10 \
ip daddr 192.168.10.50 \
tcp dport 5432 \
ct state new \
counter \
accept The security relationship becomes:
DMZ Web Server
192.168.20.10
|
| TCP/5432
v
Database
192.168.10.50 Everything else remains denied.
This is a far stronger design than simply declaring:
DMZ can access LAN Once the network contains several web servers or administration systems, individual rules become repetitive.
Instead of:
192.168.20.10
192.168.20.11
192.168.20.12 you can create:
@dmz_web_servers Likewise:
@trusted_admins and:
@web_ports This is exactly the problem addressed in our previous nftables Sets and Maps architecture.
The design evolves from:
Rule
Rule
Rule
Rule toward:
Security Groups
|
v
Sets / Maps
|
v
Policy Private networks normally cannot send RFC1918 source addresses directly across the public Internet and expect normal Internet routing.
If the WAN address is dynamic, masquerading is commonly used.
For LAN traffic:
sudo nft add rule ip nat postrouting \
oifname "eth0" \
ip saddr 192.168.10.0/24 \
masquerade If DMZ servers require outbound Internet access:
sudo nft add rule ip nat postrouting \
oifname "eth0" \
ip saddr 192.168.20.0/24 \
masquerade But ask whether DMZ servers genuinely require unrestricted outbound access.
A more secure design restricts outbound services according to operational requirements.
A compromised public server should not automatically receive unlimited outbound connectivity.
You might permit only required services such as:
DNS
HTTP
HTTPS
NTP depending on your infrastructure.
Conceptually:
DMZ
|
+--- DNS ------> Resolver
|
+--- HTTPS ----> Internet
|
X--- arbitrary outbound traffic This makes command-and-control traffic and lateral movement harder, although firewall policy is only one layer of defense.
Rather than allowing DMZ hosts to send DNS queries anywhere, consider a designated resolver.
For example:
DNS Resolver:
192.168.20.53 Then:
DMZ servers
|
DNS
|
v
192.168.20.53 This gives administrators a clearer control point for DNS behavior.
The exact architecture depends on whether the resolver itself belongs in the DMZ, an infrastructure VLAN, or another protected zone.
Do not assume:
LAN users = administrators A stronger design separates:
USERS from:
MANAGEMENT For example:
VLAN 10 → Users
VLAN 20 → DMZ
VLAN 30 → Management Then:
Users
|
X
|
SSH to DMZ
Management
|
| SSH
v
DMZ This reduces the impact of compromising an ordinary workstation.
Suppose the management workstation starts:
192.168.30.10
|
| SSH NEW
v
192.168.20.10 Our explicit SSH rule permits the initial connection.
The reply:
192.168.20.10
|
| ESTABLISHED
v
192.168.30.10 matches:
ct state established,related accept Therefore we do not need:
DMZ → Management → Allow Everything This is why connection tracking is fundamental to a stateful DMZ firewall.
Always inspect the active configuration:
sudo nft list ruleset For handles:
sudo nft -a list ruleset Inspect only the forwarding chain:
sudo nft list chain inet firewall forward Inspect NAT:
sudo nft list table ip nat Never assume the rules you intended to load are identical to the rules currently active.
Counters answer an important question:
Is traffic actually reaching this rule?
Example:
sudo nft add rule inet firewall forward \
ip saddr 192.168.30.10 \
ip daddr 192.168.20.10 \
tcp dport 22 \
counter \
accept Generate SSH traffic.
Then:
sudo nft list chain inet firewall forward If the counter increases, the packet is matching the rule.
If it stays at zero, investigate an earlier stage.
Use:
sudo tcpdump -ni eth0 for WAN traffic.
On the DMZ interface:
sudo tcpdump -ni dmz0 Or target a host:
sudo tcpdump -ni any host 192.168.20.10 For HTTPS:
sudo tcpdump -ni any \
host 192.168.20.10 and tcp port 443 Now you can observe:
WAN
↓
DNAT
↓
FORWARD
↓
DMZ
↓
RETURN rather than guessing which layer failed.
Do not immediately rewrite the DNAT rule.
Check in order.
First:
ip addr Is the WAN interface configured correctly?
Then:
sysctl net.ipv4.ip_forward Expected:
1 Then:
sudo nft list table ip nat Is the DNAT rule present?
Then:
sudo nft list chain inet firewall forward Does the forwarding policy allow TCP/80 or TCP/443?
Then:
sudo tcpdump -ni eth0 tcp port 443 Does the connection reach the WAN interface?
Then:
sudo tcpdump -ni dmz0 host 192.168.20.10 Does the translated packet reach the DMZ?
This workflow identifies the failing layer.
Suppose tcpdump shows:
Client → DMZ Server but no reply.
Check the DMZ server:
ip route Its default gateway should normally point toward the Linux firewall for this architecture.
For example:
default via 192.168.20.1 If the reply uses another router, you may create an asymmetric path.
This is especially important with stateful firewalls.
For deeper asymmetric-routing behavior, read Linux rp_filter and Asymmetric Routing.
Remember:
DNAT ≠ ACCEPT DNAT changes the destination.
The packet still has to pass the forwarding policy.
Inspect:
sudo nft list chain inet firewall forward Check:
source
destination after translation
protocol
destination port
connection state and examine counters.
Check:
ip route Then:
ip route get 1.1.1.1 Then:
sysctl net.ipv4.ip_forward Then:
sudo nft list chain inet firewall forward Then:
sudo nft list table ip nat Confirm masquerading.
Finally:
sudo tcpdump -ni eth0 The failure could be:
Routing
Firewall
NAT
DNS
Upstream gateway Do not assume all Internet-access failures are NAT failures.
A multi-homed DMZ firewall may have:
WAN1
WAN2
LAN
DMZ
Management
VPN Linux route selection becomes increasingly important.
Use:
ip route get DESTINATION For source-specific tests:
ip route get DESTINATION from SOURCE If source-dependent routing is required, Linux Policy Routing with ip rule and Multiple Routing Tables explains how policy rules select alternative routing tables.
For persistent Ubuntu configurations with multiple default routes, use Netplan Multiple Default Routes and Policy Routing.
If several interfaces exist in related Layer 2 environments, unexpected ARP behavior can complicate diagnosis.
Symptoms may look like:
wrong interface
unexpected MAC response
unstable reachability even when nftables appears correct.
CorgiNetwork’s Linux ARP Flux with Multiple NICs on the Same Subnet covers this specific class of problem.
NAT in Netfilter is closely tied to connection tracking.
Once a connection has been classified and translated, subsequent packets belonging to that flow use the associated NAT state.
This is one reason troubleshooting should consider:
NAT
+
conntrack
+
filtering
+
routing as interacting systems rather than completely independent mechanisms.
The upstream Netfilter project documents both stateful packet filtering and NAT as core Netfilter capabilities.
On high-throughput Linux routers, Netfilter also provides flowtable infrastructure that can create a fast path for selected forwarded flows.
The official Linux kernel flowtable documentation explains that, after the first packet traverses the classic forwarding path, selected subsequent packets can be offloaded to a flowtable fast path. It also notes that flowtable entries retain the NAT configuration associated with the connection.
This is an advanced optimization.
Do not begin there.
First build:
Correct routing
↓
Correct firewall
↓
Correct NAT
↓
Correct observability
↓
Performance optimization A fast incorrect firewall is still an incorrect firewall.
A common mistake is building:
Excellent IPv4 firewall while leaving:
IPv6 with a completely different security posture.
IPv4 NAT is also not a substitute for IPv6 firewall policy.
If IPv6 is enabled and routed, define an explicit IPv6 security model.
The Linux kernel exposes separate forwarding controls for IPv4 and IPv6; its IP sysctl documentation describes global IPv6 forwarding separately from net.ipv4.ip_forward.
A DMZ does not make a vulnerable server safe.
You still need:
Operating-system updates
Application patching
Minimal exposed services
TLS configuration
Authentication
Least privilege
Logging
Monitoring
Backups
Endpoint security
Vulnerability management Network segmentation limits reachability.
It does not replace host and application security.
Before deploying this architecture, verify:
✓ LAN, DMZ and management are separate zones
✓ Default forwarding policy is DROP
✓ Invalid traffic is handled
✓ Established/related traffic is handled statefully
✓ Internet cannot directly reach LAN
✓ Internet cannot directly reach management
✓ Only required services are DNATed
✓ DMZ-to-LAN access is denied by default
✓ Required DMZ-to-LAN exceptions are narrowly scoped
✓ Administrative access originates from management
✓ NAT rules are documented
✓ Firewall counters are available
✓ Logging is rate-limited
✓ IPv6 has an explicit policy
✓ Firewall rules persist across reboot
✓ Configuration backups exist
✓ Out-of-band recovery is available where possible Think of the firewall as several separate systems working together:
PACKET
|
v
+---------------+
| Layer 2/VLAN |
+---------------+
|
v
+---------------+
| Routing |
+---------------+
|
v
+---------------+
| Netfilter |
| Firewall |
+---------------+
|
+----+----+
| |
v v
FILTER NAT
| |
+----+----+
|
v
CONNTRACK
|
v
DESTINATION When troubleshooting, identify which subsystem is responsible.
A Linux DMZ is a separate network security zone containing systems that require controlled connectivity to less-trusted networks, while a Linux router/firewall regulates communication between the DMZ, Internet, and internal networks.
Yes.
nftables can provide stateful filtering, NAT, address/port translation, logging, sets, maps, and other packet-processing capabilities through the Linux Netfilter framework.
Not necessarily.
If both networks are internally routed and have valid return routes, they can communicate through normal Layer 3 routing.
NAT is normally introduced when address translation is actually required.
DNAT changes the destination address and optionally the destination port.
A common DMZ use is translating:
Public IP:443 to:
Private DMZ Web Server:443 Filtering should still explicitly decide whether that forwarded connection is allowed.
Masquerading is a form of source NAT commonly used when outbound private-network traffic shares an external address, particularly when that external address may change.
The safest starting position is generally to deny unsolicited DMZ-to-LAN access and then create narrowly scoped exceptions for documented application requirements.
For example:
Web Server
|
TCP/5432
|
Database is much safer than:
DMZ
|
ANY
|
LAN Avoid exposing administrative services unless there is a clear operational requirement and an appropriate security architecture.
A dedicated management network, VPN, bastion design, or another controlled administrative path is usually preferable to broadly exposing management interfaces.
No.
Translation and filtering are separate concepts.
A DNAT rule can change the packet destination while the forwarding firewall still drops the packet.
Start with:
ip -br addr
ip route
ip route get DESTINATION
sysctl net.ipv4.ip_forward
nft list ruleset
conntrack -L
tcpdump -ni any Then follow the packet from source to destination and back.
A secure Linux DMZ is not created by one nftables rule.
It is an architecture:
INTERNET
|
v
WAN
|
v
Linux Router
|
+------------+------------+
| | |
v v v
LAN DMZ MANAGEMENT Each component has a specific responsibility:
VLANs
↓
Layer 2 separation
Routing
↓
Layer 3 reachability
nftables filtering
↓
Security policy
DNAT
↓
Publishing selected services
SNAT/Masquerading
↓
Outbound address translation
Conntrack
↓
Stateful connection handling The core security principle is straightforward:
Expose the minimum required service and the minimum required path.
Internet users do not need access to your LAN.
A public web server does not need unrestricted access to every internal system.
Ordinary LAN users do not need administrative access to the DMZ.
And an administrator should not have to weaken the entire network simply to manage one server.
This article builds directly on the Linux nftables Inter-VLAN Firewall, where we created the default-deny and stateful firewall foundation.
The Layer 3 foundation is covered in Linux Inter-VLAN Routing with Network Namespaces, while Linux Bridge VLAN Filtering Explained covers the Layer 2 VLAN architecture underneath it.
For complex multi-homed routers, Linux Policy Routing with ip rule and Multiple Routing Tables extends the design to source- and policy-dependent route selection.
If Linux sends packets through an unexpected NIC, use Linux Traffic Uses the Wrong Interface before modifying the firewall.
For asymmetric paths, Linux rp_filter and Asymmetric Routing explains another important Linux networking behavior that can appear in multi-router environments.
For the underlying technologies, the official Netfilter project documents stateful packet filtering, NAT and masquerading, while the Linux kernel IP sysctl documentation documents IP forwarding behavior. For advanced high-throughput routers, the kernel’s Netfilter flowtable documentation explains software fast-path and hardware-offload capabilities.
The CorgiNetwork cluster now progresses naturally through:
Network Namespaces
↓
Linux Bridge
↓
VLAN Filtering
↓
Inter-VLAN Routing
↓
Stateful nftables Firewall
↓
nftables Sets & Maps
↓
Linux DMZ + NAT
↓
Production Linux Firewall Architecture AI Agents as Code: Managing AI Agent Configurations, Prompts and Tools with Git AI agents…
Data Center Power Distribution Architecture: UPS, PDU, Redundancy and Rack Power Explained Data center power…
DNSSEC Troubleshooting: Fix SERVFAIL & DNSKEY Errors DNSSEC Troubleshooting: How to Fix SERVFAIL, DS, DNSKEY…
Terraform State Management Explained: Remote State, Locking, Backends and Best Practices Terraform makes infrastructure reproducible…
RTO vs RPO Explained: Disaster Recovery Objectives, Examples and Planning A disaster recovery plan is…
Network Intrusion Detection Explained: NIDS, Sensors, Signatures and Traffic Analysis A firewall can decide whether…