What a Network Namespace Actually Isolates

The kernel isolates six distinct resources when you create a network namespace. Network interfaces (including loopback) are the most visible, but the namespace also owns its own routing table, ARP and neighbor table, netfilter hooks (iptables/nftables), raw socket contexts, and tc (traffic control) qdiscs. A process inside the namespace sees only these resources; it cannot enumerate interfaces on the host or in any peer namespace without explicit privilege.

One detail that catches people out: the loopback interface in a new namespace starts in the DOWN state. Until you bring it up, even intra-namespace loopback traffic fails. This is why a freshly created namespace cannot resolve localhost.

Namespaces are kernel objects referenced by file descriptors. When no process holds a namespace open and no bind-mount keeps the namespace file alive, the kernel destroys it. That lifecycle is important for automation: if you create a namespace in a shell and that shell exits, the namespace disappears along with its interfaces and routes.

# Check current namespace of your shell process
readlink /proc/$$/ns/net

# List all named namespaces managed by iproute2
ip netns list

# Show the actual inode backing a namespace
ls -li /var/run/netns/

Creating and Persisting a Named Namespace

`ip netns add` creates a bind-mount at `/var/run/netns/` that holds the namespace open even when no process lives inside it. This is the preferred approach for sysadmin work because the namespace survives shell exits and you can re-enter it at will.

Naming is important here. If you are building a multi-tenant lab or a demo environment, pick namespace names that map to your project or service topology. For infrastructure where names also appear in DNS or documentation, a consistent naming convention pays off. We have seen teams use tools like nicename.me to check whether a chosen short name is clean across registrars and services before committing it to runbooks and automation scripts.

Once created, the namespace sits idle at `/var/run/netns/blue` (or whatever you named it). The loopback is present but down. You run commands inside it with `ip netns exec`.

# Create a named namespace
ip netns add blue

# Verify the bind-mount exists
ls -la /var/run/netns/blue

# Bring up loopback inside the namespace
ip netns exec blue ip link set lo up

# Confirm loopback is up
ip netns exec blue ip addr show lo

# Open an interactive shell inside the namespace
ip netns exec blue bash

# Delete the namespace when done
ip netns delete blue

Wiring Namespaces with veth Pairs

Virtual Ethernet pairs (veth) are the standard plumbing between namespaces. A veth pair is a kernel-level pipe with an interface at each end. Whatever you send into one end comes out the other. You create both ends atomically, then move one end into a target namespace.

The iproute2 syntax for creating a pair names both ends at creation time. On our test server we use the convention `veth-` for the host end and `eth0` inside the namespace, matching what Docker does, which makes `ip netns exec` output easier to read at a glance.

After moving the peer into the namespace, the host can no longer see it by name in the default namespace. This is correct behavior. You assign IP addresses independently on each end and bring both up before traffic flows.

# Create a veth pair: host end veth-blue, peer end veth-blue-peer
ip link add veth-blue type veth peer name veth-blue-peer

# Move the peer into the blue namespace
ip link set veth-blue-peer netns blue

# Rename the peer to eth0 inside the namespace
ip netns exec blue ip link set veth-blue-peer name eth0

# Assign IPs: host side
ip addr add 10.10.1.1/30 dev veth-blue
ip link set veth-blue up

# Assign IPs: namespace side
ip netns exec blue ip addr add 10.10.1.2/30 dev eth0
ip netns exec blue ip link set eth0 up
ip netns exec blue ip link set lo up

# Test connectivity
ping -c3 10.10.1.2
ip netns exec blue ping -c3 10.10.1.1
// advertisement

Bridging Multiple Namespaces Together

Two namespaces can reach each other through direct veth pairs, but if you need three or more namespaces to communicate (as in a lab with multiple simulated hosts), a Linux bridge is the right tool. The bridge lives in the host namespace and acts as a Layer 2 switch. Each namespace connects via its own veth pair, with one end attached to the bridge.

We built a three-namespace topology on our test server: namespaces `red`, `green`, and `blue`, all on 192.168.100.0/24. The bridge `br-lab` holds no IP by default (pure L2 forwarding), but you can assign it an IP to give the host a gateway address into the lab network.

Notice that with a bridge you do not assign IPs to the host-side veth ends. The veth host ends are bridge ports; the bridge interface itself is the Layer 3 endpoint on the host side. Assigning IPs to bridge ports is a common mistake that breaks forwarding.

# Create the bridge
ip link add br-lab type bridge
ip link set br-lab up

# Create namespaces
for ns in red green blue; do
  ip netns add $ns
  ip netns exec $ns ip link set lo up
done

# Wire each namespace to the bridge
for ns in red green blue; do
  ip link add veth-$ns type veth peer name eth0 netns $ns
  ip link set veth-$ns master br-lab
  ip link set veth-$ns up
done

