Advanced Usage¶
Dry run mode¶
Evaluate limits without enforcing them. Useful for observing impact before enabling rate limiting in production.
When a request would be blocked, fastlimit logs a warning instead of returning 429:
Exempt IPs¶
Skip rate limiting for specific IPs — useful for internal health checks or trusted callers:
Request cost¶
Some endpoints are more expensive than others. cost lets you consume multiple slots per call:
# export costs 10 slots — a user with 100/hour gets 10 exports per hour
@router.post("/export", dependencies=[rate_limit("100/hour", cost=10)])
async def export():
...
Key prefix¶
If multiple apps share one Redis instance, set a unique prefix per app:
limiter = FastLimit(
redis_url="redis://localhost:6379",
key_prefix="myapp_v2",
)
# keys look like: myapp_v2:login:ip:1.2.3.4
Trusted proxies¶
When your app runs behind a reverse proxy (nginx, Cloudflare, a load balancer), the real client IP is in X-Forwarded-For, not request.client.host. trusted_proxies tells fastlimit how many proxy hops to trust.
X-Forwarded-For: 1.2.3.4, 172.16.0.1, 10.0.0.1
With trusted_proxies=2 (Cloudflare + nginx), fastlimit picks 1.2.3.4 — the real client IP.
| Value | Effect |
|---|---|
0 | Use request.client.host directly — no header parsing |
1 | Trust one proxy (default — works for most setups) |
2 | Trust two proxies (e.g. Cloudflare + nginx) |
Security
Setting trusted_proxies too high lets clients spoof their IP by adding fake entries to X-Forwarded-For. Set it to exactly the number of proxies between the internet and your app.
Router-level limits¶
Apply a limit to every route under a router:
from fastapi import APIRouter
from fastlimit import rate_limit
# every route in this router gets the limit
router = APIRouter(dependencies=[rate_limit("200/min")])
App-level fallback¶
Applying both router and route limits¶
When a route has a router-level limit and its own limit, both are checked independently. The first one to block wins.
router = APIRouter(dependencies=[rate_limit("200/min", name="router_global")])
@router.post("/upload", dependencies=[rate_limit(ip="5/min", user="50/min")])
async def upload():
# checked: 200/min global, then 5/min IP + 50/min user
...
from_env() (coming soon)¶
Load limiter config from environment variables: