Maintenance page workaround - can this be done?

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.

Hey!

I’m not answering your question, just being curious. Why do you think you need a maintenance page? Seems quite annoying to set up for little benefit.

My instances are down 5 minutes a month for updates and that’s basically all.

Whenever I made significant changes, like installing plugins for example, it would take sometimes 20 minutes.

Not a big deal, if we consider that these are not done every week or even every month, but for a new visitor that ugly page that something is not working, is not a good first impression. Even for returning visitors who are not aware that the website will be down, that can throw them into “panic mode” thinking that the whole community has been shut down or something, maybe some of them promptly emailing me asking what’s happening.

I think it’s just a nice touch to give the visitor, new or old, to let them know what’s happening.

If this approach Claude suggested is something that takes just a few minutes, but it’s not touched every again, I believe it’s time well spent.

i have a solution but i don’t have time to post it right now. will reply later.

It takes seconds if you move to the two container install.

You can even schedule the switch over at night if you really want to, whilst you and/or your main audience sleep.

You don’t need a maintenance page.