From 0c6223fcf13c65bc8d77a764a6b39295a04304eb Mon Sep 17 00:00:00 2001 From: Erich Blume Date: Sun, 8 Feb 2026 02:16:19 -0800 Subject: [PATCH] Fix Fly.io proxy: use TUN networking, preauthorize key, move healthz Resolves multiple issues found during first deploy: - Drop --tun=userspace-networking: Fly.io Firecracker VMs support TUN natively; userspace mode broke MagicDNS and Tailscale IP routing - Add preauthorized=True to TailnetKey: required when tailnet has device approval enabled, otherwise containers hang on restart - Move /healthz to default_server: Fly health checks send no Host header, so healthz must be on the catch-all server block - Change region from sea (deprecated) to sjc - Add iptables/ip6tables for TUN device support - Add proxy_ssl_server_name for proper TLS SNI Co-Authored-By: Claude Opus 4.6 --- docs/how-to/expose-service-publicly.md | 8 +++++++- fly/Dockerfile | 3 ++- fly/fly.toml | 2 +- fly/nginx.conf | 15 ++++++++++----- fly/start.sh | 7 ++++--- pulumi/tailscale/__main__.py | 1 + 6 files changed, 25 insertions(+), 11 deletions(-) diff --git a/docs/how-to/expose-service-publicly.md b/docs/how-to/expose-service-publicly.md index 26b9ce6..ccbe1cb 100644 --- a/docs/how-to/expose-service-publicly.md +++ b/docs/how-to/expose-service-publicly.md @@ -250,16 +250,22 @@ Extend the existing `pulumi/tailscale/` project. ```python # Auth key for Fly.io proxy container -flyio_key = tailscale.TailscaleKey( +flyio_key = tailscale.TailnetKey( "flyio-proxy-key", reusable=True, ephemeral=True, + preauthorized=True, # Skip device approval on the tailnet tags=["tag:flyio-proxy"], expiry=7776000, # 90 days ) pulumi.export("flyio_authkey", flyio_key.key) ``` +> **Note:** `preauthorized=True` is required if your tailnet has device +> approval enabled. Without it, each new container start (including +> health-check restarts) creates a node that needs manual approval, +> causing the container to hang before nginx starts. + **Add to `pulumi/tailscale/policy.hujson`:** Tag owner: diff --git a/fly/Dockerfile b/fly/Dockerfile index 7d71d85..11af474 100644 --- a/fly/Dockerfile +++ b/fly/Dockerfile @@ -6,7 +6,8 @@ COPY --from=docker.io/tailscale/tailscale:stable \ COPY --from=docker.io/tailscale/tailscale:stable \ /usr/local/bin/tailscale /usr/local/bin/tailscale -RUN mkdir -p /var/run/tailscale /var/lib/tailscale +RUN mkdir -p /var/run/tailscale /var/lib/tailscale \ + && apk add --no-cache iptables ip6tables COPY nginx.conf /etc/nginx/nginx.conf COPY start.sh /start.sh diff --git a/fly/fly.toml b/fly/fly.toml index 676b215..90b649e 100644 --- a/fly/fly.toml +++ b/fly/fly.toml @@ -1,5 +1,5 @@ app = "blumeops-proxy" -primary_region = "sea" +primary_region = "sjc" [build] diff --git a/fly/nginx.conf b/fly/nginx.conf index c2ed6bb..bf89e09 100644 --- a/fly/nginx.conf +++ b/fly/nginx.conf @@ -25,6 +25,7 @@ http { location / { proxy_pass https://docs.tail8d86e.ts.net; proxy_ssl_verify off; + proxy_ssl_server_name on; # Cache aggressively — static site only. # Do NOT use these settings for dynamic services. @@ -43,14 +44,18 @@ http { add_header X-Cache-Status $upstream_cache_status; } + } + + # Catch-all: reject unknown hosts, but serve health check + server { + listen 8080 default_server; + location /healthz { return 200 "ok\n"; } - } - # Catch-all: reject unknown hosts - server { - listen 8080 default_server; - return 444; + location / { + return 444; + } } } diff --git a/fly/start.sh b/fly/start.sh index 918c455..8478f09 100644 --- a/fly/start.sh +++ b/fly/start.sh @@ -1,8 +1,9 @@ #!/bin/sh set -e -# Start tailscale in userspace networking mode (no TUN device needed) -tailscaled --tun=userspace-networking --statedir=/var/lib/tailscale & +# Start tailscale daemon. Fly.io runs Firecracker microVMs which support +# TUN devices natively — no need for --tun=userspace-networking. +tailscaled --statedir=/var/lib/tailscale & sleep 2 # Authenticate and join tailnet @@ -12,5 +13,5 @@ tailscale up --authkey="${TS_AUTHKEY}" --hostname=flyio-proxy until tailscale status > /dev/null 2>&1; do sleep 1; done echo "Tailscale connected" -# Start nginx +# Start nginx — MagicDNS resolves *.tail8d86e.ts.net hostnames nginx -g "daemon off;" diff --git a/pulumi/tailscale/__main__.py b/pulumi/tailscale/__main__.py index 80e2793..631780e 100644 --- a/pulumi/tailscale/__main__.py +++ b/pulumi/tailscale/__main__.py @@ -77,6 +77,7 @@ flyio_key = tailscale.TailnetKey( "flyio-proxy-key", reusable=True, ephemeral=True, + preauthorized=True, tags=["tag:flyio-proxy"], expiry=7776000, # 90 days )