Skip to content

ADR-060: Defer custom-domain auth proxy until users or repeat reachability reports

Status: Deferred Date: 2026-05-16 Context: On the May 14 multiplayer playtest, one Beacon team member reported Failed to fetch on both sign-in and password recovery. Diagnosis on May 16 traced it to net::ERR_CONNECTION_TIMED_OUT against rxhsyhpkfyysuhvoccpi.supabase.co — his VPN exit couldn't reach Supabase's auth host. CoachJ separately observed the same failure fingerprint on his own machine with BitDefender's web protection enabled. The shared root cause is that long random subdomains of less-known parent hosts (*.supabase.co) trip AV/VPN heuristics that silently drop the connection at the TCP layer. The durable fix is to put auth (and ideally realtime + PostgREST + storage) behind a custom domain we control — auth.beaconinfra.xyz or similar — so the browser never sees the random Supabase hostname.

At a glance

What it decides: defer putting auth behind a custom domain to dodge AV/VPN heuristics that drop connections to *.supabase.co. Deferred — revisit when a third reachability report lands, the first paid user onboards, monetization offsets ~$45/mo, a public launch nears, or realtime gameplay (not just sign-in) starts failing.

  • Why defer — two cases, both with workarounds (split-tunnel, AV whitelist, hotspot); neither is losing access.
  • Cost flipped the call. Supabase Pro is ~$45/mo at the current 2-project org, not the $25 headline — too much to fix one teammate's VPN routing pre-revenue.
  • Rejected alternatives — Pro now (cost) and a free Cloudflare Worker (~half-day build + tricky realtime websocket proxying).
  • Safe to defer — the fix is mechanical, not architectural. DNS + dashboard + one env var + redeploy (~30 min on Pro path).
  • Urgency softened. A follow-up health check passed with VPN on, suggesting the two cases may have been transient routing, not a structural block.

Decision

Defer the proxy. Don't ship a custom-domain auth path right now. Users hitting AV/VPN reachability blocks work around manually (split tunneling, AV whitelist, mobile hotspot). Revisit when a trigger below fires.

Rationale

  1. Two cases, both with workable workarounds. The affected team member uses a VPN that can be toggled or split-tunnelled. CoachJ's BitDefender case is internal/diagnostic, not a real user blocker. Neither is losing access to the product.
  2. Pre-revenue, no monetization model defined. Supabase Pro ($25/mo) is the cleanest path — native Custom Domains handles auth + realtime + PostgREST + storage uniformly — but spending real cash on infrastructure friction for two known cases is hard to justify when there's no offsetting revenue and the team is still figuring out the model. CoachJ on May 16: "I don't even know how we're monetizing this yet. So even when we do have users, then what? Like I'm still paying out of pocket for all this stuff."
  3. The free path (Cloudflare Worker) isn't actually free in time. ~Half-day of build + ongoing maintenance + the genuine complexity of proxying realtime websockets through a Worker. Carry cost outweighs the value of two known fixes.
  4. The fix is mechanical when revisited. No design work blocked by deferring. Whichever path we pick later, the actual change is DNS + dashboard config + one env var update + redeploy.

Alternatives Considered

  • Upgrade Supabase Pro now and use native Custom Domains (~$45/mo actual cost across our org). Cleanest solution. PITR and removal of free-tier auto-pause come as side benefits. The base Pro plan is $25/mo per org and unlocks Pro features for every project, but each active project gets billed compute ($10/mo Micro) separately. With Sync + Mystery Maker both active, real cost is ~$45/mo, not $25/mo — the per-project compute caught us off-guard during the upgrade flow and pushed the decision back to defer. Rejected because the side benefits aren't currently felt as pain (CoachJ explicitly said PITR isn't a real issue) and the auth-friction problem has only two cases.
  • Build a Cloudflare Worker proxy now (free). Rejected: half-day of work + maintenance carry cost + websocket realtime complexity, all to fix two cases that have workarounds. Disproportionate effort.
  • Document the VPN/AV issue in user-facing FAQ instead of fixing it. Considered as a lightweight middle path. Acceptable as a stopgap if reports start to accumulate, but doesn't change the underlying friction. Not chosen on its own — paired with this deferral if reports increase.

Discussion

Initial framing after the May 14 playtest was "this will keep happening — build the proxy now." That held through diagnosis: ERR_CONNECTION_TIMED_OUT on the random Supabase hostname is exactly the failure mode AV suites and corporate firewalls produce, and the fix is well-understood.

The reframe came when weighing concrete options: - $25/mo for Pro felt small in isolation, but pre-revenue every recurring cost compounds. - Free Worker path was still half-a-day of focused work, and the realtime websocket piece is genuinely tricky (Workers support websockets but the cross-origin auth-cookie story for Supabase realtime through a Worker is non-trivial). - The actual harm right now is two cases with workarounds, not a stream of complaints.

