Repository navigation
How TPS determines the client IP
TPS cares about the client's IP address in three places: token IP tracking,
bypass keys, and the analytics event log. Since TPS always sits behind a front
proxy (Caddy, nginx, a CDN, ...), the address on the connection itself is
usually the proxy's, and the real client IP arrives in the X-Forwarded-For
header.
That header is only trustworthy when the peer that sent it is your own
infrastructure — anyone else can write whatever they want into it. So TPS
keeps a list of trusted peers, and it's the equivalent of Caddy's
trusted_proxies setting:
- A request arriving from a trusted address gets its client IP from
X-Forwarded-For, walking past any other trusted hops in the chain. - A request arriving from anywhere else is its own client: the connection's address is used and forwarded headers are ignored.
As of v3.1.0, the list is configurable via the TRUSTED_PROXIES environment
variable — a comma-separated list of IP addresses and/or CIDR ranges. See
env-example
for the full documentation. Before v3.1.0 the default below was hardcoded.
Default (unset): loopback plus the private ranges — 127.0.0.0/8,
10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, ::1/128, fc00::/7. This
covers simple deployments where the front proxy reaches TPS over localhost or a
Docker/private network, with no configuration needed.
Narrowing: the default trusts every host on your private network, which
means any of them could spoof client IPs to TPS — including dodging a bypass
key's CIDR restriction. Setting TRUSTED_PROXIES to your front proxy's
actual address (e.g. TRUSTED_PROXIES="10.1.2.3") closes that off. This is
the same hardening Caddy's docs recommend for its own trusted_proxies.
Widening: if a hop reaches TPS from outside the private ranges, its
forwarded headers won't be honored and TPS will see that hop's address as the
client. The common case is a CDN in front of your front proxy: Caddy appends
the CDN edge's (public) address to X-Forwarded-For, so TPS stops there and
treats the edge IP as the client. List the CDN's egress ranges alongside your
front proxy's address to walk past it. The special entry private_ranges
expands to everything the default trusts (just like Caddy's own
private_ranges), so the easy version is
TRUSTED_PROXIES="private_ranges,<CDN egress ranges>" rather than spelling
the private networks back out.
Trusting no one: the special value TRUSTED_PROXIES=none ignores
forwarded headers entirely; every client is its connection's address. Unlike
private_ranges, none must be the entire value — none next to other
entries is a contradiction, and TPS refuses to start rather than guess.
TPS still works — a wrong client IP degrades things rather than breaking them, which is worth understanding before you panic about misconfiguration:
- With the request budget enabled (the default), the IP is a surcharge, not a hard binding, and the User-Agent binding still applies. If every client appears to come from one proxy IP, bots just stop paying the IP-switch surcharge.
- Bypass keys' CIDR restrictions check the derived client IP, so they're only as good as this setting.
- The event log and
tps key listusage numbers record the derived IP, so analytics get muddy.