Skip to main content

IPv6 the honest way: ULA for identity, GUA for the internet

·625 words·3 mins

IPv6 at home is where confident plans go to get corrected by reality. Mine did — twice — before settling into something clean. The lurches are the interesting part, so here they are.

Two address families, only one of which is yours to design
#

The key mental unlock: you’re planning two IPv6 layers.

  • GUA (global) comes from your ISP. You don’t control the bits, and the ISP can renumber it. Building meaning into GUA is building on sand.
  • ULA (fd00::/8, a random /48 you generate) is yours forever. It never renumbers.

So the model that survived: ULA is the stable internal identity — it’s what internal DNS AAAA records point at, what the failover VIPs use, what every service config references. GUA is purely for reaching the internet, picked up automatically via SLAAC, and nothing internal depends on it.

Lurch #1: “ULA is deferred”
#

My first plan deferred ULA entirely, because the gateway seemed to allow either a delegated prefix or a static prefix per network, not both. Wrong framing: that limit is about SLAAC clients. Statically-configured infrastructure hosts don’t care — you just type the ULA in. And it turned out the gateway happily carries a static ULA and a GUA on the same network. So ULA wasn’t deferred at all; full dual-stack was deployable now.

Lurch #2: “I only have one /64”
#

Reading the WAN router config, I briefly saw what looked like a single delegated /64 and started designing around that constraint — which is brutal, because you can’t split a /64 (SLAAC needs a full /64 per VLAN). Panic. Then a closer look: the ISP delegates a /61 — eight /64s. One is the WAN transit link; the other seven route to my LAN. Four internet-facing VLANs, four /64s, three spare. Crisis cancelled.

The misconception that cost the most time
#

Trying to make GUA readable the way the ULA is. My ULA is a /48, so I encode the VLAN right into the address: …:<vlan>::/64. Lovely. So I tried the same with GUA — and it doesn’t work, because the GUA delegation is a /61: only three subnet bits, nowhere near enough to hold a VLAN number. Writing …:<gua-prefix>:52::… doesn’t create a “VLAN-52 subnet”; the /64 is fixed by the first four hextets and the rest is just host bits. Lesson: GUA is not self-describing — pick one of your /64s per VLAN and document the mapping; don’t try to smuggle the VLAN into the address.

Where IoT quietly wins
#

The IoT VLAN blocks external egress. In v6 that’s even cleaner than in v4: I give it no GUA prefix at all. A device with only a link-local address can’t even attempt to phone home over v6 — there’s nothing to phone home from. No NAT tricks, no shim; the hole is closed structurally by the absence of a prefix.

Two things that are non-negotiable with real v6
#

  1. WAN-inbound must be default-deny. In v4, NAT incidentally hides every host. A GUA host is globally routable — a permissive inbound rule exposes it directly. This is the IPv6 gotcha; verify it before anything picks up a GUA.
  2. Firewall parity. Every v4 rule needs its v6 twin — there’s no NAT to paper over a missing one. (The tooling I ended up using applies each rule to both families at once, which makes this free — more on that next post.)

What I’d tell past-me
#

  • Design two layers on purpose: ULA = identity, GUA = internet. Don’t let the ISP’s prefix be your source of truth.
  • Check the actual delegation size before you architect around a scarcity that may not exist.
  • You can’t encode meaning into a /61 of GUA. Stop trying; write a table instead.