Cloudflare Worker that acts as a strict pull-through proxy to an upstream GraphQL endpoint in your k8s cluster.
- Accepts GraphQL requests on
/graphql(GETandPOST). - Accepts Directus asset passthrough requests on
/directus/assets/*(GETandHEAD). - Proxies requests to
UPSTREAM_GRAPHQL_URLwithout local schema composition/resolvers. - Supports GraphQL subscription upgrades over WebSocket by forwarding required upgrade and
Sec-WebSocket-*headers to upstream. - Preserves upstream schema exposure (including introspection behavior) because the worker does not host schema locally.
- Enforces CORS allow-list via
CORS_DOMAINS(exact origins and wildcard subdomain patterns likehttps://*.suncoast.systemsor*.suncoast.systems); additionally allows localhost loopback origins only when the request hostname has adevsubdomain label. - Uses bearer-token passthrough only:
- HTTP GraphQL requests (
GET/POST) requireAuthorization: Bearer ...; missing/invalid bearer headers return401 Not authorized. - WebSocket upgrade handshakes are allowed without
Authorizationso browser clients can connect; upstream GraphQL must enforce token auth duringconnection_init. - Valid
Authorization: Bearer ...headers are forwarded to upstream unchanged.
- HTTP GraphQL requests (
- Forwards client IP metadata to upstream using Cloudflare-trusted source only:
- Reads only
CF-Connecting-IPfrom the Cloudflare edge request. - Ignores inbound
X-Forwarded-For,Forwarded,X-Real-IP, andTrue-Client-IPvalues from clients. - Sends normalized IP via
X-Client-IP,X-Real-IP,CF-Connecting-IP,True-Client-IP,X-Forwarded-For, andForwarded.
- Reads only
- Provides edge caching for unauthenticated query operations.
- Cache is enabled by default and controlled via env vars.
- Only query operations are cache candidates.
- Requests with
AuthorizationorCookieare never cached. - Mutations/subscriptions are never cached.
- POST query responses are cached with a SHA-256 body-based key.
- Responses with
Set-Cookie,Cache-Control: private, orCache-Control: no-storeare not cached. - By default, GraphQL responses containing
errorsare not cached (CACHE_INCLUDE_GRAPHQL_ERRORS=false). - WebSocket upgrade requests are always bypassed from cache and returned as raw upgraded responses.
UPSTREAM_GRAPHQL_URL: full upstream GraphQL URL.- By default this is generated by Terraform from a managed proxied upstream DNS record.
- If
MANAGE_UPSTREAM_DNS_RECORD=false, supplyUPSTREAM_GRAPHQL_URLdirectly.
UPSTREAM_TIMEOUT_MS(optional, default60000): timeout for upstream GraphQL request before returning504(also used for WebSocket handshake timeout).UPSTREAM_DIRECTUS_ASSET_BASE_URL(optional): base URL used for/directus/assets/*passthrough. If omitted andMANAGE_UPSTREAM_DNS_RECORD=true, defaults tohttps://<UPSTREAM_DNS_NAME>.<domain>.UPSTREAM_DIRECTUS_ASSET_PATH(default/assets): upstream path prefix appended toUPSTREAM_DIRECTUS_ASSET_BASE_URL.DIRECTUS_ASSET_PROXY_PREFIX(default/directus/assets): public path prefix exposed by this worker for asset passthrough.CORS_DOMAINS(required): comma-separated allowed origins. Supports exact origins and wildcard subdomain entries likehttps://*.suncoast.systemsor*.suncoast.systems.- Terraform sets this from
ALLOWED_HOSTSand automatically appends*.${domain}.
- Terraform sets this from
CACHE_ENABLED(defaulttrue).CACHE_TTL_SECONDS(default60).CACHE_STALE_WHILE_REVALIDATE_SECONDS(default30).CACHE_INCLUDE_GRAPHQL_ERRORS(defaultfalse).
MANAGE_UPSTREAM_DNS_RECORD(defaulttrue): create and manage a proxied Cloudflare DNS record for upstream GraphQL.UPSTREAM_SHARED_OWNER_ENVIRONMENT(defaultdev): only this environment manages shared upstream DNS+tunnel resources to avoid duplicate create errors acrossdev/prod.UPSTREAM_DNS_NAME(defaultgraphql-origin): relative DNS label in your zone.UPSTREAM_TUNNEL_ID(default69517d80-2079-43f1-97f7-cfd716298c50): when set, managed DNS is a proxiedCNAMEto<UPSTREAM_TUNNEL_ID>.cfargotunnel.com.UPSTREAM_ORIGIN_IP(default64.251.17.245): fallback origin IPv4 whenUPSTREAM_TUNNEL_IDis empty (managed DNS is proxiedA).UPSTREAM_GRAPHQL_PATH(default/v1/graphql): path appended to managed hostname.UPSTREAM_GRAPHQL_URL: only used whenMANAGE_UPSTREAM_DNS_RECORD=false.
MANAGE_UPSTREAM_TUNNEL_CONFIG(defaulttrue): manage tunnel ingress config forUPSTREAM_DNS_NAME.UPSTREAM_TUNNEL_SERVICE(defaulthttp://hasura.graphql.svc.cluster.local:8080): origin service URL tunnel should route to (for examplehttp://apisix.gateway.svc.cluster.local:80).- Uses
cloudflare_zero_trust_tunnel_cloudflared_configand defines:- ingress rule for
${UPSTREAM_DNS_NAME}.${domain}->UPSTREAM_TUNNEL_SERVICE - catch-all
http_status:404rule
- ingress rule for
- Important: this resource manages the tunnel's ingress config for that tunnel id. Use a dedicated tunnel if other hostname rules are managed elsewhere.
- Shared ownership: only the workspace where
environment == UPSTREAM_SHARED_OWNER_ENVIRONMENTcreates/updates this shared tunnel config.
GET /healthzreturns proxy health and version.GET /upstream-probeperforms a direct probe toUPSTREAM_GRAPHQL_URLand returns reachability/latency/status diagnostics.