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 |
Pattern 1 — auth_request (recommended)
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.