mirror of
https://github.com/open-webui/open-webui.git
synced 2026-07-16 06:03:26 -05:00
[GH-ISSUE #24631] RFC: Backend-only, config-driven, per-user endpoint rate limiting middleware #91104
Reference in New Issue
Block a user
Delete Branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Originally created by @Srujan4812 on GitHub (May 12, 2026).
Original GitHub issue: https://github.com/open-webui/open-webui/issues/24631
RFC: Backend-only, config-driven, per-user endpoint rate limiting middleware
Summary
Add a pure-ASGI middleware that applies per-user, per-endpoint-class rate limits across the API surface (
chat,embeddings,uploads,admin), using the existing Redis connection andAppConfigplumbing. Backend-only, off by default, zero UI and zero telemetry changes in this RFC. Admin-UI bindings, observability, and per-user overrides are intentionally deferred to follow-ups once this lands.Today the project only rate-limits signin (
RateLimiterat 15 req / 180 s inrouters/auths.py). Every other endpoint — including the ones that directly drive upstream LLM cost — is unthrottled.Problem
A legitimately-issued or compromised JWT / API key can:
/api/chat/completions(Ollama / OpenAI / Anthropic / Gemini) with no per-user ceiling./api/embeddings,/api/v1/retrieval/*) —SentenceTransformercalls use the shared anyio thread pool, so a burst starves every other handler in the worker./api/v1/files,/api/v1/retrieval/process/*,/api/v1/knowledge/*) — only a total-size cap exists, no per-user rate./api/v1/configs/*,/api/v1/models/*,/api/v1/groups/*) if an admin token is misused or a SCIM bulk sync is misconfigured.The algorithmic primitive (
utils/rate_limit.RateLimiter, Redis rolling window with in-memory fallback) already exists. It just isn't wired into anything beyond signin.Scope (intentionally small)
In scope for this RFC / first PR:
backend/open_webui/utils/rate_limit_middleware.py.backend/open_webui/main.py.AppConfigfields inbackend/open_webui/config.pyand an env bootstrap flag inbackend/open_webui/env.py.backend/open_webui/constants.py.backend/open_webui/test/util/test_rate_limit_middleware.pyusingfakeredis.aioredis.Explicitly out of scope for this RFC (deferred):
src/changes)./statsendpoint or runtime introspection API.signin_rate_limiterto the new primitive.This keeps the first PR narrow enough for a single reviewer pass while establishing the primitive that the deferred work can plug into.
Design
Identity resolution (no DB hits)
The middleware runs immediately after
AuthTokenMiddleware, sorequest.state.tokenis already parsed. Priority:utils/auth.decode_token, take theidclaim. No DB lookup; ifis_valid_tokenrevocation matters, the route handler'sget_current_userwill reject shortly after.sk-…) → SHA-256 first 12 chars as identity (never the raw key in a Redis key).request.client.host(respectingforwarded_allow_ips='*'already configured inuvicorn.run).Endpoint classification
A compiled regex table inside the middleware module — ships hard-coded for this PR:
chat/api/chat/completions,/api/v1/chat/completions,/ollama/api/chat,/openai/chat/completionsembeddings/api/embeddings,/api/v1/embeddingsuploads/api/v1/files,/api/v1/retrieval/process,/api/v1/knowledge/*/file/addadmin/api/v1/configs,/api/v1/models,/api/v1/groups,/api/v1/users(write methods only)Unmatched paths are not rate-limited. Skip list (always bypass):
/health,/ready,/health/db,/ws/*, SPA static assets.Algorithm
Sliding-window log via a single Lua script on a Redis sorted set — one atomic
EVALSHAround-trip per limited request.Script is loaded once per worker with
SCRIPT LOAD; requests useEVALSHAand fall back toEVALonNOSCRIPT. Sorted-set size is bounded bylimit + 1entries per active (user, class) tuple — ~3 KB peak at default caps, aggressively cleaned byZREMRANGEBYSCORE.Key layout
id_type:u(user_id),k(api_key_hash),i(ip).Config shape
Extend
AppConfiginconfig.py:Defaults are conservative so operators who flip the switch don't get surprise 429s on normal usage.
Failure semantics
429 Too Many RequestswithRetry-Afterseconds and standardRateLimit-Limit/RateLimit-Remaining/RateLimit-Resetheaders (draft-ietf-httpapi-ratelimit-headers-07 form). Body:{"detail": "Rate limit exceeded for <class>"}.Middleware placement
Rationale: sits after
AuthTokenMiddleware(so it can readrequest.state.tokenwithout re-parsing), beforeWebsocketUpgradeGuardMiddlewareandCORSMiddleware(so it short-circuits before any WS/Engine.IO negotiation and before CORS preflight costs).Why this is worth accepting
AppConfig, pure-ASGI middleware convention, existingRateLimiteralgorithm). No new dependencies.ENABLE_RATE_LIMITING=true. Operators upgrade without noticing.utils/asgi_middleware.py; usesapp.state.redisexactly like the Socket.IO pool, Yjs manager, and task listener.Proposed PR sequence (for transparency; only #1 is up for debate here)
openwebui.ratelimit.hits{class,outcome}) and a minimal/api/v1/configs/rate-limitsread endpoint.src/routes/(app)/admin/settings/.signin_rate_limiterto the new primitive.Open questions for maintainers
Depends(rate_limit("chat"))decorators? Middleware is faster and centralises policy; decorators are more explicit. Happy to switch before implementation.Happy to iterate scope, defaults, and algorithm choice here before opening the PR.
@owui-terminator[bot] commented on GitHub (May 12, 2026):
🔍 Related Issues Found
I found some existing issues that might be related. Please check if any of these are duplicates or contain helpful solutions:
🟣 #21632 feat: Firebase Auth & API Rate limit
This is the closest prior request for API rate limiting. It explicitly asks for throttling API usage for approved users, which overlaps with the new RFC’s backend rate limiting goals even though the proposed mechanism is broader and per-endpoint.
by amjiddader
🟣 #23323 feat: built-in per-user and per-group token/message usage limits
This issue asks for built-in enforcement of per-user and per-group usage limits. While it focuses on token/message quotas rather than request-rate middleware, it addresses the same unmet need of platform-level limits on user consumption and is a strong conceptual follow-up.
by smorello87
🟣 #23400 bug: [Security] Unauthenticated /api/config endpoint and missing rate limiting on /signup
This security issue mentions missing rate limiting on /signup, showing prior concern about absent rate limiting in the backend. It is related as evidence of the project already having endpoint rate-limit gaps, though it targets a different route and threat model.
by autoailabadmin
💡 If your issue is a duplicate, please close it and add any additional details to the existing issue instead.
This comment was generated automatically. React with 👍 if helpful, 👎 if not.
@Classic298 commented on GitHub (May 12, 2026):
As per issue template - discussions not issues