Nebula: A Mesh Overlay When Tailscale Isn't Enough
When you want a mesh VPN with a control plane you actually own, Slack's Nebula is the answer

Contents
Tailscale is the right answer for most people who want their devices to reach each other over an encrypted mesh without port-forwarding. I recommend it constantly. It’s WireGuard underneath, the client is excellent, and the magic that lets two devices behind separate NATs find each other just works. There’s one thing it asks of you that some of us eventually stop wanting to give: trust in a coordination server you don’t run. Tailscale’s control plane — the thing that tells your devices about each other and hands out keys — is hosted by Tailscale. For a homelab that’s usually fine. But if you want the entire system to keep working when a company’s servers don’t, or you simply want to own the control plane end to end, you reach a point where you want something else. That something is Nebula.
Nebula came out of Slack, who built it to connect a very large fleet of servers across clouds and datacentres. It’s open source, it’s a single static binary, and its defining trait is that there is no hosted control plane at all. Trust is established with a certificate authority you generate and keep offline. Devices authenticate to each other with certificates you signed. The only always-on infrastructure is a “lighthouse” — a host with a known address that helps peers find each other — and you run that yourself. Nobody else is in the loop.
Why you’d leave Tailscale for this
Be honest about the trade first, because Nebula asks more of you. Tailscale gives you SSO logins, a slick admin console, MagicDNS, and a mesh that configures itself. Nebula gives you a certificate authority and some YAML. You’re taking on the job that Tailscale’s control plane does for free. The reasons to accept that job are specific:
You want no external dependency. With your own CA and your own lighthouse, the mesh has no third party in its trust or discovery path. If every SaaS on earth went dark, your Nebula network would carry on. For people who self-host precisely to reduce dependence on other companies’ uptime, that closes the loop.
You’re running at a scale or shape Tailscale prices or limits awkwardly. Nebula was built for thousands of hosts. There are no per-node accounts to manage; a host is anything you’ve signed a certificate for. If you’re connecting a large or unusual fleet, the certificate model scales without a billing conversation.
You want the firewall inside the overlay. Nebula has a built-in host firewall defined right in the config, expressed in terms of certificate groups rather than IP addresses. You can say “hosts in the db group accept 5432 only from hosts in the app group” and it’s enforced at every node. That group-based policy is genuinely lovely to work with once you’ve felt it.
If none of those apply, stay on Tailscale — it’s less work and it’s superb. Nebula is for when the control-plane ownership is the point.
The trust model in one breath
Everything hinges on the certificate authority. You run nebula-cert ca once to create a CA key and certificate. That CA cert gets distributed to every host (it’s public; the CA key stays offline and safe). For each device you run nebula-cert sign to mint a host certificate that binds a name, an overlay IP, and a set of groups, all signed by your CA. A host proves who it is by presenting its cert; peers trust it because your CA signed it. There’s no login, no account, no server to phone. The security of the whole mesh is the security of that CA key, so guard it the way you’d guard any root key.
Building it: CA, lighthouse, hosts
Start with the certificate authority and a couple of host certs:
| |
Each host gets three files: the CA cert (ca.crt), its own cert (host.crt), and its own key (host.key). Copy them to the device along with a config.yml. The lighthouse config is the one that needs a reachable address — it’s the only host that must be reachable at a known IP or port, typically a VPS or a home host behind a forwarded UDP port:
| |
A regular host’s config is nearly identical, except am_lighthouse: false and it points at the lighthouse in lighthouse.hosts:
| |
Start the binary (nebula -config /etc/nebula/config.yml), and the host registers with the lighthouse, which then helps it discover and directly connect to peers. Traffic between two ordinary hosts flows peer-to-peer over UDP; the lighthouse only brokers the introduction and steps out of the data path. That’s the same NAT-traversal trick that makes these overlays feel magic, and it’s why one lighthouse can serve a large mesh without becoming a bottleneck.
Notice the firewall block. Those rules are the whole security posture of the node, expressed in terms of certificate groups. Change a host’s allowed callers by changing which groups may reach a port — no IP lists to maintain as machines come and go.
Reaching things that aren’t on the mesh
A mesh where every host runs the agent is the easy case. Real networks always have a printer, a NAS appliance, or an IoT gadget that will never run Nebula, and you still want to reach them from the road. Nebula handles this with unsafe_routes: you nominate one host on the target LAN as a gateway, enable IP forwarding on it, and tell the mesh that a given subnet is reachable through that host’s overlay IP.
| |
The gateway host needs net.ipv4.ip_forward=1 and a firewall rule permitting the forwarded traffic. It’s the equivalent of Tailscale’s subnet router, and it means one Nebula host can pull an entire physical segment onto the overlay for the devices that can’t join it themselves. Keep the gateway well secured — it’s a bridge between the mesh and a whole LAN, so its host certificate and firewall rules deserve extra scrutiny.
How it sits with the rest of your network
Nebula gives every host a stable overlay IP that follows it anywhere, which pairs well with a segmented LAN. If your services live on their own VLANs behind firewall rules — the approach in segmenting a home network — the overlay becomes a clean, authenticated way to cross those segments for management without poking holes in the physical firewall. And because it’s a genuine overlay with its own addresses, it composes nicely with containers that carry their own real IPs, letting a container be a first-class mesh member.
The lighthouse is the one piece that needs to be reachable from the internet, and that’s where home networking gets in the way. If your ISP has you behind carrier-grade NAT, you can’t simply forward a UDP port to your home lighthouse — the router that matters isn’t yours. Put the lighthouse on a cheap VPS with a public IP instead, or read up on escaping double-NAT first. A VPS lighthouse is the common pattern anyway: it’s just a coordinator, it needs almost no resources, and it keeps your discovery point independent of your home connection’s quirks.
Troubleshooting
Hosts start but never see each other. Check the lighthouse first. It must be reachable at the address in static_host_map on its UDP port (4242 by default), which means the port is genuinely open end to end — a home lighthouse behind a NAT needs that UDP port forwarded, and CGNAT breaks it entirely. Confirm from outside with a UDP probe; a ping proves nothing about a UDP listener.
They see the lighthouse but can’t connect peer-to-peer. This is NAT hole-punching failing between two peers, common with strict/symmetric NATs on both ends. Nebula can relay through the lighthouse in newer versions if you enable relays; otherwise, place one peer somewhere with a friendlier NAT, or route through a host that both can reach.
Handshake failures / “certificate is not valid”. Two usual causes. Clock skew — certificates have validity windows and a host with a badly wrong clock will reject perfectly good certs, so run NTP everywhere. Or the CA cert on one host doesn’t match the CA that signed the peer’s cert (you regenerated the CA and forgot to redistribute). Certs also expire; nebula-cert print -path host.crt shows the dates.
Connected but throughput is poor or connections stall on large transfers. Classic MTU problem. The overlay adds encapsulation overhead, so the effective MTU inside the tunnel is smaller than the physical link. Nebula defaults sensibly, but if the underlying path has a reduced MTU (some ISPs, some tunnels), lower the tun.mtu in the config until large transfers stop hanging.
Firewall rule “isn’t working”. Remember Nebula’s firewall is default-deny inbound once you define any inbound rules, and rules match on the presenting certificate’s groups. If a host can’t reach a port it should, check that its cert actually carries the group the target’s rule allows — nebula-cert print again — and that you signed it with that group rather than adding the group only to the config.
The verdict
Nebula is the overlay for people who want to own the control plane and mean it. If you’ve outgrown trusting a hosted coordinator, or you’re connecting a fleet large or odd enough that per-node management chafes, or you fall in love with group-based firewalling defined right beside the tunnel, it’s excellent and it’s rock solid — Slack runs enormous infrastructure on it.
For the ordinary case of “I want my five devices to reach each other from anywhere”, it’s more work than you need, and Tailscale will make you happier with an afternoon less effort. The deciding question is simple: do you want to run a certificate authority? If that sentence excites you rather than tires you, Nebula is your overlay. If it makes you sigh, you already have your answer, and there’s no shame in the easier road.




