Linux Network Namespaces: Isolated Network Stacks Without a Hypervisor
Every process on a Linux host shares the same network stack by default — the same interfaces, the same routing table, the same iptables rules, the same socket namespace. Network namespaces break that assumption. A process running in a network namespace has its own completely independent network stack: its own interfaces, routing table, ARP table, nftables ruleset, and port space. Two processes in different namespaces can both bind to port 80 without conflict.
This is the mechanism underneath Docker containers, Kubernetes pods, network function virtualisation, and most VPN implementations on Linux. Understanding namespaces directly — not through an abstraction layer — gives you both a cleaner mental model of how those systems work and a powerful tool for network testing and isolation in its own right.
The Linux VRFs post covered VRFs as a lighter-weight route isolation mechanism. This post covers namespaces, which go further: where a VRF isolates only the routing table, a namespace isolates the entire network stack.
(If you want the BGP/OSPF multi-router lab angle instead, see Linux Networking from the Ground Up — this post takes the container-runtime angle: the same primitives, applied to how Docker and Kubernetes actually build pod networking.)
What a Network Namespace Contains
Each network namespace is an independent instance of:
- Network interfaces (each interface belongs to exactly one namespace at a time)
- Routing tables (including policy routing rules)
- ARP / neighbour table
- Netfilter hooks (nftables / iptables rules)
- Socket table (bound ports, established connections)
sysctlnetwork settings (e.g.net.ipv4.ip_forwardis per-namespace)/proc/net/entries
The loopback interface (lo) is created automatically in every new namespace but starts down — you need to bring it up explicitly.
Basic Namespace Operations
# Create a named namespace
ip netns add blue
# List namespaces
ip netns list
# Execute a command inside a namespace
ip netns exec blue ip link show
# Open a shell inside a namespace
ip netns exec blue bash
# Delete a namespace
ip netns delete blue
Named namespaces are stored as bind-mounts under /var/run/netns/. The name is just a handle — the namespace itself is a kernel object referenced by file descriptor.
Inside a new namespace, only the loopback exists and it is down:
ip netns exec blue ip link show
# 1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN
ip netns exec blue ip link set lo up
Connecting Namespaces: veth Pairs
The standard way to connect two namespaces (or a namespace to the host) is a veth pair — a virtual Ethernet cable with one end in each namespace. Packets sent into one end emerge from the other.
# Create a veth pair
ip link add veth-blue type veth peer name veth-host
# Move one end into the namespace
ip link set veth-blue netns blue
# Configure addresses
ip addr add 10.10.0.1/24 dev veth-host
ip netns exec blue ip addr add 10.10.0.2/24 dev veth-blue
# Bring both ends up
ip link set veth-host up
ip netns exec blue ip link set veth-blue up
ip netns exec blue ip link set lo up
# Test
ping -c3 10.10.0.2
ip netns exec blue ping -c3 10.10.0.1
At this point the namespace has its own interface and can communicate with the host over the veth pair. It has no default route and no access to anything beyond the host’s veth end.
Giving a Namespace Internet Access
To give a namespace outbound internet access, the host needs to NAT the namespace’s traffic. This requires ip_forward and a masquerade rule:
# Enable IP forwarding on the host
sysctl -w net.ipv4.ip_forward=1
# Add a default route in the namespace pointing to the host veth end
ip netns exec blue ip route add default via 10.10.0.1
# NAT outbound traffic from the namespace (nftables)
nft add table ip nat
nft add chain ip nat postrouting '{ type nat hook postrouting priority 100; }'
nft add rule ip nat postrouting ip saddr 10.10.0.0/24 oif eth0 masquerade
Or with iptables if that is what is in use:
iptables -t nat -A POSTROUTING -s 10.10.0.0/24 -o eth0 -j MASQUERADE
Now the namespace has full outbound connectivity through the host:
ip netns exec blue curl -s https://ifconfig.me
# Returns the host's public IP — the namespace is NATted
Multiple Namespaces with a Bridge
For multiple namespaces that need to talk to each other and the host, use a Linux bridge:
# Create a bridge on the host
ip link add br0 type bridge
ip addr add 10.20.0.1/24 dev br0
ip link set br0 up
# Create namespace red
ip netns add red
ip link add veth-red type veth peer name veth-red-br
ip link set veth-red netns red
ip link set veth-red-br master br0
ip link set veth-red-br up
ip netns exec red ip addr add 10.20.0.2/24 dev veth-red
ip netns exec red ip route add default via 10.20.0.1
ip netns exec red ip link set veth-red up
ip netns exec red ip link set lo up
# Create namespace green
ip netns add green
ip link add veth-green type veth peer name veth-green-br
ip link set veth-green netns green
ip link set veth-green-br master br0
ip link set veth-green-br up
ip netns exec green ip addr add 10.20.0.3/24 dev veth-green
ip netns exec green ip route add default via 10.20.0.1
ip netns exec green ip link set veth-green up
ip netns exec green ip link set lo up
Now red and green can reach each other and the host through the bridge. This is exactly how Docker creates a container network — the bridge is docker0, and each container’s veth pair is plumbed in the same way.
Verify connectivity:
ip netns exec red ping -c2 10.20.0.3 # red -> green
ip netns exec green ping -c2 10.20.0.1 # green -> host
Moving Physical Interfaces Into Namespaces
Any interface can be moved into a namespace — including physical NICs:
# Move eth1 into namespace blue
ip link set eth1 netns blue
# Configure it inside the namespace
ip netns exec blue ip addr add 192.168.100.10/24 dev eth1
ip netns exec blue ip link set eth1 up
ip netns exec blue ip route add default via 192.168.100.1
Once moved, eth1 disappears from the host’s network stack entirely. The host cannot see or use it. This is useful for network appliances, out-of-band management isolation, or giving a process exclusive access to a physical NIC.
To return it to the host namespace:
# Namespace 1 is always the host (initial) namespace
ip netns exec blue ip link set eth1 netns 1
Namespace-Scoped nftables
Each namespace has its own independent nftables instance. Rules loaded in the host namespace have no effect inside a namespace, and vice versa:
# Load firewall rules that only apply inside namespace blue
ip netns exec blue nft -f - <<'EOF'
flush ruleset
table inet filter {
chain input {
type filter hook input priority 0; policy drop;
ct state established,related accept
iif lo accept
tcp dport { 80, 443 } accept
}
}
EOF
# Verify — this ruleset is isolated to blue
ip netns exec blue nft list ruleset
nft list ruleset # host ruleset is unchanged
This per-namespace isolation is what makes container security models work — each container’s network namespace has its own firewall, and a misconfigured container rule cannot affect the host or other containers.
Network Namespaces for Testing
The most practical everyday use of namespaces for network engineers is building isolated test topologies without virtual machines. The combination of namespaces, veth pairs, and the tc netem qdisc from the tc post lets you simulate any network condition:
# Build a two-node topology: client -- [100ms latency, 1% loss] -- server
ip netns add client
ip netns add server
ip link add veth-client type veth peer name veth-server
ip link set veth-client netns client
ip link set veth-server netns server
ip netns exec client ip addr add 10.30.0.1/24 dev veth-client
ip netns exec server ip addr add 10.30.0.2/24 dev veth-server
ip netns exec client ip link set veth-client up
ip netns exec server ip link set veth-server up
ip netns exec client ip link set lo up
ip netns exec server ip link set lo up
# Apply netem impairments on the client's egress
ip netns exec client tc qdisc add dev veth-client root netem delay 100ms loss 1%
# Test
ip netns exec client ping -c10 10.30.0.2
# rtt min/avg/max/mdev = 100.xxx/100.xxx/101.xxx ms
# Run iperf3 — server in one terminal, client in another
ip netns exec server iperf3 -s &
ip netns exec client iperf3 -c 10.30.0.2 -t 10
This replaces a two-VM lab for most TCP behaviour testing. Spin it up in seconds, apply any tc netem profile, tear it down when done.
Namespaces vs VRFs — When to Use Which
From the comparison in the Linux VRFs post, the decision is usually straightforward:
| Need | Use |
|---|---|
| Isolate routing tables on one host, all traffic visible to host firewall | VRF |
| Management plane / data plane separation on a single interface set | VRF |
| Full network stack isolation — own firewall, own ports, own interfaces | Namespace |
| Container / pod networking | Namespace (Docker/k8s manage this for you) |
| Reproducible network test topology | Namespace |
| Process scoping (only one process uses an interface) | Namespace |
The practical difference: a VRF is a routing construct that lives inside the host network stack. A namespace is a separate network stack. If you need the host to be able to see and filter traffic in the isolated segment, use a VRF. If you need hard isolation — separate firewall rules, separate port space, no shared kernel state — use a namespace.
Persistent Namespaces with systemd
The ip netns commands are not persistent across reboots. For persistent namespace topology, a startup script managed by systemd is the simplest approach:
# /usr/local/bin/setup-namespaces.sh
#!/usr/bin/env bash
set -euo pipefail
ip netns add blue 2>/dev/null || true
ip link add veth-blue type veth peer name veth-host 2>/dev/null || true
ip link set veth-blue netns blue
ip addr add 10.10.0.1/24 dev veth-host 2>/dev/null || true
ip netns exec blue ip addr add 10.10.0.2/24 dev veth-blue 2>/dev/null || true
ip link set veth-host up
ip netns exec blue ip link set veth-blue up
ip netns exec blue ip link set lo up
ip netns exec blue ip route add default via 10.10.0.1 2>/dev/null || true
# /etc/systemd/system/namespaces.service
[Unit]
Description=Network namespace setup
After=network.target
[Service]
Type=oneshot
ExecStart=/usr/local/bin/setup-namespaces.sh
RemainAfterExit=yes
[Install]
WantedBy=multi-user.target
systemctl enable --now namespaces.service
Connecting to the Series
Namespaces are the layer that ties the rest of this series together in a container or VM context. The tc netem profiles from the tc post apply inside namespaces for test topology simulation. The ss tool scoped with ip netns exec shows per-namespace socket state. The nftables rules from the nftables post are per-namespace. The ip vrf exec pattern from the VRFs post has a direct parallel in ip netns exec — the difference is the scope of isolation.
If you have ever wondered exactly what docker network create does, the answer is: it creates a Linux bridge, a network namespace per container, and a veth pair connecting each namespace to the bridge — exactly the multi-namespace bridge topology above.
What to Read Next
- Linux VRFs — the lighter-weight routing isolation model; read alongside this post for a clear comparison of when each is appropriate
- Cilium — the eBPF-based Kubernetes CNI that builds directly on the namespaces, veth pairs, and bridge topology covered above to implement pod networking across nodes