Not being an experienced developer (I would say I’m on the basic level, actually), trying the nginx solution to present a maintenance page was a bit of a nightmare. Today, I had this idea and decided to ask Claude if this would be possible, now that I use Railway for other websites and applications:
“Since Discourse uses Docker, it seems very complicated to show a maintenance message, because it requires nginx, which is a complicated process for me.
Can I, via Railway or any other service, create a “switch” that quickly directs traffic from one server to the other?
For example, right now Cloudflare redirects example.com to my Hetzner server where Discourse is installed.
Would I be able to send Cloudflare to a Railway project/environment/service where that “switch” would be implemented and maybe where changing a variable value from “maintenance” to “live” would direct the traffic to either another Railway service for the maintenance page, or to Hetzner?”
And Claude’s reply:
Yes, technically it works, but running the switch through Railway is the wrong layer for this. You’d be adding a second network hop (Cloudflare → Railway → Hetzner) for every single request when Discourse is live, which means:
- Extra latency on every request, permanently, not just during maintenance
- A new single point of failure (if Railway service goes down, Discourse goes down even though it’s healthy)
- Egress costs on Railway for all your normal traffic, not just maintenance windows
Better approach: do the switch in a Cloudflare Worker. Workers run at the edge (300+ PoPs), so there’s no added round trip when you’re in “live” mode, and toggling is instant.
Architecture:
Cloudflare Worker (bound to example.com)
→ checks KV value "maintenance_mode"
→ if "off": fetch(origin request to Hetzner IP/hostname) and return it
→ if "on": return static maintenance HTML directly from the Worker
Toggle happens via a KV write (through API call or dashboard), not a redeploy. Propagates in seconds, no code push needed.
Minimal worker:
export default {
async fetch(request, env) {
const mode = await env.MAINTENANCE_KV.get("mode"); // "on" or "off"
if (mode === "on") {
return new Response(MAINTENANCE_HTML, {
status: 503,
headers: { "Content-Type": "text/html", "Retry-After": "3600" },
});
}
// pass through to Hetzner untouched
return fetch(request);
},
};
const MAINTENANCE_HTML = `<!DOCTYPE html><html><body><h1>Back soon</h1></body></html>`;
Setup:
1. Create a KV namespace, bind it as MAINTENANCE_KV.
2. Set the Worker route to example.com/*.
3. Toggle with wrangler kv:key put --binding=MAINTENANCE_KV mode "on" or via the Cloudflare API from a shell script.
This gives you the exact “switch a variable, traffic redirects” behavior you described, without touching Discourse’s nginx/docker setup at all, and without adding a permanent extra hop for your live traffic.
One thing worth double-checking before you build this: is your Cloudflare proxy currently doing a DNS-only passthrough or already proxied (orange cloud)? If it’s DNS-only right now, Workers won’t intercept anything, you’d need to switch that record to proxied first.
So I would be interested to know if Claude’s reply is valid and can indeed be implemented? Would that be an instant switch and any user would be presented with the maintenance page? Or does it still redirect some users to the Hetzner installation and showing them the broken page, because of some cache being involved?
And replying to Claude: Yes, my example.com is proxied in Cloudflare.
I then decided to open a new Claude window and ask about this interaction, and I got this (so it seems that a bit more work needs to be done:
The architecture is sound. Cloudflare Workers on a proxied (orange-cloud) zone intercept requests at the edge before they hit Hetzner, so the design works. But the “instant, all users see it” claim needs qualification:
What’s actually true:
Worker route matching is fast, effectively immediate once deployed
KV writes propagate globally, but not instantly — Cloudflare’s docs say up to 60 seconds for KV to replicate across all edge locations. Some users could hit stale “off” state for up to a minute after toggling.
Since Discourse is proxied through Cloudflare already, the Worker will intercept 100% of requests to example.com — no bypass path exists (assuming no other DNS records like a subdomain pointing straight at Hetzner IP, or a CDN cache serving stale content in front of it).
What the reply glossed over / got wrong:
Browser cache: if any prior response (from Hetzner) was cached with a long TTL and Cache-Control headers, a returning visitor’s browser could serve a cached page without even hitting Cloudflare. Minor risk, but real — worth setting Cache-Control: no-store on the maintenance response.
Cloudflare cache: if you have caching rules/page rules caching HTML for example.com, a cached copy could be served without ever reaching the Worker logic, depending on rule order. Workers execute before cache lookup by default in most configs, but if there’s an existing Cache Rule set to “Cache Everything” it can interact in ways worth testing, not assuming.
503 status code: using 503 is correct practice (search engines won’t deindex during brief maintenance), but he should add Retry-After properly formatted — the example does this correctly.
The claim “without touching Discourse’s nginx/docker setup at all” is accurate.
No mention of KV read latency inside the Worker itself — trivial, sub-ms, not a real concern.
Bottom line: workable, and better than the nginx approach. “Instant” is optimistic — call it “propagates within ~60 seconds worldwide” and tell them to test with cache disabled first. The reply should have flagged KV propagation delay instead of implying it’s instant.
