diff --git a/.env.example b/.env.example
index fb361c4f8..d3da90d0a 100644
--- a/.env.example
+++ b/.env.example
@@ -122,6 +122,28 @@ LOCAL_UPLOAD_BASE_URL=http://localhost:8080
# Example: CORS_ALLOWED_ORIGINS=https://app.multica.ai,https://staging.multica.ai
CORS_ALLOWED_ORIGINS=
+# ==================== Rate limiting (optional Redis) ====================
+# Per-IP fixed-window rate limiter on the public auth endpoints
+# (/auth/send-code, /auth/verify-code, /auth/google). Backed by Redis.
+# When REDIS_URL is unset the limiter is a no-op (fail-open) and the
+# backend logs "rate limiting disabled: REDIS_URL not configured" at
+# startup. The same REDIS_URL is reused by the realtime fan-out hub,
+# the PAT cache, and the daemon-token cache.
+# REDIS_URL=redis://localhost:6379/0
+# Max requests per IP per minute. Defaults are 5 for send-code/google
+# and 20 for verify-code.
+# RATE_LIMIT_AUTH=5
+# RATE_LIMIT_AUTH_VERIFY=20
+# Comma-separated CIDRs whose X-Forwarded-For the auth limiter is
+# allowed to trust. Empty (default) = never trust XFF, only RemoteAddr.
+# REQUIRED behind a reverse proxy — otherwise every real user shares
+# the proxy IP and the whole deployment lands in one bucket, turning
+# /auth/send-code into 5 req/min site-wide. Use e.g. "127.0.0.1/32,::1/128"
+# for same-host Caddy/Nginx, or the CDN's published ranges for ALB/CF.
+# This is a separate list from MULTICA_TRUSTED_PROXIES above (which
+# governs the autopilot webhook limiter).
+# RATE_LIMIT_TRUSTED_PROXIES=
+
# Realtime metrics endpoint (/health/realtime) access control. See MUL-1342.
# When unset, the endpoint only serves direct loopback (127.0.0.1 / ::1)
# callers with no forwarding headers and returns 404 to everything else —
diff --git a/apps/docs/content/docs/environment-variables.mdx b/apps/docs/content/docs/environment-variables.mdx
index 83eb02605..c3b4f5d4c 100644
--- a/apps/docs/content/docs/environment-variables.mdx
+++ b/apps/docs/content/docs/environment-variables.mdx
@@ -128,6 +128,25 @@ Three allowlist layers combine by priority. **If any layer is set to a non-empty
**Invite flows themselves do not check the signup allowlist** — but the invitee must still be able to **sign in** before accepting the invite. If they already have a Multica account (for example from another workspace), they can accept directly, unaffected by the allowlist; **if they have never signed up**, the first step of sign-in (requesting a verification code) still passes through the allowlist check, and an email rejected by `ALLOW_SIGNUP=false` or by `ALLOWED_EMAILS` / `ALLOWED_EMAIL_DOMAINS` **cannot finish signup, and therefore cannot accept the invite**.
+## Rate limiting (optional Redis)
+
+Public auth endpoints — `/auth/send-code`, `/auth/verify-code`, `/auth/google` — have per-IP fixed-window rate limiting in front of them. The limiter is backed by Redis. When `REDIS_URL` is unset the middleware is a **no-op** (fail-open) and the backend logs `rate limiting disabled: REDIS_URL not configured` at startup.
+
+| Variable | Default | Description |
+|---|---|---|
+| `REDIS_URL` | empty | Redis connection URL (for example `redis://localhost:6379/0`). When unset, rate limiting on auth endpoints is disabled. The same Redis is also used by the realtime hub fan-out, the PAT cache, and the daemon-token cache — they all fall back to in-memory / direct-DB mode when unset |
+| `RATE_LIMIT_AUTH` | `5` | Max requests per IP per minute against `/auth/send-code` and `/auth/google` |
+| `RATE_LIMIT_AUTH_VERIFY` | `20` | Max requests per IP per minute against `/auth/verify-code` |
+| `RATE_LIMIT_TRUSTED_PROXIES` | empty | Comma-separated CIDRs whose `X-Forwarded-For` header the limiter is allowed to trust. Empty (the default) means **never trust XFF** — the limiter only uses the direct connection's `RemoteAddr` |
+
+When a request is over the limit, the server replies with `429 Too Many Requests`, `Retry-After: 60`, and body `{"error":"too many requests"}`.
+
+
+**Behind a reverse proxy you must set `RATE_LIMIT_TRUSTED_PROXIES`.** Otherwise every real user shares the proxy's IP from the backend's point of view, the whole deployment ends up in one bucket, and `/auth/send-code` becomes 5 req/min for the entire site. Typical values: `127.0.0.1/32,::1/128` for a same-host Caddy / Nginx; the CDN's published ranges for Cloudflare / ALB / CloudFront. Only IPs whose `RemoteAddr` falls inside one of these CIDRs may use `X-Forwarded-For` to identify the client.
+
+
+This separate `RATE_LIMIT_TRUSTED_PROXIES` is **not** the same as `MULTICA_TRUSTED_PROXIES`, which controls the autopilot-webhook limiter (`/api/webhooks/autopilots/{token}`). Each limiter parses its own list, so a deployment behind a proxy should set both.
+
## Daemon tuning parameters
The daemon runs on the user's local machine, and its config is read from local environment variables too. The common ones:
diff --git a/apps/docs/content/docs/environment-variables.zh.mdx b/apps/docs/content/docs/environment-variables.zh.mdx
index 57dc22f4d..b65d3a8a1 100644
--- a/apps/docs/content/docs/environment-variables.zh.mdx
+++ b/apps/docs/content/docs/environment-variables.zh.mdx
@@ -128,6 +128,25 @@ Multica 存储用户上传的附件(评论里的图片、文件等)。**优
**邀请流程本身不检查 signup 白名单**——但被邀请人必须先能**登录**才能接受邀请。如果对方已经有 Multica 账号(比如在其他工作区注册过),可以直接接受,不受白名单影响;**如果对方还没注册过**,他们登录的第一步(发送验证码)仍然会过白名单检查,被 `ALLOW_SIGNUP=false` 或 `ALLOWED_EMAILS` / `ALLOWED_EMAIL_DOMAINS` 拒绝的邮箱**无法完成注册,也就没法接受邀请**。
+## 速率限制(可选 Redis)
+
+公开认证端点——`/auth/send-code`、`/auth/verify-code`、`/auth/google`——前面挂了按 IP 的固定窗口限流。限流器后端是 Redis。`REDIS_URL` 不设时中间件**直通**(fail-open),后端启动会打日志 `rate limiting disabled: REDIS_URL not configured`。
+
+| 环境变量 | 默认值 | 说明 |
+|---|---|---|
+| `REDIS_URL` | 空 | Redis 连接 URL(例如 `redis://localhost:6379/0`)。不设时认证端点的限流功能直接关闭。同一个 Redis 也被实时事件 fan-out、PAT 缓存、守护进程 token 缓存复用;不设时这些组件分别回落到内存模式 / 直查 DB |
+| `RATE_LIMIT_AUTH` | `5` | 单 IP 每分钟对 `/auth/send-code` 和 `/auth/google` 的最大请求数 |
+| `RATE_LIMIT_AUTH_VERIFY` | `20` | 单 IP 每分钟对 `/auth/verify-code` 的最大请求数 |
+| `RATE_LIMIT_TRUSTED_PROXIES` | 空 | 逗号分隔的 CIDR 列表,列在内的来源 IP 才允许通过 `X-Forwarded-For` 标识客户端。默认空 = **永不信任 XFF**,限流器只看直连的 `RemoteAddr` |
+
+被限流的请求会返回 `429 Too Many Requests`,带 `Retry-After: 60` 头和 `{"error":"too many requests"}` 响应体。
+
+
+**部署在反向代理后面时必须设 `RATE_LIMIT_TRUSTED_PROXIES`。** 否则在后端看来所有真实用户都共用代理那个 IP,整个部署落到同一个桶里,`/auth/send-code` 会变成全站每分钟只能发 5 次。常见值:本机 Caddy / Nginx 用 `127.0.0.1/32,::1/128`;Cloudflare / ALB / CloudFront 用各家公开的 CDN IP 段。只有 `RemoteAddr` 落在这些 CIDR 内的请求才被允许通过 `X-Forwarded-For` 改写客户端 IP。
+
+
+这里的 `RATE_LIMIT_TRUSTED_PROXIES` 和 `MULTICA_TRUSTED_PROXIES` **不是同一个**变量——后者控制的是 autopilot webhook 端点(`/api/webhooks/autopilots/{token}`)的限流器。两个限流器各自读各自的列表,部署在代理后面的实例需要两个都配上。
+
## 守护进程的调节参数
守护进程跑在用户本地机器上,配置也是读本地环境变量。常用的几个: