Happy Eyeballs: Dual-Stack Connection Preference Explained

Happy Eyeballs: Dual-Stack Connection Preference Explained

Most dual-stack clients don’t “pick IPv6 or IPv4” once and for all—they race them. The idea, nicknamed Happy Eyeballs, is simple: start with a bias toward IPv6, introduce small delays so you don’t overload networks, and keep whichever connection completes first. Done right, this hides broken paths and shaves visible wait time without flooding servers with duplicate attempts.

Two IETF documents define how this works in practice. The 2012 algorithm (often called v1) introduced fast fallback from IPv6 to IPv4 so users don’t stare at a spinner when IPv6 is down, and the 2017 update (v2) tightened the steps: send both AAAA and A queries, sort and interleave addresses, and stagger connection attempts with specific timers.

If you run an app, CDN, or proxy, a handful of knobs determine whether Happy Eyeballs helps you or hurts you. The big ones are the DNS resolution behavior (including a short “resolution delay” to give AAAA a chance), the connection attempt spacing, and how you order and reuse destination addresses. Getting those right keeps latency low while still preferring IPv6.

How Happy Eyeballs Decides Between IPv6 and IPv4

The decision starts at name resolution. A dual-stack client fires a AAAA query first, immediately followed by an A query. Resolution is asynchronous; whichever answer arrives first can trigger connection setup. If an A answer returns first, v2 recommends waiting a tiny resolution delay—recommended 50 ms—to give AAAA a chance, so IPv6 doesn’t lose purely due to reply ordering. Late AAAA answers can still be folded into the candidate list if a connection hasn’t been established yet.

Timers That Matter

Three timer concepts shape behavior. First, the resolution delay (50 ms recommended) prevents IPv4 from always winning when DNS reorders answers. Second, the connection attempt delay is the gap between starting one connect() and the next; the recommended default is 250 ms, and a nuanced stack times the next attempt to when the prior one would send its second TCP SYN based on the retransmission timer. Third, guardrails: the delay must not be less than 10 ms, should be at least 100 ms, and should have an upper bound (recommended 2 s).

Sorting, Interleaving, and Address Selection

After DNS answers arrive, the client sorts destinations using the default destination address selection rules (RFC 6724), optionally seasoning the list with historical RTTs and previously used addresses. Then it interleaves families: if the first entry is IPv6, the next one is pulled from IPv4, and so on. Interleaving avoids waiting through a long run of one family if that family is impaired.

Connection Racing and Cancellation

The client starts one connection attempt, then—after the connection attempt delay—starts the next candidate while keeping previous attempts alive. When any attempt finishes the handshake, all others are canceled, and outstanding DNS queries may be canceled after a short grace period to let caches fill. If new DNS answers arrive mid-race, they’re merged into the not-yet-tried portion so the race stays fair.

Tuning Behavior in Real Implementations

Implementations differ in defaults and exposed knobs. cURL (libcurl) defaults its “happy eyeballs timeout” to 200 ms and lets you tune it via CURLOPT_HAPPY_EYEBALLS_TIMEOUT_MS or the command line; historically several browsers have used around 300 ms for the family switch, while Safari is notable for actually implementing the RFC’s 50 ms resolution delay. These differences explain why the same hostname can feel faster in one client than another on the same network.

When to Nudge the Timers

Shorten the connection attempt delay if you consistently see slow first SYN completion on one family and you’d rather not wait for it to time out; lengthen it if your backends suffer from too many speculative connections or you observe churn. Keep the resolution delay small; stretch it only if your AAAA answers are known to trail A by more than a few tens of milliseconds and you truly want to favor IPv6 at the cost of occasional extra wait.

State, Caches, and Per-Network Memory

A well-behaved engine tracks historical RTT and “addresses used” to inform ordering, but it must scope this state to the current network and flush it when the interface changes. That keeps heuristics from penalizing a healthy network for problems seen on a different one.

Operational Edge Cases You Should Expect

IPv6-only segments with NAT64/DNS64 are common now. If apps use IPv4 literals, the stack may need to synthesize IPv6 endpoints for them or provide 464XLAT, so v4-only inputs still work in an IPv6-only environment. v2 details how to keep these networks usable without breaking the bias toward IPv6.

DNS Behavior Can Make or Break Perceived Speed

Slow or reordered answers often explain “why did it pick IPv4” reports. If A consistently arrives well before AAAA at your resolver, the resolution delay is the only thing standing between you and an IPv4-first bias. Where multiple DNS server addresses exist on an interface, v2 recommends sending queries over the IPv6-reachable resolver first; penalize unresponsive resolvers and rotate as needed. Measure both DNS response timings and connect handshake RTTs before changing defaults.

Server and Network Hygiene Still Matters

Happy Eyeballs isn’t a band-aid for broken routing. Ensure IPv6 prefixes are fully reachable, that anycast DNS returns consistent A/AAAA sets across vantage points, and that path MTU discovery works so post-handshake data doesn’t stall. If you terminate TLS, keep certificate chains and ALPN offerings consistent across families so the “first to connect” is also “first to handshake.”

Checklist: Shipping a Good Happy Eyeballs Experience

Send AAAA then A without waiting; apply a 50 ms resolution delay if A wins the race; sort with RFC 6724, interleave families with a first-family count of 1; start the next connect after ~250 ms (or at the second-SYN point) with a hard lower bound (≥100 ms, never <10 ms) and an upper bound (~2 s); cancel losers fast; cache per-network outcomes; and validate your choices with real RTT and DNS traces before rolling to users.

Happy Eyeballs and Dual-Stack Connectivity (FAQ)

Capture DNS timing and TCP handshake logs; if AAAA appears and the first connect starts on v6 within a tiny delay, it’s preferring v6, and you can also run an IPv6 Test from the same network to confirm end-to-end reachability.

The client waits a small resolution delay to give AAAA a chance; if it arrives in that window, v6 goes first, otherwise v4 starts while late AAAA can still be added to the queue before a connection is established.

Usually not; publish consistent A and AAAA, ensure resolvers respond quickly for both, and verify publication from multiple vantage points with a quick DNS Lookup.

They use different defaults for the family-switch delay and DNS handling; a 200–300 ms gap versus a dynamic scheme can change which family gets a head start.

If the first SYN commonly takes longer, reduce the delay so the other family races sooner; keep a hard floor around a tenth of a second to avoid too many parallel attempts.

Hit an endpoint that echoes the remote address or visit a site that shows My IP Address, then compare while toggling family preferences.

It can, because the race stops at handshake; you still need to test data transfer, watch for stalls, and validate path MTU discovery.

The stack can synthesize reachable endpoints or use 464XLAT so v4-literal traffic still works, while the algorithm continues to prefer native v6 when available.