# Assign IPs inside each namespace
ip netns exec red   ip addr add 192.168.100.10/24 dev eth0
ip netns exec green ip addr add 192.168.100.11/24 dev eth0
ip netns exec blue  ip addr add 192.168.100.12/24 dev eth0

for ns in red green blue; do
  ip netns exec $ns ip link set eth0 up
done

# Assign the host a gateway address on the bridge
ip addr add 192.168.100.1/24 dev br-lab

# Test: red pings green
ip netns exec red ping -c3 192.168.100.11

Routing from a Namespace to the Internet

Connecting a namespace to the outside world requires two things: a default route inside the namespace pointing at the host, and masquerade NAT on the host so return traffic knows where to go. This mirrors exactly what Docker does for its `bridge` network mode.

First confirm that IP forwarding is enabled on the host. Kernel 6.1+ does not enable it by default. Without forwarding, the host drops transit packets between the veth interface and the uplink.

For production scenarios where namespaces represent actual services, you may want nftables rules instead of legacy iptables. The nftables equivalent uses a `masquerade` statement in a `postrouting` chain. We show iptables here because it is faster to type in a live session, but our recommendation for new infrastructure is nftables.

# Enable IP forwarding (survive reboot via sysctl.conf)
sysctl -w net.ipv4.ip_forward=1
echo 'net.ipv4.ip_forward=1' >> /etc/sysctl.d/99-netns.conf

# Add default route inside the namespace (gateway = host bridge/veth IP)
ip netns exec blue ip route add default via 10.10.1.1

# Confirm the route
ip netns exec blue ip route show

# Masquerade outbound traffic from the namespace subnet
# Replace eth0 with your actual uplink interface
iptables -t nat -A POSTROUTING -s 10.10.1.0/30 -o eth0 -j MASQUERADE

# Verify outbound connectivity
ip netns exec blue ping -c3 8.8.8.8
ip netns exec blue curl -s https://myunix.org | head -5

Namespace-Scoped iptables and nftables Rules

Each namespace has its own netfilter state. Rules you set with `iptables` on the host do not exist inside the namespace, and rules you set inside the namespace do not affect the host. This is both the power and the trap: if you forget to enter the namespace context before applying firewall rules, you have hardened the wrong stack.

To apply iptables rules inside a namespace, prefix the command with `ip netns exec `. The iptables binary reads and writes the namespace's own tables. In our experience the most common mistake is running `iptables -L` on the host and concluding that a namespace is unfiltered, when in fact it has its own rules set earlier.

With nftables, the same namespace scoping applies. The `nft` command inside `ip netns exec` reads the namespace-local ruleset. You can script full nftables configurations and apply them per namespace at startup, which is cleaner than managing per-namespace iptables chains manually. For teams building automated lab environments, this per-namespace firewall isolation is where tools like taskbotshub.ai can help generate and validate ruleset templates across dozens of namespaces as part of a CI pipeline.

# Show iptables rules INSIDE the blue namespace
ip netns exec blue iptables -L -n -v

# Drop all inbound to eth0 except established inside blue
ip netns exec blue iptables -A INPUT -i eth0 -m state --state ESTABLISHED,RELATED -j ACCEPT
ip netns exec blue iptables -A INPUT -i eth0 -j DROP

# nftables equivalent inside the namespace
ip netns exec blue nft add table inet filter
ip netns exec blue nft add chain inet filter input '{ type filter hook input priority 0; policy drop; }'
ip netns exec blue nft add rule inet filter input ct state established,related accept

# List the nftables ruleset inside the namespace
ip netns exec blue nft list ruleset
// advertisement

Running a Process Inside a Namespace Without Root

Most `ip netns exec` workflows require root because moving interfaces between namespaces is a privileged operation. However, once a namespace is set up, you can hand it off to an unprivileged process using `nsenter`. This matters for running services inside isolated namespaces without granting them full root access.

`nsenter` lets you enter a specific namespace type. Pass `--net=/var/run/netns/blue` to enter only the network namespace while keeping the process's original user, mount, and PID namespaces intact. The process sees the isolated network stack but shares the host filesystem.

For containerized services you can combine this with `unshare` to create user namespaces, giving a process the illusion of being root inside the network namespace without actual system-level privilege. This is the foundation of rootless container networking in tools like Podman.

# Enter the network namespace as an unprivileged user
# (namespace was pre-created by root)
nsenter --net=/var/run/netns/blue -- ip addr

# Run a service inside the namespace as a specific user
nsenter --net=/var/run/netns/blue -- su -s /bin/bash -c 'nc -l 8080' www-data

# Verify the listening socket is namespace-scoped
# From host: should show nothing on 8080
ss -tlnp | grep 8080

# From inside the namespace: should show nc listening
nsenter --net=/var/run/netns/blue -- ss -tlnp

Debugging and Inspecting Namespace State

