Rate limiting behind a proxy
Break 1 of eleven in the 0.3.0 → 1.0.0 upgrade.
Before you start
Read Upgrade to 1.0.0 first: it carries the checklist, the backup step and the preflight scan.
This is the highest-impact change on the page. It applies only when rate_limit.enabled = true, still false by default. Where it applies, it changes who shares a bucket.
What changed. The rate limiter’s client identity used to be read straight from X-Forwarded-For (first element) or X-Real-IP. It fell back to the literal string "unknown" when neither was present. Any client could mint a fresh bucket by sending a made-up header. The identity is now the TCP peer address, and forwarded headers are honoured only when the peer address falls inside the new rate_limit.trusted_proxies list. That list is empty by default, which means “trust nothing”. When the peer is trusted, the client is resolved from the right end of X-Forwarded-For, which is the hop your proxy appended. Hops that are themselves trusted proxies are skipped. The leftmost elements are whatever the client sent and are never used as the identity.
How you’ll notice. If Orion sits behind a proxy, load balancer, ingress controller or service mesh, the TCP peer is always that hop. Every client collapses into a single bucket, and legitimate traffic starts getting 429s far below the configured rate. Watch orion_rate_limit_rejections_total climb while real request volume is unchanged.
What to do. List the addresses your proxies connect from, as CIDR blocks or bare IPs (IPv4 and IPv6 both accepted):
[rate_limit]
enabled = true
trusted_proxies = ["10.0.0.0/8", "192.168.1.1", "fd00::/8"]
Or by environment variable (comma-separated; it replaces the list, it does not append):
ORION_RATE_LIMIT__TRUSTED_PROXIES="10.0.0.0/8,fd00::/8"
On Kubernetes this is your pod or node CIDR; behind a cloud LB it is the LB’s subnet, not the client’s. Orion canonicalises IPv4-mapped IPv6 peers, so a server bound on [::] still matches an IPv4 CIDR.
Two things to know:
- A malformed entry is a hard startup failure, even when
rate_limit.enabled = false. The message israte_limit.trusted_proxies: invalid entry '<x>': expected an IP address or CIDR block (e.g. "10.0.0.0/8"). Runorion-server validate-configbefore you deploy. - Per-channel
rate_limit.key_logicis affected too. Any channel whose key expression references{"var": "client_ip"}now receives the peer address under the same rules.
It is no longer gated on
rate_limit.enabled. A channel’s ownrate_limitblock is enforced on every ingress by the channel guards, keyed on the same trusted-proxy-gated client identity, whether or not the platform limiter is running, and the audit trail’sdetails.client_ipand the failed-auth backoff read it too. If you deliberately left[rate_limit] enabled = falseand rely on per-channel limits, you still needtrusted_proxiesset; otherwise every client behind the proxy keys on the proxy’s own address and the whole fleet shares one bucket.
Not changed: sticky rollout bucketing still reads forwarded headers directly and does not consult
trusted_proxies. See Sticky rollouts.
Related
- Upgrade to 1.0.0: the checklist, and every other break.
- Upgrades: the version-independent procedure.
orion-server preflight: the scan that finds the stored ones.- Releases: what changed in each version.
Last verified 14 September 2026