From d8960266b1c7b49530b5684c526f33cce9342617 Mon Sep 17 00:00:00 2001 From: Erich Blume Date: Mon, 9 Feb 2026 11:58:50 -0800 Subject: [PATCH] Serve friendly error page when Fly.io proxy upstreams are unreachable When indri is offline, the Tailscale tunnel is down, or the emergency shutoff was triggered, visitors now see a branded 503 page instead of nginx's default 502. Stale cache is still served when available (proxy_cache_use_stale takes priority). Test endpoint at /_error. Co-Authored-By: Claude Opus 4.6 --- .../feature-fly-proxy-error-page.feature.md | 1 + fly/Dockerfile | 1 + fly/error.html | 68 +++++++++++++++++++ fly/nginx.conf | 14 ++++ 4 files changed, 84 insertions(+) create mode 100644 docs/changelog.d/feature-fly-proxy-error-page.feature.md create mode 100644 fly/error.html diff --git a/docs/changelog.d/feature-fly-proxy-error-page.feature.md b/docs/changelog.d/feature-fly-proxy-error-page.feature.md new file mode 100644 index 0000000..1939d19 --- /dev/null +++ b/docs/changelog.d/feature-fly-proxy-error-page.feature.md @@ -0,0 +1 @@ +Fly.io proxy serves a friendly error page when upstreams are unreachable (indri offline, Tailscale tunnel down, etc.). Test at `docs.eblu.me/_error`. diff --git a/fly/Dockerfile b/fly/Dockerfile index 6e6146c..ec0ad99 100644 --- a/fly/Dockerfile +++ b/fly/Dockerfile @@ -17,6 +17,7 @@ COPY --from=docker.io/grafana/alloy:v1.5.1 \ RUN mkdir -p /var/log/nginx /etc/alloy /tmp/alloy-data COPY nginx.conf /etc/nginx/nginx.conf +COPY error.html /usr/share/nginx/html/error.html COPY alloy.river /etc/alloy/config.alloy COPY start.sh /start.sh RUN chmod +x /start.sh diff --git a/fly/error.html b/fly/error.html new file mode 100644 index 0000000..ef31eae --- /dev/null +++ b/fly/error.html @@ -0,0 +1,68 @@ + + + + + +Service Unavailable + + + +
+
503
+

Service Unavailable

+

The upstream server is not reachable right now. This usually means + the backend is offline for maintenance or the network tunnel is down.

+

Services should recover automatically. Please try again shortly.

+
BlumeOps — eblu.me
+
+ + diff --git a/fly/nginx.conf b/fly/nginx.conf index 1884150..50b0440 100644 --- a/fly/nginx.conf +++ b/fly/nginx.conf @@ -46,11 +46,25 @@ http { limit_req zone=general burst=20 nodelay; + # Serve a friendly error page when upstreams are unreachable + # (indri offline, Tailscale tunnel down, emergency shutoff, etc.) + # proxy_cache_use_stale still takes priority when cached content exists. + error_page 502 503 504 /error.html; + location = /error.html { + root /usr/share/nginx/html; + internal; + } + location = /_error { + root /usr/share/nginx/html; + try_files /error.html =404; + } + location / { set $upstream_docs https://docs.tail8d86e.ts.net; proxy_pass $upstream_docs$request_uri; proxy_ssl_verify off; proxy_ssl_server_name on; + proxy_intercept_errors on; # Cache aggressively — static site only. # Do NOT use these settings for dynamic services.