all posts

Best Firecracker Networking Tools & Approaches (2026)

Ajay Kumar··11 min read

Firecracker's networking is one of its most honest features: it gives you a virtio-net device inside the guest, wires it to a TAP device on the host, and stops there. No IP allocation, no NAT, no isolation between guests, no egress policy, no throughput tuning, no way to scale it to thousands of VMs without a plan. Firecracker hands you a TAP device and a firm handshake; the rest of the network is your problem. This is a 2026 field guide to the tools and approaches that make it not your problem — the real building blocks people reach for, what each one buys you, what it costs, and a 'reach for this when…' so you can match the approach to the workload instead of cargo-culting whatever the last tutorial used.

If you want the mechanics of how the guest's virtio-net NIC, the host TAP, and per-sandbox namespaces actually fit together, that's covered in /blog/firecracker-networking-explained; this post assumes that picture and lives one layer up, at the level of tools and design trade-offs. The field: a plain TAP on a shared Linux bridge (simple, doesn't isolate), a per-VM network namespace with a veth pair and iptables NAT (real isolation, slow to set up cold), CNI plugins (the firecracker-go-sdk / Ignite path, tc-redirect-tap), vhost-net for throughput, Firecracker's built-in rate limiter, MMDS for guest metadata, egress control for untrusted guests, and pre-allocated netns pools for fast create. None of these is 'the best' in isolation — they compose, and the right stack depends on whether you're running trusted workloads or someone else's code, ten VMs or ten thousand.

Disclosure: I founded PandaStack, an open-source Firecracker microVM platform, so read the PandaStack parts as a vendor's pitch and weight them accordingly. I keep the rest honest the only way that works: I describe every third-party tool and approach qualitatively from its own docs and source, I don't invent benchmarks, and I cite specific latency figures only for PandaStack. The one cold-setup figure I lean on (~100ms to build a per-VM netns + veth + iptables from scratch) is a PandaStack measurement — it's the reason our networking pre-allocates — and third-party throughput and behavior are exactly the sort of thing that shifts across kernels and versions, so verify tc-redirect-tap, CNI plugin behavior, vhost-net support, and the rate-limiter/MMDS details against their current docs for your build before you commit.

What Firecracker actually gives you (and what it doesn't)

Firecracker's entire networking contract is one line in the machine config: a network interface that binds a guest MAC to a named host TAP device. You create the TAP, you tell Firecracker its name, and Firecracker attaches the guest's virtio-net device to it. That's the whole API surface. Below is roughly what that config looks like — a single interface pointing at a host TAP you already made.

// Firecracker's network config is this small: bind a guest MAC to a host TAP.
// Everything above this — the TAP's namespace, its IP, NAT, egress rules,
// rate limits — is on you. Verify field names against the FC docs for your
// version (configured via the machine config or PUT /network-interfaces/eth0).
{
  "network-interfaces": [
    {
      "iface_id": "eth0",
      "guest_mac": "06:00:AC:10:00:02",
      "host_dev_name": "tap0"
    }
  ]
}

// Optionally, the same interface can carry a built-in rate limiter — see below:
// "rx_rate_limiter": { "bandwidth": { "size": 1048576, "refill_time": 1000 } }

So the jobs you're on the hook for, above that line, are: give each TAP an IP and a route; decide whether guests can see each other (isolation); let guests reach the outside world (NAT); decide what they're allowed to reach (egress policy); make throughput acceptable under load (vhost-net); optionally cap per-VM bandwidth and packet rate (rate limiter); optionally hand each guest metadata without a network service (MMDS); and do all of it fast enough that it isn't the slow part of creating a sandbox. The tools below each cover a slice of that list.

TAP + Linux bridge: the simple path that doesn't isolate

The first thing every Firecracker tutorial shows you: create one Linux bridge on the host, create a TAP per VM, and enslave every TAP to that shared bridge. Give the bridge an IP, hang a NAT/MASQUERADE rule off it, and every guest gets connectivity through one shared Layer 2 segment. It is genuinely the least code, it's easy to reason about, and for a handful of trusted VMs on one host it's completely fine.

The problem is that a shared bridge is a shared broadcast domain. Every guest on it can ARP for every other guest, send packets to it directly, and — depending on your firewall — reach it. They also all sit on the same subnet as whatever else is on that bridge, so a curious or hostile guest can scan for an internal service, a database, or the cloud metadata endpoint at 169.254.169.254. For trusted workloads that's an acceptable trade for the simplicity. For untrusted or multi-tenant code — the exact workload microVMs exist to run — a shared bridge is a cross-tenant leak with good ergonomics. Reach for TAP + bridge when: you have a small number of mutually-trusting VMs on a single host and want the least moving parts. Do not reach for it when isolation between guests matters.

Per-VM network namespace + veth + iptables NAT: real isolation

The isolation-correct approach is a Linux network namespace per VM. A netns is an isolated copy of the kernel's entire networking stack — its own interfaces, routing table, ARP cache, and iptables/nftables rules — and a process or interface in one netns cannot see another's. You put each VM's TAP inside its own netns, connect that namespace to the host root namespace with a veth pair (a virtual cable with one end in each), and NAT outbound traffic through an iptables MASQUERADE rule in the root namespace. Now there is no shared segment for guests to discover each other on, each VM's egress rules live in its own namespace where they govern exactly one guest, and tearing the VM down deletes the whole netns atomically. Here's the shape of wiring up one VM by hand.

# One VM's isolated network, from scratch. This is the isolation-correct
# approach — and the slow one: doing all of this cold is ~100ms per VM.

# 1. A private network namespace for this one VM.
ip netns add ns-vm01

# 2. A veth pair: vg-vm01 (guest side) <-> vh-vm01 (host side).
ip link add vg-vm01 type veth peer name vh-vm01
ip link set vg-vm01 netns ns-vm01            # move guest end into the namespace

# 3. The TAP device Firecracker drives as the guest's NIC, inside the netns.
ip netns exec ns-vm01 ip tuntap add tap0 mode tap
ip netns exec ns-vm01 ip link set tap0 up

# 4. Address the /30 subnet and bring the links up.
ip netns exec ns-vm01 ip addr add 10.200.0.2/30 dev tap0
ip netns exec ns-vm01 ip route add default via 10.200.0.1
ip addr add 10.200.0.1/30 dev vh-vm01        # gateway, host side
ip link set vh-vm01 up

# 5. NAT outbound traffic so the private /30 can reach the world.
iptables -t nat -A POSTROUTING -s 10.200.0.0/30 -j MASQUERADE
# For untrusted code, prepend default-deny egress rules to this chain.

# Now point Firecracker at tap0 — inside ns-vm01 — as host_dev_name.
The catch with the correct approach is cost on the hot path. Doing ip netns add, building the veth pair, creating the TAP, and installing iptables rules cold takes on the order of 100ms — fine occasionally, ruinous if it's on the latency budget of every single create. That ~100ms figure is a PandaStack measurement, and it is precisely why our NATID layer pre-allocates the whole apparatus instead of building it per sandbox (see the pool section below). If you build netns-per-VM yourself, budget for either a pool or the slow path.

Reach for netns + veth + NAT when: you run untrusted or multi-tenant code and need structural isolation between guests, plus a clean per-VM place to enforce egress policy. It is the right default for anything sandbox-shaped. The trade-off is exactly the cold-setup cost above, which pushes you toward a CNI plugin (to standardize the wiring) or a pre-allocated pool (to remove it from the create path).

CNI plugins: standardize the wiring (firecracker-go-sdk, Ignite, tc-redirect-tap)

Rather than hand-rolling the netns/veth/NAT dance, you can drive it through CNI (the Container Network Interface) — the same plugin model Kubernetes and container runtimes use to attach a workload to a network. The idea: you write a CNI config describing a chain of plugins (create a bridge or a ptp/veth link, run an IPAM plugin to assign an address, add firewall rules), and a CNI runtime executes that chain to produce a configured namespace. firecracker-go-sdk supports a CNI-based network setup, and Weave Ignite leaned on CNI as its networking model, so this is a fairly well-trodden path for driving Firecracker.

The Firecracker-specific piece is tc-redirect-tap, a CNI plugin that bridges the gap between 'CNI gives me a veth interface' and 'Firecracker wants a TAP.' CNI plugins produce a veth (the container-networking primitive); Firecracker's guest NIC needs a TAP. tc-redirect-tap creates a TAP alongside the CNI-provided veth and uses tc (traffic control) redirect rules to shuttle packets between them, so you get to reuse the entire standard CNI plugin ecosystem — bridge, ptp, host-local IPAM, firewall, bandwidth — and still end up with the TAP Firecracker requires. It's the glue that lets the container-networking world's tooling apply to microVMs.

Reach for CNI when: you want standardized, composable networking config instead of bespoke shell, especially if you already live in a CNI-shaped world (Kubernetes, containerd) and want to reuse IPAM and firewall plugins you already run. The trade-offs: CNI is another layer to understand and operate, plugin behavior and maintenance vary (verify tc-redirect-tap's current status and supported versions against its repo), and the tc-redirect trick, while clever, is one more moving part in the packet path to reason about when something's slow. It standardizes the wiring; it doesn't by itself make cold setup fast — you still want a pool underneath high churn.

vhost-net: throughput for the data path

Everything so far is about where packets go; vhost-net is about how fast. By default, the host side of virtio-net is serviced in the Firecracker userspace process, which means packet processing costs userspace/kernel transitions. vhost-net moves the virtio-net data path into a kernel thread, so the guest's transmit/receive queues are drained in-kernel without bouncing through the VMM's userspace on every packet — materially better throughput and lower CPU overhead for network-heavy guests. If your workload pushes real bandwidth (proxies, scrapers, data movers), this is the lever that matters.

Reach for vhost-net when: your guests are throughput-bound and you're paying for it in CPU or hitting a bandwidth ceiling. Leave it alone when: your workload is latency-on-create and light on data (most AI-agent and code-exec sandboxes spend far more of their life idle or CPU-bound than saturating a NIC), because it adds complexity you won't feel the benefit of. The mechanics, when it helps, and the caveats are worked through in /blog/firecracker-vhost-net-explained — verify vhost-net availability and behavior for your kernel and Firecracker version, since support and defaults have moved over time.

The built-in rate limiter: caps that live in the VMM

Firecracker has one piece of network policy built in: a per-device rate limiter. You can attach token-bucket limits — bandwidth (bytes) and ops (operations/packets), each with a size and a refill time, plus an optional one-time burst — directly to a network interface (and to block devices) in the machine config. Because it's enforced inside the VMM on that device's queues, it's a hard ceiling the guest cannot exceed no matter what it does, and it doesn't depend on host-side tc or firewall rules being correct. For multi-tenant fairness — stopping one noisy guest from starving its neighbors' bandwidth — this is the cleanest available knob.

Reach for the built-in rate limiter when: you need per-VM bandwidth or packet-rate caps enforced at the VMM boundary (fairness, abuse limiting, predictable per-tenant ceilings) and want them independent of host firewall correctness. Its limits are worth pairing with host-side egress policy rather than treating either as sufficient alone: the rate limiter controls how much a guest can send, not where. The token-bucket model, tuning, and how it composes with host controls are in /blog/firecracker-rate-limiter-explained; verify the exact config schema against the FC docs for your version.

MMDS: handing the guest metadata without a network service

Often a guest needs a little bootstrap data — an identity, a token, config — and you'd rather not stand up a metadata server or bake secrets into the image. Firecracker's MMDS (microVM Metadata Service) is a small in-VMM store you populate over the API; the guest reads it over HTTP at a link-local address (the 169.254.169.254 convention familiar from cloud metadata), optionally gated behind an IMDSv2-style token. Nothing leaves the host, there's no extra service to run, and the data is scoped to that one microVM. It's the tidy way to pass per-VM config into an otherwise-generic rootfs.

Reach for MMDS when: you need to inject per-VM identity or config and want it host-local, per-VM-scoped, and free of an external metadata service. Two cautions: whatever you put in MMDS is readable by anything in the guest, so treat it as guest-visible (short-lived tokens over long-lived secrets); and if you're isolating untrusted guests, be deliberate about the 169.254.169.254 address so guest access to MMDS doesn't become confusion with a real cloud metadata endpoint. The full setup, the token flow, and security notes are in /blog/firecracker-mmds-metadata-service.

Egress control: firewalling untrusted guests

The per-VM namespace stops guests reaching each other; it does nothing about where a guest reaches. Out of the box, NAT lets a guest reach the entire internet outbound — and that outbound path is exactly the channel data exfiltration and a prompt-injected agent 'phoning home' use. For untrusted code the right posture is the inverse of the default: default-deny egress, then an allowlist of the few destinations the task genuinely needs (a package registry, one API). Because the per-VM netns owns its own iptables/nftables chain, that chain is the natural enforcement point — the rules govern exactly one guest and vanish when it's torn down.

Isolation between guests and egress control are two different jobs, and the network boundary gives you the first for free but not the second. A per-VM namespace means guest A cannot address guest B; it does not mean guest A cannot exfiltrate to the open internet. For untrusted or multi-tenant workloads, run default-deny egress with a per-task allowlist, confirm the cloud metadata endpoint is unreachable from inside the guest, and remember the boundary enforces your policy — it doesn't choose it. The deeper treatment is in /blog/controlling-network-egress-untrusted-code.

Reach for explicit egress control when: you run anyone's code but your own. Allowlist-based egress, DNS filtering, and per-tenant egress IPs (for rotation or attribution) all hang off the same per-VM namespace, which is why netns-per-VM is the foundation the rest of untrusted-workload networking is built on. Skip the heavy version only when every guest is fully trusted — and be honest with yourself about whether that's actually true.

Pre-allocated netns pools: making isolation cheap on create

This is the approach that resolves the central tension of the whole post. Per-VM namespaces give you correct isolation but cost ~100ms of cold setup; a shared bridge is fast but doesn't isolate. A pre-allocated pool gets you both: do the expensive structural work — create the namespace, the veth pair, the TAP, the iptables rules — ahead of time, for many slots, and keep them standing and ready. Then 'network this new VM' is a fast patch of an existing slot (assign it, fix up the guest's MAC and routes) instead of building a namespace from scratch. The isolation is fully per-VM; the cost just moved off the create path and into a background prebuild.

This is exactly what PandaStack's networking layer, NATID, does. Each agent pre-allocates a pool of 16,384 /30 subnets out of 10.200.0.0/16, each handed out as a complete, pre-built unit — its own network namespace, veth pair, TAP device, and iptables rules — before any sandbox needs it. A /30 holds exactly two usable addresses (a gateway plus one guest), so a sandbox's own subnet has no room for another guest to even exist. Because the structural work is already done, allocating networking for a new sandbox is a fast patch rather than the ~100ms cold build, which is a big part of why a PandaStack create lands at roughly 179ms p50 and ~203ms p99, with the snapshot-restore step itself around 49ms. The only slow path is the first-ever cold boot of a brand-new template. The per-VM isolation is uncompromised; it just isn't rebuilt on every create. The mechanics are in /blog/firecracker-networking-explained and the reference at /docs/internals/networking.

Reach for a pre-allocated pool when: you create sandboxes at any real rate and want per-VM isolation without paying cold-setup on each one — which is essentially any sandbox platform. The trade-off is the machinery to build, manage, and reclaim the pool (a leaked slot is worse than a leaked TAP), which is a real chunk of engineering. It's the correct design at scale, and it's also exactly the sort of undifferentiated plumbing that argues for adopting a platform that already has it rather than building it yourself.

The field, at a glance

The core three approaches side by side — because most Firecracker networking decisions come down to picking one of these as the base and layering the rest (vhost-net, rate limiter, MMDS, egress rules) on top. Everything below is qualitative; verify third-party behavior and versions against current docs.

  • TAP + shared bridge — Isolation: none (shared L2 broadcast domain; guests can reach each other and your host services). Setup cost: lowest, trivial config. Scale: fine for a few trusted VMs on one host; a liability past that. Egress control: coarse (one shared chain for all guests). Reach for it when: a small number of mutually-trusting VMs and you want the least code.
  • Per-VM netns + veth + iptables NAT — Isolation: strong and structural (private namespace per VM; guests can't address each other). Setup cost: high cold (~100ms/VM to build from scratch — a PandaStack figure). Scale: correct at scale but the cold cost forces a pool or CNI. Egress control: excellent (per-VM iptables chain is the enforcement point). Reach for it when: untrusted or multi-tenant code — it's the sandbox default.
  • CNI plugins (+ tc-redirect-tap) — Isolation: as strong as the plugin chain you configure (typically netns-based). Setup cost: moderate; standardized config instead of bespoke shell, still cold per VM unless pooled. Scale: good, reuses the container-networking ecosystem (IPAM, firewall, bandwidth plugins). Egress control: via firewall plugins in the chain. Reach for it when: you want composable, standardized wiring, especially if you already run CNI (Kubernetes/containerd).
  • Pre-allocated netns pool (e.g. NATID) — Isolation: strong and structural (fully per-VM, same as netns-per-VM). Setup cost: near-zero on the create path (a fast patch; the ~100ms build happens ahead of time). Scale: excellent — designed for high create churn (16,384 /30 slots per agent). Egress control: excellent (per-VM chain, pre-built). Reach for it when: you create sandboxes at any real rate and want isolation without per-create cost.

And the composable add-ons, each answering a different question: vhost-net (throughput — reach for it when guests saturate a NIC); the built-in rate limiter (per-VM caps at the VMM boundary — reach for it for multi-tenant fairness and abuse limits); MMDS (host-local per-VM metadata — reach for it to inject identity/config without a metadata service); and explicit default-deny egress (where a guest may reach — reach for it whenever you run untrusted code). These aren't alternatives to the base three; they layer on whichever base you pick.

How PandaStack wires it (the vendor part)

Here's the flagged vendor section. PandaStack is an open-source (Apache-2.0) Firecracker platform, and because it restores a snapshot on every create rather than cold-booting, the networking had to be both fully isolated and fast enough to not blow the create budget. The answer is the pre-allocated pool above: NATID stands up 16,384 pre-built /30 slots per agent — each a private netns with its own veth pair, TAP, and iptables chain — so every sandbox gets structural per-VM isolation and a per-VM egress enforcement point, while the create path only pays a fast patch of the guest's MAC and routes rather than the ~100ms cold build. Because a sandbox's /30 has room for exactly one guest, there is no shared segment for guests to discover each other on, and tearing a sandbox down reclaims its whole slot. That combination — real isolation, pre-allocated so it's cheap — is why creates land around 179ms p50 / ~203ms p99 with the restore step near 49ms, and it's all self-hostable on your own /dev/kvm hosts, so you can watch the namespaces and TAPs get created yourself.

Don't adopt any of this — including PandaStack — on the strength of a description. tc-redirect-tap's behavior, CNI plugin maintenance, vhost-net support, and the exact rate-limiter and MMDS config schemas all drift across kernels and Firecracker versions, and a shared bridge that's 'fine in the demo' becomes a cross-tenant leak the moment the workload is untrusted. Before you commit, stand up your top one or two approaches against your real workload — your rootfs, your churn rate, your threat model — boot untrusted-shaped traffic through them, and confirm the isolation holds and the create latency is what you expect. An afternoon of that settles more than a week of reading networking docs.

The bottom line

Firecracker gives you a TAP device and nothing above it, and the tools in this post are the difference between that and a network you can run untrusted code on at scale. Reach for a shared bridge only when every guest trusts every other guest. Reach for a per-VM netns + veth + NAT the moment isolation matters — it's the sandbox default — and standardize its wiring with CNI (and tc-redirect-tap) if you already live in that world. Layer on vhost-net when you're throughput-bound, the built-in rate limiter for per-VM fairness, MMDS for host-local metadata, and default-deny egress whenever the code isn't yours. And once you're creating VMs at any real rate, pre-allocate the whole apparatus into a pool so isolation stops costing ~100ms per create — the design that lets a fully isolated network be the default on every create rather than a luxury you ration. Start from the job that's hurting — isolation, throughput, egress, or create latency — pick the approach that targets it, and verify third-party behavior against its own docs before you build on it.

Frequently asked questions

What networking does Firecracker provide out of the box?

Very little, by design. Firecracker's entire networking contract is a machine-config entry that binds a guest MAC to a named host TAP device — you create the TAP, tell Firecracker its name, and it attaches the guest's virtio-net NIC to it. That's the whole API surface. IP allocation, NAT, isolation between guests, egress policy, throughput tuning, and scaling to thousands of VMs are all on you, above that TAP. The one piece of network policy built into the VMM is a per-device rate limiter (token-bucket bandwidth and ops caps). Everything else — where the TAP lives, what subnet it's on, whether guests can see each other, what they can reach — is host-side plumbing you assemble around the TAP.

Should I use a Linux bridge or network namespaces for Firecracker?

It depends entirely on trust. A single shared Linux bridge with a TAP per VM is the least code and is fine for a small number of mutually-trusting VMs on one host — but every guest on it shares a Layer 2 broadcast domain, so one can ARP for and reach another and scan for your internal services or the metadata endpoint. For untrusted or multi-tenant code — the workload microVMs exist to run — that's a cross-tenant leak. The isolation-correct approach is a network namespace per VM (its own interfaces, routing table, and iptables rules), connected out with a veth pair and NAT, so guests can't address each other and each VM's egress policy lives in its own chain. The catch is that building a netns + veth + iptables cold costs on the order of 100ms per VM, which is why high-churn platforms pre-allocate the namespaces into a pool instead of building them on each create.

What is tc-redirect-tap and why do CNI plugins need it for Firecracker?

CNI plugins — the Container Network Interface model that Kubernetes and container runtimes use — produce a veth interface, because that's the container-networking primitive. Firecracker's guest NIC needs a TAP device, not a veth. tc-redirect-tap is a CNI plugin that bridges that gap: it creates a TAP alongside the CNI-provided veth and uses tc (traffic control) redirect rules to shuttle packets between them. The payoff is that you can reuse the entire standard CNI plugin ecosystem — bridge/ptp links, host-local IPAM for addressing, firewall and bandwidth plugins — and still end up with the TAP Firecracker requires. firecracker-go-sdk supports a CNI-based setup and Weave Ignite used CNI as its networking model, so it's a well-trodden path. Verify tc-redirect-tap's current maintenance status and supported versions against its repo before relying on it.

How do I stop an untrusted Firecracker guest from exfiltrating data?

Network isolation between guests and egress control are two different jobs, and the network boundary only gives you the first for free. A per-VM network namespace stops guest A from addressing guest B, but the default NAT setup still lets any guest reach the entire internet outbound — and that outbound path is exactly what data exfiltration and a prompt-injected agent phoning home use. To close it, run default-deny egress in the per-VM namespace's iptables/nftables chain and allowlist only the destinations the task genuinely needs (a package registry, one API), confirm the cloud metadata endpoint at 169.254.169.254 is unreachable from inside the guest, and consider pairing that with Firecracker's built-in rate limiter to cap how much a guest can send. The per-VM namespace is the right enforcement point because its chain governs exactly one guest; the boundary enforces the policy you write, but it doesn't choose it for you.

How do you make per-VM network isolation fast enough for a sandbox platform?

Pre-allocate it. Per-VM namespaces give correct isolation but cost around 100ms of cold setup (ip netns add, the veth pair, the TAP, the iptables rules), which is ruinous if it's on the latency budget of every create. A pre-allocated pool does that expensive structural work ahead of time for many slots and keeps them standing, so 'network this new VM' becomes a fast patch of an existing slot — assign it and fix up the guest's MAC and routes — instead of building a namespace from scratch. The isolation stays fully per-VM; the cost just moves off the create path. PandaStack's NATID layer does exactly this, pre-building 16,384 /30 slots per agent (each a private netns with veth, TAP, and iptables), which is a large part of why creates land around 179ms p50 and ~203ms p99 with the snapshot-restore step near 49ms. The trade-off is the machinery to build, manage, and reclaim the pool, since a leaked slot is worse than a leaked TAP.

Run code in a microVM in one API call.

49ms p50 cold start. Fork, snapshot, and scale to zero.

Start free
Written by Ajay Kumar, Founder, PandaStack.