Your rate limiter is broken behind a tunnel - the X-Forwarded-For problem
You put your app behind a tunnel (or any reverse proxy) to test webhooks. Everything works. Then you notice something odd in your logs: every single request comes from the same IP address. Congratulations, you've met the X-Forwarded-For problem. What actually happens When a request flows through a tunnel, the TCP connection to your app comes from the relay, not the real client. So request.remote_addr - the value your framework uses for rate limiting, IP logging, geo-blocking, brute-force detection - is the relay's address. For every request. From every user. The consequences are quiet and nasty: Your rate limiter now rate-limits the relay, not the client. One aggressive user trips the limit and everyone gets blocked. Or worse, the limit is per-IP and effectively unlimited, because each relay node looks like one "user." * Your access logs are fiction. Security review of an incident? Every entry says the same address. * IP allowlists silently break. "Only allow my office IP" now allows nothing, or everything, depending on how it's wired. The fix (and its trap) The proxy already tells you the real client IP - in the X-Forwarded-For header. Every framework has a setting to trust it. Flask: ProxyFix . Express: app.set('trust proxy', ...) . Rails, Django, Laravel: equivalents exist. Here's the trap: trust that header blindly and anyone can spoof it. A client can send X-Forwarded-For: 1.2.3.4 directly, and if your app believes headers from anyone, your rate limiter is bypassed with a curl flag. The correct setup has two halves: 1. Trust X-Forwarded-For only when the immediate connection comes from a proxy you control (your tunnel relay, your load balancer). 2. Strip or ignore the header on direct connections. Most frameworks express this as "trusted proxies" - a list of proxy IPs whose forwarded headers you believe. Set it. It's five minutes of config that determines whether your security features are real or decorative. Why this matters more in the tunnel era Tunnels used to be a demo-day tool. Now they're how teams test webhooks, preview for clients, and let AI agents reach local environments - often for days at a time, fronting apps with real auth and real rate limits. Every one of those apps is behind a proxy, whether the developer thought about it or not. If you're picking a tunnel, this is one of the details that separates serious ones: does the relay set and document its forwarded headers? We built 21tunnel to pass real client IPs through correctly (and gate tunnels at the edge when you don't want the internet reaching your app at all) - but whatever you use, spend the five minutes on trusted proxies. Your rate limiter will thank you. Top comments (0)
Comments
No comments yet. Start the discussion.