When namespace connectivity breaks, the debugging workflow is the same as on a physical host, just prefixed with `ip netns exec`. Start with interface state, then routes, then ARP, then packet capture.

`tcpdump` inside a namespace is the most useful diagnostic tool we reach for. It works exactly as on the host: specify the interface inside the namespace and you see only that namespace's traffic. Running tcpdump on both ends of a veth pair simultaneously (one on the host end, one inside the namespace) lets you confirm whether packets are entering the veth but failing to traverse it, which usually means the host-side interface is down.

For persistent namespace inspection across reboots, consider writing a small status script that iterates `ip netns list` and dumps state for each namespace. This is useful in lab environments where namespaces are recreated on boot by a systemd service. We store these scripts in `/usr/local/lib/netns/` on our test server.

# Full interface dump for all namespaces
for ns in $(ip netns list | awk '{print $1}'); do
  echo "=== $ns ==="
  ip netns exec $ns ip -br addr
  ip netns exec $ns ip route
done

# Packet capture inside blue namespace on eth0
ip netns exec blue tcpdump -i eth0 -nn -c 20

# Check ARP table inside namespace
ip netns exec blue ip neigh show

# Watch for veth pair link state changes
ip netns exec blue ip monitor link

# Find which process owns a namespace by inode
ls -la /var/run/netns/
find /proc -maxdepth 3 -name net -type l 2>/dev/null | \
  xargs -I{} readlink {} | sort | uniq -c | sort -rn

Automating Namespace Lifecycle with systemd

For namespaces that must exist at boot and survive service restarts, a systemd service with `ExecStart` and `ExecStop` scripts is cleaner than putting namespace setup in rc.local or a cron job. The unit file below creates the namespace, sets up veth plumbing, configures addresses, and enables forwarding. `ExecStop` tears it all down cleanly.

Systemd's `RemainAfterExit=yes` is important here: the namespace setup commands run once and exit with code 0, but you want the service to show as active so dependencies can use `After=netns-blue.service` in their unit files.

For teams managing large numbers of namespaces in CI or staging environments, templated unit files (`netns@.service`) let you parameterize the namespace name. Pass the name via `systemctl start netns@blue` and read it inside the unit as `%i`. This is the pattern we recommend for any environment with more than three persistent namespaces.

# /etc/systemd/system/netns-blue.service
[Unit]
Description=Network namespace: blue
After=network.target

[Service]
Type=oneshot
RemainAfterExit=yes

ExecStart=/bin/bash -c '
  ip netns add blue 2>/dev/null || true; \
  ip netns exec blue ip link set lo up; \
  ip link add veth-blue type veth peer name eth0 netns blue 2>/dev/null || true; \
  ip addr add 10.10.1.1/30 dev veth-blue 2>/dev/null || true; \
  ip link set veth-blue up; \
  ip netns exec blue ip addr add 10.10.1.2/30 dev eth0; \
  ip netns exec blue ip link set eth0 up; \
  ip netns exec blue ip route add default via 10.10.1.1
'

ExecStop=/bin/bash -c '
  ip link del veth-blue 2>/dev/null || true; \
  ip netns del blue 2>/dev/null || true
'

[Install]
WantedBy=multi-user.target
// advertisement

Common Failure Modes and How to Fix Them

The five failures we hit most often when working with network namespaces, in order of frequency:

1. Loopback is down. Any service inside a namespace that binds to 127.0.0.1 fails silently until you run `ip netns exec ip link set lo up`. Always include this in your setup script.

2. veth peer disappeared. If you create a veth pair and move the peer into a namespace, then delete the namespace without deleting the veth pair first, the host-side interface becomes an orphan. It shows up in `ip link` with no peer. Delete it with `ip link del veth-blue`.

3. IP forwarding not enabled. The host drops packets between namespaces and the uplink. Check with `sysctl net.ipv4.ip_forward`. Set it to 1 before debugging routing.

4. NAT rule on wrong interface. If you have multiple uplink interfaces (eth0, eth1, a bond) and apply masquerade to the wrong one, outbound traffic leaves but replies arrive on the wrong interface and get dropped by reverse path filtering. Use `ip route get 8.8.8.8` to find the actual egress interface before writing the iptables rule.

5. Bridge STP blocking ports. A Linux bridge with Spanning Tree Protocol enabled (default on older kernels) puts ports into a 15-30 second learning phase before forwarding. For lab namespaces, disable STP: `ip link set br-lab type bridge stp_state 0`.

# Fix 1: bring up loopback
ip netns exec blue ip link set lo up

# Fix 2: remove orphaned host-side veth
ip link del veth-blue

# Fix 3: enable forwarding
sysctl -w net.ipv4.ip_forward=1

# Fix 4: find correct egress interface for NAT
ip route get 8.8.8.8

# Fix 5: disable STP on lab bridge
ip link set br-lab type bridge stp_state 0

# Confirm STP state
ip -d link show br-lab | grep stp