Skip to main content

The decorator

@rate_limit keeps its counters in the process. Under gunicorn --workers 4 a limit of ten per minute is really forty per minute. Treat it as a development guard and a convenience for single-process deployments, not as protection.

In production: Flask-Limiter

Scaffolded apps with authentication ship a rate_limits.py that does this properly. It limits the Google OAuth login and callback and every admin POST route through Flask-Limiter, sharing counters across workers via Redis.
.env
Without it the limiter falls back to memory and logs a warning saying so. The limits themselves live in config.py as RATELIMIT_DEFAULT, RATELIMIT_LOGIN and RATELIMIT_ADMIN, each overridable by environment variable. Run flask limiter limits to print which routes are actually limited.

Two things that fail silently

Both cost real debugging time in production if you wire this up by hand.
Flask-Limiter enforces a limit only through the function it returns. Writing limiter.limit(rule)(app.view_functions[ep]) and discarding the result does nothing — and worse, drops that endpoint out of the default limit too.
One page load fetches ten or more scripts and fonts from Flask. Without a limiter.request_filter exempting static and feather_static, a busy user gets a 429 on the app’s own JavaScript while the page is still loading.