Skip to content

Cloudflare Worker failover

The Sprey v1 website uses a Cloudflare Worker instead of paid Cloudflare Load Balancing:

visitor -> sprey.win -> Cloudflare Worker -> primary WordPress VPS
\-> sprey-outage.pages.dev on failure

The Worker attempts primary for every request. A network failure, five-second timeout, or configured upstream origin failure causes that request to receive the static outage page. Every later request tries primary again, so the site returns automatically after the VPS recovers.

Configured status coverage is currently:

502 503 504 520 521 522 523 524 525 526

This is request-time failover. It does not run periodic probes, share health state between requests, or discover an outage before a visitor arrives. The static page is only an outage notice. It cannot preserve or provide WooCommerce cart, checkout, accounts, orders, sessions, or payment flows.

  • sprey.win is an active Cloudflare zone and its WordPress DNS record is proxied.
  • The primary VPS already serves valid HTTPS through Caddy.
  • Cloudflare SSL/TLS mode is Full (strict).
  • sprey-outage.pages.dev serves the approved static outage page.
  • Cloudflare cache rules bypass dynamic WordPress and WooCommerce traffic.
  • The current Workers Free request and CPU limits cover expected traffic.

Use a Workers Route when the Worker runs before the external WordPress application server.

The canonical source is cloudflare/failover-worker.js in Sprey WP Stack.

The Worker:

  • preserves the original request method, body, path, query, headers, and cookies when calling primary;
  • makes exactly one primary attempt;
  • fails over only for network errors, the explicit timeout, and the configured failure-status set;
  • cancels an unused failed-origin response body before fetching fallback;
  • fetches the static outage document with GET or HEAD;
  • returns fallback as HTTP 503 with Cache-Control: no-store, Retry-After: 60, and an X-Sprey-Failover diagnostic header;
  • returns a small plain-text 503 if both primary and fallback fail.

Do not add arbitrary application-level 500 responses without review. Broad failover can hide useful WordPress or WooCommerce diagnostics.

When the production storefront must remain uninterrupted, use a temporary hostname such as failover-test.sprey.win:

  1. Add a proxied DNS record for the test hostname that resolves to the same WordPress VPS.
  2. Configure Caddy to accept the test hostname and verify its certificate.
  3. Add failover-test.sprey.win/* to the Worker’s Domains & Routes.
  4. Verify representative pages, assets, redirects, cookies, login, cart, and checkout.
  5. Confirm healthy responses do not contain X-Sprey-Failover.
  6. During a controlled maintenance window, create a handled failure.
  7. Confirm the Worker returns the static page with HTTP 503 and X-Sprey-Failover: static-outage-page.
  8. Restore primary and confirm the next request returns WordPress without a DNS change.

Remove temporary DNS/Caddy/Worker-route configuration after testing if it is not meant to remain deployed.

The production route has been verified directly on sprey.win for full-origin outages and the controlled 525 TLS-handshake path. A dedicated proxied test hostname using the same Worker was used to verify the controlled 526 invalid-origin-certificate path without disrupting the production storefront.

Verified behavior includes:

  • healthy origin traffic returned HTTP 200 through Caddy with no X-Sprey-Failover header;
  • stopping Caddy produced Cloudflare 521;
  • handling 521 returned the static outage page as HTTP 503 with Cache-Control: no-store, Retry-After: 60, and X-Sprey-Failover: static-outage-page;
  • starting Caddy restored the next request to normal WordPress service without a DNS change;
  • the same failover-and-recovery behavior was verified during a normal VPS reboot and a VPS hard reboot;
  • a controlled TLS-handshake failure was created by stopping Caddy and temporarily binding a non-TLS listener to origin port 443, verifying the configured 525 path end to end;
  • for 526, Cloudflare Full (strict) was used on an isolated proxied test hostname; Caddy was stopped and a temporary TLS listener with a self-signed certificate was bound to origin port 443; a direct origin connection established TLS successfully, while Cloudflare rejected the invalid certificate;
  • the Worker returned the static outage page for that invalid-certificate path as HTTP 503 with Cache-Control: no-store, Retry-After: 60, and X-Sprey-Failover: static-outage-page;
  • removing the temporary listener and restarting Caddy restored the next proxied request to normal WordPress as HTTP 200 without the failover header.

Both TLS-specific paths in the configured failure set are now explicitly verified end to end:

  • 525 — TLS handshake failure at the origin path;
  • 526 — TLS connection succeeds, but Cloudflare Full (strict) rejects the invalid/self-signed origin certificate.

In both cases, the Worker served the Sprey static outage page instead of exposing Cloudflare’s default TLS error page, and normal WordPress service returned on the next request after the origin was restored.

Failover does not make dynamic commerce content safe to cache. Bypass cache for at least:

  • /cart*, /checkout*, and /my-account*;
  • /wp-admin* and /wp-login.php*;
  • WooCommerce Store API, REST API, AJAX, and webhook endpoints as applicable;
  • authenticated requests and requests with WordPress or WooCommerce cart/session cookies.
Terminal window
curl -sS -D - -o /dev/null https://sprey.win/

Normal WordPress responses have no X-Sprey-Failover header. Controlled failover returns HTTP 503 and either static-outage-page or fallback-unavailable in that header.

To roll back, remove or disable only the relevant Workers Route. Do not change DNS.

For operational diagnosis and incident handling, use WP Stack failover operations.