Cloudflare

Cloudflare integration is typically implemented with a Worker that calls Fairvisor before forwarding traffic to your origin.

Integration patterns

Pattern When to use
Worker decision check (recommended) You need strict control over allow/reject handling and header propagation
Fairvisor as origin proxy You want Cloudflare to route directly to Fairvisor in reverse_proxy mode

Flow:

  1. Worker receives request
  2. Worker calls POST /v1/decision
  3. 200: forward to origin
  4. 429: return reject with Retry-After, X-Fairvisor-Reason, and RateLimit*
export default {
  async fetch(request, env) {
    const url = new URL(request.url);
    const originalUri = url.pathname + url.search;

    const decisionHeaders = new Headers();
    decisionHeaders.set("X-Original-Method", request.method);
    decisionHeaders.set("X-Original-URI", originalUri);

    const auth = request.headers.get("authorization");
    if (auth) decisionHeaders.set("Authorization", auth);

    const clientIp = request.headers.get("cf-connecting-ip");
    if (clientIp) decisionHeaders.set("X-Forwarded-For", clientIp);

    const decisionRes = await fetch(`${env.FAIRVISOR_EDGE_URL}/v1/decision`, {
      method: "POST",
      headers: decisionHeaders,
    });

    if (decisionRes.status === 429) {
      const headers = new Headers();
      for (const k of [
        "retry-after",
        "x-fairvisor-reason",
        "ratelimit",
        "ratelimit-limit",
        "ratelimit-remaining",
        "ratelimit-reset",
      ]) {
        const v = decisionRes.headers.get(k);
        if (v) headers.set(k, v);
      }
      return new Response("Too Many Requests", { status: 429, headers });
    }

    if (!decisionRes.ok) {
      // Fail policy choice:
      // - fail-closed: return 503
      // - fail-open: continue to origin
      return new Response("Decision service unavailable", { status: 503 });
    }

    const upstream = await fetch(new Request(request, { cf: { cacheEverything: false } }));
    return upstream;
  }
};

Required env vars

FAIRVISOR_EDGE_URL=https://fairvisor-edge.internal.example.com

Keep Fairvisor reachable only from trusted network paths (for example private origin network or tunnel), not public internet.

Pattern 2 — Fairvisor as reverse-proxy origin

Set Cloudflare origin to Fairvisor Edge, run edge in reverse_proxy mode:

FAIRVISOR_MODE=reverse_proxy
FAIRVISOR_BACKEND_URL=http://your-origin-service:3000
FAIRVISOR_CONFIG_FILE=/etc/fairvisor/policy.json

In this setup, every request to origin is enforced inline by Fairvisor.

Failure policy

Cloudflare pattern should explicitly choose fail-open or fail-closed:

  • Fail-closed: safer for sensitive API routes
  • Fail-open: better availability for low-risk/public routes

Recommended split is documented in Gateway Failure Policy.

Timeout and retry guidance

  • Keep decision timeout low (typically 200-500 ms budget)
  • Avoid repeated retries on the hot path
  • Emit logs/metrics for every fallback decision

Header mapping checklist

When calling Fairvisor, include:

  • X-Original-Method
  • X-Original-URI
  • Authorization (if used for JWT descriptors)
  • X-Forwarded-For (from cf-connecting-ip)

Verification

# readiness
curl -i http://fairvisor-edge.internal.example.com:8080/readyz

# direct decision probe
curl -i -X POST http://fairvisor-edge.internal.example.com:8080/v1/decision \
  -H "X-Original-Method: GET" \
  -H "X-Original-URI: /v1/chat/completions" \
  -H "X-Forwarded-For: 198.51.100.42"

Look for 429 rejects with X-Fairvisor-Reason and Retry-After.

Troubleshooting

Symptom Likely cause Fix
Worker always returns 503 Fairvisor unreachable or timeout too strict Verify network path and adjust timeout budget
Fairvisor sees wrong route Missing or malformed X-Original-URI Forward full path + query
Per-user limits collapse into one bucket Missing identity headers/JWT Forward Authorization and required descriptor headers

See Decision API for the full contract.