nginx

There are two integration patterns for nginx:

Pattern When to use
auth_request Fairvisor Edge runs as a separate service; nginx calls it via HTTP
Direct Lua Fairvisor Lua modules are loaded directly into the nginx/OpenResty process

Fairvisor Edge runs as an independent container or service. nginx uses the auth_request directive to call the /v1/decision endpoint before proxying each request.

upstream fairvisor_edge {
  server fairvisor-edge.internal:8080;
  keepalive 32;
}

server {
  listen 80;

  # Internal auth location — not accessible from outside
  location = /_fairvisor_decision {
    internal;
    proxy_pass         http://fairvisor_edge/v1/decision;
    proxy_pass_request_body off;
    proxy_set_header   Content-Length "";
    proxy_set_header   X-Original-URI    $request_uri;
    proxy_set_header   X-Original-Method $request_method;
    proxy_set_header   Authorization     $http_authorization;
    proxy_set_header   X-Forwarded-For   $proxy_add_x_forwarded_for;

    # Don't cache auth responses
    proxy_no_cache     1;
    proxy_cache_bypass 1;
  }

  location /api/ {
    auth_request /_fairvisor_decision;

    # Capture decision headers so you can forward them to the client or log them
    auth_request_set $fv_reason   $upstream_http_x_fairvisor_reason;
    auth_request_set $retry_after $upstream_http_retry_after;
    auth_request_set $ratelimit   $upstream_http_ratelimit;
    auth_request_set $ratelimit_reset $upstream_http_ratelimit_reset;

    # Forward 429 rejection with correct headers
    error_page 401 403 = @fairvisor_reject;
    error_page 429     = @fairvisor_reject;

    proxy_pass http://your_backend;
  }

  location @fairvisor_reject {
    internal;
    add_header Retry-After         $retry_after always;
    add_header X-Fairvisor-Reason  $fv_reason   always;
    add_header RateLimit           $ratelimit always;
    add_header RateLimit-Reset     $ratelimit_reset always;
    return 429;
  }
}

Why keepalive on the upstream?

The Fairvisor decision call happens on every request. keepalive 32 keeps a pool of persistent connections to the edge container, avoiding TCP handshake overhead on each call. Keep the pool size ≥ expected concurrent nginx workers × 2.

Timeout recommendations

location = /_fairvisor_decision {
  proxy_connect_timeout 50ms;
  proxy_read_timeout    200ms;
  proxy_send_timeout    200ms;
  ...
}

If the edge is unreachable within the timeout, nginx returns 502. Configure an error_page 502 handler to decide whether to fail-open or fail-closed:

# Fail-open: allow if Fairvisor is unreachable
error_page 502 = @fairvisor_unavailable;
location @fairvisor_unavailable {
  return 200;   # auth_request will treat 200 as "allowed"
}

Pattern 2 — Direct Lua embedding

If you run OpenResty, you can load Fairvisor modules directly into your nginx process — no separate service needed.

See Standalone for the nginx.conf structure. The key directives are:

http {
  lua_package_path "/opt/fairvisor/src/?.lua;;";
  lua_shared_dict  fairvisor_counters 128m;

  init_worker_by_lua_block { /* initialise engine */ }

  server {
    location /api/ {
      access_by_lua_block {
        local decision_api = require("fairvisor.decision_api")
        decision_api.access_handler()
      }

      # For response token reconciliation (LLM mode):
      header_filter_by_lua_block {
        local decision_api = require("fairvisor.decision_api")
        decision_api.header_filter_handler()
      }

      # For mid-stream SSE enforcement:
      body_filter_by_lua_block {
        local decision_api = require("fairvisor.decision_api")
        decision_api.body_filter_handler()
      }

      proxy_pass http://your_backend;
    }
  }
}

Lua phase summary

nginx phase Directive Purpose
Access access_by_lua_block Evaluate policy; reject or allow
Header filter header_filter_by_lua_block Apply rate-limit response headers; initiate streaming context
Body filter body_filter_by_lua_block SSE chunk processing; token reconciliation

Shared dict sizing

Deployment size Recommended lua_shared_dict size
< 10 000 concurrent keys 64m
10 000 – 100 000 keys 128m (default)
100 000 – 500 000 keys 256m
> 500 000 keys 512m or larger

Passing additional context

Fairvisor extracts descriptor keys from the request automatically. For context not carried in standard headers (e.g., internal tenant ID resolved from a database), set a custom header before the auth_request:

set $tenant_id ""; # populated by some upstream logic
auth_request_set $tenant_id $upstream_http_x_tenant_id;
proxy_set_header X-Tenant-ID $tenant_id;

Then reference header:x-tenant-id in your policy’s limit_keys.