CoachJ's framing: defer, "glue together" the ADR in a state we can call up easily, and revisit when users actually start coming in. The aspirational-thousands argument cuts both ways — yes, this will matter at scale, but at scale we'll also have signal on monetization and the spend decision becomes easier.

Reversed and re-confirmed within the same session. After writing the initial deferral, CoachJ reversed: "OK well then let's execute, no? Let's get this working for that person." The pull was strong — we'd just diagnosed a real teammate-blocking issue and the deferral felt like leaving him stranded. Recommended path swung to Pro plan ($25/mo) over Worker (free, ~half-day + websocket complexity) on the reasoning that for a one-person dev team, the saved engineering hours dominate the recurring cost. CoachJ accepted: "OK fine I'll do pro as long as for sure this will solve the issue for them." Then while clicking through the upgrade flow, discovered the actual cost was ~$45/mo, not $25 — the per-project compute add-on for the second active project (Mystery Maker) made the bill 80% higher than my initial estimate. At $45/mo × 12 = $540/yr to fix one teammate's VPN routing, the math flipped back hard: "that is not worth it for this one person's VPN." Returned to the original deferred decision. The lesson for future readers: the cost wasn't $25/mo — it's $45/mo at the current 2-project org size, and any future re-evaluation should anchor on $45 (or whatever per-project compute scales to if more projects are added), not the headline base plan price.

What makes deferral safe: the fix is mechanical, not architectural. No code shape changes. Single env var update at revisit time.

May 16 follow-up: Tae ran a direct browser health check against https://rxhsyhpkfyysuhvoccpi.supabase.co/auth/v1/health the next day. It returned JSON ({"message":"No API key found in request"}) rather than timing out — meaning the route was working. He then signed in successfully with his VPN still on. The May 14 failure appears to have been a transient routing issue, not a structural VPN-vs-Supabase incompatibility. This softens the trigger count: the two "confirmed cases" may both have been transient rather than persistent blocks. The structural risk (long random subdomains tripping AV/VPN heuristics) is still real, but the urgency is lower than it appeared on May 16.

Consequences

  • Users hitting AV/VPN reachability blocks must work around manually (split tunneling, AV whitelist, mobile hotspot, different browser). No in-product affordance for this yet.
  • The random *.supabase.co hostname remains visible to AV/firewall heuristics on every auth and realtime request. Acceptable while user base is team-only.
  • If/when this becomes the bottleneck for a real user we're trying to retain, the response time is "DNS change + dashboard + redeploy" — ~30 min on Pro path, ~half-day on Worker path. Not a multi-week project.
  • Realtime gameplay (post-sign-in) goes to the same *.supabase.co host via websocket. Users with AV/VPN reachability issues will likely hit it again during gameplay, not just sign-in. Workaround scope is "use the app off-VPN entirely," not just "sign in off-VPN."

Triggers for revisit:

  1. A third reachability report. Two confirmed cases is anecdote; three is pattern. Especially if any of the three is a real external user, not internal team.
  2. First paid user onboarded. Losing access to a paid product over infrastructure friction is more expensive than $45/mo. At that point, default to Pro path immediately.
  3. Monetization model defined and revenue projection trivially offsets ~$45/mo Pro cost. Removes the "out-of-pocket" objection. (Note: cost scales with active project count — see Discussion.)
  4. Public launch or press moment in next 30 days. Pre-launch upgrade is calm; mid-launch upgrade is a fire drill. Front-load it.
  5. Realtime websocket reachability issues reported during gameplay (distinct from sign-in). Expands the scope beyond auth and makes the unified Pro Custom Domains path more attractive than a Worker that would have to handle both surfaces.

When triggered: - Pro path: Add CNAME for auth.beaconinfra.xyz (or chosen subdomain) → Supabase. Configure Custom Domain in Supabase dashboard. Update NEXT_PUBLIC_SUPABASE_URL in Vercel env. Redeploy. ~30 min. - Worker path: Write Cloudflare Worker that proxies /auth/v1/*, /rest/v1/*, /realtime/v1/* (websocket upgrade), /storage/v1/* to the Supabase project. Add DNS for worker route. Update NEXT_PUBLIC_SUPABASE_URL. Redeploy. ~half-day, plus websocket testing.

Key files (at revisit time):

  • src/lib/supabase/client.ts — browser client; reads NEXT_PUBLIC_SUPABASE_URL. No code change needed beyond env var.
  • src/lib/supabase/server.ts — server client; same env var. No code change needed.
  • .env.local / Vercel env — single var update at revisit time.
  • Supabase Auth dashboard → URL Configuration → add new origin to allowed redirect URLs if domain changes how the redirect URL is composed.