Skip to content

Security

The [security] block groups controls that apply before and around request handling: connection caps, trusted-proxy CIDRs for real-IP resolution, IP allow/deny lists (see IP filtering), rate limits (see Rate limiting), and response security headers (HSTS, CSP, custom).

TLS termination and mTLS are configured under [tls]; see TLS.

max_connections caps concurrent client connections. Static: enforced at the acceptor.

security:
max_connections: 512

When the proxy sits behind a load balancer or ingress, client IPs in X-Forwarded-For can be trusted for real-IP resolution. trusted_proxies is a global CIDR list: it applies to all requests and is not overridable per domain or route.

When set and the peer IP is in this list, the proxy walks X-Forwarded-For right-to-left and uses the first IP not in the trusted list as the real client IP. This real IP drives rate limiting (limit_by = "ip" or "combined") and IP filtering. The same list gates PROXY protocol headers: only trusted peers may supply a PROXY address.

When empty (default), the non-forgeable TCP peer IP is used directly. Dynamic (hot-reloadable).

security:
trusted_proxies:
- "10.0.0.0/8"
- "172.16.0.0/12"

HSTS, CSP, and custom headers attach to responses. The global [security.headers] block applies to all responses. Per-domain and per-route overrides use the same shape inside [domains.security.headers] and [domains.routes.security.headers] respectively: each is a whole-block replace: the most specific scope that defines a security.headers block replaces the parent’s block entirely (no field-level merge).

Example (global):

security:
headers:
custom:
- name: "X-Frame-Options"
value: "DENY"
- name: "X-Content-Type-Options"
value: "nosniff"
hsts:
enabled: true
max_age: 31536000
include_subdomains: false
preload: false
csp:
enabled: true
policy: "default-src 'self'; script-src 'self' 'unsafe-inline'"

There are two header mechanisms with different semantics:

  • [headers] (add/remove): additive cascade. Global → domain → route accumulate; for a given header name, the most specific scope wins. Nothing is replaced wholesale.
  • security.headers: whole-block replace. Like ip_filter and rate_limit, the most specific scope that defines the block replaces the parent entirely. Setting security.headers on a route silently drops any global or domain security.headers for that route.

Use [headers] for free-form request/response plumbing; use security.headers as a security policy you intend to define completely at a given scope.

Per-domain and per-route security overrides

Section titled “Per-domain and per-route security overrides”

ip_filter, rate_limit, and security.headers can be overridden at the domain and route levels. Each override is a whole-block replace: it does not merge with the global config.

domains:
- host: "api.example.com"
security:
ip_filter:
mode: "allowlist"
allowlist: ["10.0.0.0/8"]
rate_limit:
enabled: true
requests_per_second: 50
burst: 100
limit_by: "ip"
routes:
- prefix: "/internal"
backend: "backend-a:9000"
security:
rate_limit:
enabled: false # this route is exempt

The proxy sets trusted X-Forwarded-* values for backends:

  • X-Forwarded-For: appends the client IP (comma-separated), or creates the header.
  • X-Forwarded-Host: set from the HTTP request authority (:authority in HTTP/2, Host header in HTTP/1.1). Client-supplied X-Forwarded-Host values are not trusted and are removed first.
  • X-Forwarded-Port / X-Forwarded-Proto: derived from the peer connection and scheme.

These do not require config. Global [headers] request add/remove lists are separate.