Reference
Rate limits
Every limiter, its default, what it keys on, and how to tune it safely.
Rate limiting is on by default and covers logins, registrations, API writes, and the federation inbox.
The limiters
| Limiter | Variable | Default | Window | Keyed on |
|---|---|---|---|---|
| Login | RL_LOGIN_MAX |
15 | 15 min | Source IP |
| Registration | RL_REGISTER_MAX |
5 | 1 hour | Source IP |
| API writes | RL_API_WRITE_MAX |
120 | 1 min | Signed-in user, else IP |
| Federation inbox | RL_INBOX_MAX |
300 | 1 min | Source IP |
Plus a hard payload cap on the inbox:
| Variable | Default | Meaning |
|---|---|---|
INBOX_MAX_BODY_BYTES |
1000000 |
Maximum accepted inbox POST body, in bytes |
How the write limiter keys
key: (c) => {
const user = c.get("user");
return user ? `u:${user.id}` : `ip:${clientIp(c)}`;
}
Session resolution runs before the limiter, so a signed-in user gets their own budget rather than sharing one with everyone behind the same NAT.
Read methods — GET, HEAD, OPTIONS — skip this limiter entirely. They are
cheap and safe; the general limiter targets mutations.
Endpoint-specific limiters (login, registration, inbox) layer stricter caps on top of this backstop.
The inbox path
The federation inbox accepts unauthenticated POSTs from arbitrary instances, so it is defended in three stages:
- Declared length — a
Content-Lengthover the cap is rejected with413before the body is touched at all. - Rate limit — over the per-IP budget returns
429withRetry-After. - Capped buffering — the body is read under a hard cap, so a missing, chunked, or spoofed length cannot stream an unbounded payload into the parser. The request is then rebuilt from the exact same bytes, so the HTTP-Signature digest still verifies.
The defaults are generous on purpose: a busy instance legitimately delivers many activities in a burst.
Redis changes the semantics
| Without Redis | With REDIS_URL |
|
|---|---|---|
| Counter scope | Per backend process | Shared across processes |
| Survives restart | No | Yes |
Responses
A limited request returns 429 Too Many Requests with a Retry-After header.
Clients should honour it rather than retrying immediately.
Turning it off
RATE_LIMIT_ENABLED=false
Tuning
- Login too tight for a shared office IP? Raise
RL_LOGIN_MAXrather than disabling the limiter. - An import tool hitting the write cap? Raise
RL_API_WRITE_MAXtemporarily and put it back afterwards. - A large federated instance getting throttled on delivery? Raise
RL_INBOX_MAX; the cost is bounded by the body-size cap.
Apply changes with docker compose up -d.
Found a mistake?Edit this page on GitHub.