-
Notifications
You must be signed in to change notification settings - Fork 0
Behind a Proxy
If anything sits between clients and the application — a load balancer, a reverse proxy, an ingress controller, a CDN — you must configure this. It is the single most consequential setting in the extension.
ratelimit.WithTrustedProxies(ratelimit.PrivateNetworks()...)
// or
ratelimit.WithTrustedProxies(ratelimit.MustParsePrefixes("10.0.0.0/8")...)Without it, RemoteAddr is the proxy's address — identical for every
client — so the per-client limiter becomes a single global one. Every client
shares one bucket, and one busy caller rate-limits everyone.
The failure is silent and looks like working code: the limiter fires, just for the wrong reason.
X-Forwarded-For is a client-appendable list. A proxy appends the address
it saw to whatever the request already carried:
X-Forwarded-For: <whatever the client sent>, <client as seen by proxy 1>, <proxy 1 as seen by proxy 2>
The values on the left are the ones an attacker controls. Sending
X-Forwarded-For: 1.2.3.4
produces
X-Forwarded-For: 1.2.3.4, <real address>
after the proxy appends. So taking the leftmost entry — the obvious reading, and the one most naive implementations use — takes exactly the attacker's value. A client that rotates that header defeats the limiter entirely, and can also exhaust the bucket store by inventing addresses.
-
If the peer itself is not a trusted proxy, ignore the header completely
and use
RemoteAddr. An untrusted peer's forwarding claims are worth nothing. - Otherwise walk the list from the right, skipping entries that are themselves trusted proxies, and take the first untrusted address. That is the closest address a trusted hop actually observed.
- If every entry is a trusted proxy, fall back to
RemoteAddr.
An empty TrustedProxies trusts nothing — the header is ignored and
RemoteAddr is used. That is the safe default, and it is also wrong behind a
proxy, which is why the warning exists on the config field.
// Every RFC 1918 / RFC 4193 private range, plus loopback.
ratelimit.WithTrustedProxies(ratelimit.PrivateNetworks()...)A convenience for the common deployment where the ingress is on the same private network. It is a starting point, not a recommendation: trusting the whole private range means trusting anything that can reach the application from inside it. In a shared cluster, that is a lot of things.
Narrower is better:
ratelimit.WithTrustedProxies(ratelimit.MustParsePrefixes(
"10.42.0.0/16", // the ingress subnet, and nothing else
)...)MustParsePrefixes panics on a malformed CIDR, on purpose. Configuration
written as a literal makes a bad prefix a programming error, and the alternative
— silently producing an empty trust list — would look like working code behind a
proxy while actually rate limiting every client as one.
For prefixes from configuration, parse them yourself with netip.ParsePrefix
and fail startup on the error.
Kubernetes with an ingress controller. Trust the ingress controller's pod CIDR, not the whole cluster network.
Cloud load balancer. Trust the load balancer's documented address ranges. Most providers publish them; several publish them as a downloadable list that changes, which is worth refreshing at deploy time rather than hardcoding.
CDN in front of a load balancer. Two hops, both trusted, and the walk skips both to find the real client. Trust both ranges.
Nothing in front. Leave TrustedProxies empty. RemoteAddr is the client.
The cheapest check is to make the same request from two different addresses and
confirm X-RateLimit-Remaining decrements independently. If both share a
counter, the key function is returning the proxy's address.
curl -si https://api.example.com/health | grep -i x-ratelimitThen try to lie:
curl -si -H 'X-Forwarded-For: 1.2.3.4' https://api.example.com/health | grep -i x-ratelimitIf your remaining count resets, the header is being trusted from the left and the limiter is bypassable.
rextension-ratelimit — global, per-router and per-endpoint rate limiting for Rex · MIT · © 2026 Kryovyx
Ecosystem