Fixed dev gateway forcing X-Forwarded-Proto https on ActivityPub routes#29076
Conversation
no ref - the ActivityPub service derives its URLs — and whether it fetches Ghost's JWKS over http or https — from X-Forwarded-Proto (via x-forwarded-fetch's behindProxy), so hardcoding https broke plain http://localhost:2368 dev: the AP service tried to fetch the JWKS over TLS from a plain-http gateway and Ghost<->AP auth failed - the hardcode existed for the tunnel workflow (tailscale funnel / ngrok), where TLS terminates at the agent and everything reaches Caddy as http; trusted_proxies now covers that case instead — the agents inject X-Forwarded-Proto: https and connect from private ranges, so Caddy passes the header through untouched - this matches the AP repo's own dev nginx config, which maps X-Forwarded-Proto to https-if-claimed, otherwise http; the hardcode on these routes was a copy-paste artifact from the Ghost blocks - the Ghost-backend routes keep forcing https: Ghost builds URLs from config and only uses the header for redirect decisions, so the lie is harmless there and prevents redirect loops when url is configured as https
WalkthroughThe dev-gateway Caddyfile is updated to configure trusted proxies on the :80 site via Changes
Related issues: None specified. Related PRs: None specified. Possibly related PRs
Suggested labels: docker, configuration Suggested reviewers: None specified. 🐰 A hop, a trust, a proxy's flight, 🚥 Pre-merge checks | ✅ 4✅ Passed checks (4 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
| Command | Status | Duration | Result |
|---|---|---|---|
nx run @tryghost/admin:build |
✅ Succeeded | 8s | View ↗ |
nx run ghost:build:assets |
✅ Succeeded | 2s | View ↗ |
nx run ghost:build:tsc |
✅ Succeeded | 7s | View ↗ |
nx run-many --target=build --projects=tag:publi... |
✅ Succeeded | <1s | View ↗ |
nx run-many -t lint -p ghost-monorepo |
✅ Succeeded | <1s | View ↗ |
💡 Verify your cache is correct by running tasks in a sandbox. Read docs ↗
☁️ Nx Cloud last updated this comment at 2026-07-03 13:48:16 UTC

Problem
Accessing the ActivityPub service through the docker dev gateway over plain
http://localhost:2368fails: Ghost↔AP auth breaks and you can't get the two talking locally.Root cause: the gateway hardcodes
header_up X-Forwarded-Proto httpson the three ActivityPub routes. The AP service reconstructs request URLs from that header (viax-forwarded-fetch'sbehindProxy), and its role-guard only downgrades the Ghost JWKS fetch to http when the request URL isn't https (role-guard.ts). With the spoofed header it always trieshttps://localhost:2368/ghost/.well-known/jwks.json, nothing serves TLS there, JWT verification fails, and every AP request 401s.Why the hardcode existed
The dev gateway was built around the tunnel workflow (né
dev:forward): the documented AP dev setup points a Tailscale funnel / ngrok https domain at port 80, TLS terminates at the agent, and everything reaches Caddy as plain http. Forcinghttpsupstream kept Ghost from redirect-looping and made the AP service mint https federation URLs in that flow.Fix
Tell the AP service the truth, without breaking the tunnel flow:
trusted_proxies static private_rangesto the global options. Everything reaching this gateway arrives from loopback or the docker bridge, so Caddy treats the sender as a proxy hop and passes throughX-Forwarded-Protowhen the client supplies it — which tailscale funnel, ngrok, and cloudflared all do.X-Forwarded-Proto: httpsfrom the three AP routes. Direct localhost requests now gethttp(fixing local dev); tunneled requests still arrive upstream ashttps.https: Ghost builds its URLs from config and only uses this header for redirect decisions, so the lie is harmless there and prevents redirect loops whenurlis an https tunnel domain.This also matches the AP repo's own reference proxy — its dev nginx config maps
X-Forwarded-Prototo https-if-claimed-else-http. The hardcode on these routes was a copy-paste artifact from the Ghost blocks.Verification
caddy validate+caddy fmtclean with the dev-gateway image. Ran the real gateway container against a header-echo upstream:X-Forwarded-Protoseen upstream/.ghost/activitypub/*direct localhosthttp✅ (washttps— the bug)/.ghost/activitypub/*with tunnel-agent headerhttps✅ passed through/.well-known/webfinger,/.well-known/nodeinfo/foo,/ghost/api/*(Ghost backend)https✅ unchangedKnown edge: a tunnel that terminates TLS but injects no
X-Forwarded-Proto(e.g. a rawssh -RTCP forward) would now stamphttpwhere the hardcode papered over it — the documented agents all inject the header.No automated test — this config only runs in the local dev environment; verification above exercises the real image + Caddyfile.