Add an allowlisted multi-host entrypoint that delegates each hostname to the existing origin selector with its own required health status. Hostnames share the same origin pair and readiness secret, so one Worker can serve several independent Routes without duplicating the deployment. Validation: 21 Worker tests and independent review passed. Disclosure: I'm an AI assistant helping the author; I'm not the author.
Readiness-gated origin routing on Workers Free
This local ES-module Worker asset selects between two origins for one public HTTPS hostname. It implements the ingress part of the composition replication RFC. It is not deployed and does not establish production high availability. Use the multi-host entrypoint below when hostnames share origins and a readiness secret. Verify origin routes and readiness independently for every hostname. Activate only after the declared backend failure behavior and authenticated application readiness are verified. The Worker does not promote databases or activate a standby.
The module has no runtime dependencies, paid bindings, KV, R2, storage or scheduled jobs. This directory does not enable billing, enroll an account, upload a Worker or change DNS/routes.
Bindings
Set these string bindings through the operator's chosen Cloudflare deployment process. Keep the readiness token in a Worker secret, never a committed variable, URL or log.
| Binding | Value |
|---|---|
APEX_PUBLIC_HOST |
Existing public SSO hostname, for example sso.example.com |
APEX_ZONE |
The actual Cloudflare DNS zone, for example example.com |
APEX_ORIGIN_PRIMARY |
Dedicated DNS-only primary origin hostname in that zone |
APEX_ORIGIN_SECONDARY |
Dedicated DNS-only secondary origin hostname in that zone |
APEX_READY_PATH |
Dedicated absolute readiness path, for example /.well-known/apex-ready |
APEX_READY_TOKEN |
High-entropy secret shared only by this Worker and both readiness adapters |
APEX_READY_STATUS |
Optional exact status string: 204 (default) or 200 for a dedicated application health route |
Hostnames accept ordinary ASCII DNS labels, without a scheme, port, trailing dot, wildcard or IP literal. Case is normalized. Public and origin hostnames must be distinct and within the declared zone. Readiness paths accept ASCII letters, digits, ., _, ~, - and /; queries, fragments, escapes, repeated slashes and dot traversal segments are rejected. The secret must be nonempty visible ASCII without whitespace. Invalid/missing bindings produce a generic no-store 503 before any fetch. Syntax validation cannot establish actual DNS ownership or prevent an operator from naming a separately delegated zone; deployment must verify both origins are in the actual same Cloudflare zone.
Routing and readiness contract
Create two dedicated DNS-only origin records pointing to the participating servers; do not point either at the public hostname or a Worker. Retain the existing proxied public SSO DNS record. Use a Worker Route such as sso.example.com/* on that existing hostname, not a Worker Custom Domain. Both origins must serve HTTPS with a valid certificate and virtual host for the public name. Configure strict origin TLS verification. Disable any Workers Cache in front of this Worker and any cache policy that would bypass its invocation. Cloudflare Routes
Require global_fetch_private_origin in the Worker compatibility flags and prohibit global_fetch_strictly_public. The strictly-public flag sends same-zone global fetches through Cloudflare's public routing layer and can re-enter this Worker; a readiness probe would then hit its public-path rejection instead of the origin. Private-origin behavior routes same-zone subrequests to the origin, bypassing mapped Workers and Cloudflare security settings, so the origin adapter must still enforce its own authentication. Global fetch compatibility
Set compatibility_date to 2024-11-11 or later and do not set cache_option_disabled. cache_option_enabled became the default on that date; the example also enables it explicitly. Without this behavior, constructing requests with cache: "no-store" throws instead of dispatching. Request cache compatibility
The required non-secret upload metadata fields are:
{
"main_module": "worker.mjs",
"compatibility_date": "2026-10-10",
"compatibility_flags": [
"global_fetch_private_origin",
"cache_option_enabled"
]
}
Use this as the multipart API metadata portion, with the ES-module file part named worker.mjs. Bindings and the readiness secret are configured separately through the operator-owned enrollment process; they are deliberately absent from this example, as are paid bindings. This metadata is not a complete enrollment or route activation command. Verify the resulting effective settings before activation, including after later uploads. Multipart upload metadata
Every accepted HTTPS request first probes the primary with a GET at https://APEX_PUBLIC_HOST/APEX_READY_PATH. The request carries only X-APEX-Ready-Token, rather than client cookies or authorization. cf.resolveOverride selects the DNS-only origin while the URL remains the public hostname. Only the configured exact status is ready: 204 by default, or 200 when explicitly selected with APEX_READY_STATUS. Redirects are manual and never accepted. Do not use 200 with a generic login page or native liveness endpoint; a protected application health route must verify both active SSO dependencies and its own backend. Each probe has a two-second deadline. Unread response bodies are canceled, including responses arriving after timeout. Failed primary readiness leads to a secondary probe; neither ready produces a no-store 503. There is no readiness cache or background health state.
The dedicated origin adapter must authenticate the token, reject its absence/incorrect value and prove that this application instance can safely serve traffic: required dependencies, compatible configuration, current write authority for the declared topology, session state and any application-specific prerequisites. Authelia /api/health, an nginx 204, a TCP check or a running container alone is insufficient. Protect adapter aliases and normalize paths consistently at the origin. The Worker rejects public requests to the configured readiness path (also with a query or percent-encoded spelling), and removes every client-supplied readiness header before application dispatch. An unpromoted standby must remain unready. This asset does not implement or enroll the backend adapter. Preserve existing path-specific routes when attaching the Worker; two ingress paths do not replicate a shared application backend.
Cloudflare documents resolveOverride as DNS substitution while retaining the URL's Host, subject to both names belonging to the same zone. The asset retains the HTTPS public URL for routing; actual origin TLS SNI/certificate selection and routing must be demonstrated natively before activation. No local Node test proves Cloudflare DNS/TLS behavior. Request API
Application request behavior
After selection, the original method, URL/path/query, body, cookies and authorization are forwarded once. The body is streamed; no request-body buffering or replay copy is made. The client readiness header is removed. Application redirects remain manual, and response status, body, Location and separate Set-Cookie headers are preserved.
No application request is replayed after an origin failure, including GET callbacks. A fetch exception returns a generic no-store 502; an origin HTTP error is returned with its original status/body. There is no second probe or alternative-origin dispatch after this point. Readiness can become stale between probe and dispatch; failover applies to subsequent requests. Application requests have no additional Worker timeout so streaming is not cut off; platform/client deadlines still apply.
All probe and application fetches use cache: "no-store", cacheEverything: false and negative cacheTtlByStatus. Ordinary responses override browser/CDN cache headers to no-store. There is no Cache API use. Verify native cache bypass with cookies, authorization, redirects and authentication error responses before enrollment. Fetch caching
A platform status-101 response is returned unchanged to preserve Cloudflare's WebSocket upgrade object. The Worker does not terminate or inspect WebSocket messages, migrate existing connections, reconnect clients or claim protocol HA. Ordinary response bodies remain streams. Native WebSocket behavior is an optional deployment test if the application needs it; the local test only checks unchanged forwarding of the platform response. XHTTP and XHTTP-over-Cloudflare are excluded from this SSO HA scope. Workers Response API
Other hostnames, non-default ports and non-HTTPS requests receive no-store 421. The public route must already have the intended HTTPS policy. Client errors and unavailable responses contain no secret diagnostics; the module emits no logs.
Free-tier deployment gates
Workers Free currently provides 100,000 incoming requests per day per account, reset at 03:00 MSK (midnight UTC), and 10 ms CPU per HTTP request. Quota is shared with the account's other Workers. This asset performs two fetch subrequests normally (one probe plus application dispatch), or three when primary readiness fails. Waiting for network I/O does not count toward CPU time, but actual Worker CPU consumption must be measured; Node wall time is not that measurement. Cloudflare Workers limits
Keep the account on Workers Free. Do not enable a paid plan, paid load balancer or paid bindings for this asset. An operator must inspect existing account usage and peak traffic to confirm the free quota is sufficient. This is a no-added-cost deployment candidate, not unlimited availability.
Set the Worker Route to fail closed. On Free quota exhaustion, Cloudflare documents a 1027 error page for this mode; fail open bypasses the Worker and is unacceptable here. The module cannot enforce the platform route setting or prevent quota exhaustion. Record and verify that setting before activation.
Operator-owned enrollment gates:
- Validate the declared backend failure behavior, adapter authentication/readiness, dependency loss and write-authority withdrawal independently of this Worker.
- Confirm account Free plan/quota, same-zone DNS-only origin records, public proxied Route, strict TLS, native SNI/Host at each origin, required private-origin/cache compatibility flags and date, and no Worker/route recursion or pre-Worker caching.
- Test native primary/secondary readiness, both-unready failure, redirects, cookies, cache bypass, POST and GET callback non-replay, streaming and any required WebSockets. Measure Worker CPU and account request headroom.
- Verify fail-closed quota handling and exercise location loss, network partitions, stale/rejoining origins and recovery. Record observed recovery time and session behavior before making an HA claim.
- Activate only after those gates pass with the intended backend failure behavior. Maintain a documented operator rollback that does not silently route to an unsafe origin.
Local verification
From the shared APEX repository root, with Node available:
node --test assets/composition-failover/worker.test.mjs
Tested with Node v24.11.1 using its built-in test runner and Web APIs. Tests call the exported handler and replace only the external fetch boundary, with fake timers for probe deadlines. They do not call Cloudflare or use credentials. No npm install, new dependency or change to the default Python test workflow is required. Native Cloudflare deployment is deliberately outside this local test's proof.
Several hostnames in one Worker
Use multi-host.mjs as the main module and upload worker.mjs alongside it when
several hostnames share the same origins and readiness secret. Attach one Route
per hostname to that single Worker. Set APEX_PUBLIC_HOSTS to a JSON object of
allowed hostnames and exact health status strings, for example
{"sso.example.com":"204","app.example.com":"200"}. It replaces the
APEX_PUBLIC_HOST and APEX_READY_STATUS bindings; all other bindings and
deployment gates above still apply. Unknown hostnames are rejected before any
origin request. Each allowed hostname uses the existing single-host handler,
including its authentication, caching, streaming and no-replay behavior.
Verify both entrypoints with node --test assets/composition-failover/*.test.mjs.