[[rate_limits]]
Token-bucket request budgets per client and path, enforced in Rust before routing: a refused request gets 429 and never reaches Node.
gio.toml
[[rate_limits]]
path = "/api/*rest"
per_ip = 100
window_seconds = 60
burst = 20
[[rate_limits]]
path = "/api/login"
per_ip = 5
window_seconds = 60
burst = 0There are no rate limits by default. Each table is one rule; add as many as you need.
Reference
| Key | Default | Description |
|---|---|---|
path (required)string | - | The paths the rule covers, in the pattern syntax the other path rules use: literal segments, :param for one segment, and a trailing *rest catch-all (/api/*rest, which also covers /api itself). Without a catch-all the match is exact (/api/login). The older spellings still work: /api/* is the same as /api/*rest, and /api* covers every path starting with /api (/apiary too). A path that cannot be parsed stops startup. |
per_ipinteger | 100 | Requests per window_seconds per client: the bucket refills at this rate. |
window_secondsinteger | 60 | The window per_ip is counted over. |
burstinteger | 20 | Extra requests on top of per_ip: a new client's bucket holds per_ip + burst tokens. |
key_headerstring | - | Key buckets on this request header's value (an API key) as well as the client. Case-insensitive name; the first 64 bytes of the value count. A request without the header uses the client's own bucket. |
max_keys_per_clientinteger | 64 | With key_header: distinct header values one client may hold a budget for. Further values share the client's own bucket, so rotating the header cannot mint fresh budgets. Raise it, or set 0, for an API gateway whose many keys arrive from one address. |
The total number of live buckets is capped by [server] rate_limit_max_buckets (100000).
Behavior
- One rule per request. The rule whose segments cover most of the path before its wildcard wins; on a tie an exact rule beats a wildcard, then more literal text beats a
:param(/users/meover/users/:id). Above,/api/loginuses its own rule and every other/apipath the general one. - Before the rules. Limits run before the CSRF check, guards, redirects and rewrites, so a flood of refused or redirected requests still spends budget. With
[i18n]on, they match the path without its locale prefix: a/api/*restrule also covers/de/api/x. - Canonical paths. Requests are matched with repeated and trailing slashes collapsed and unreserved escapes decoded, so
/api/login/,//api//loginand/api/%6Coginshare one bucket. - A client is an IPv4 address or an IPv6 /64 (IPv6 hosts control a whole /64), after
[server] trusted_proxiesresolution. - Headers. An admitted response carries
X-RateLimit-Limit, the bucket's size (per_ip + burst), andX-RateLimit-Remaining, the requests left in it (at most the limit). A refused one looks like this, for aper_ip = 3,burst = 0rule (other headers left out):
text
HTTP/1.1 429 Too Many Requests
content-type: application/json
retry-after: 20
x-ratelimit-limit: 3
x-ratelimit-remaining: 0
x-gio-refused: unread
{"error":"rate limit exceeded"}Retry-Afteriswindow_seconds / per_ip(at least 1): the time one token takes to come back.x-gio-refused: unreadtells<GioForm>the submission never reached the app.- Rust's own
/_gioendpoints are not limited, except/_gio/image, the most expensive one. Apublic/file answered at the site root is also held to rules written for its/public/...URL, charged once. - Each refusal is logged at
warnwith the client, path and rule, and counted in the metrics.
Startup errors
A path that cannot be parsed stops startup, and --check-config reports it: one that does not start with /, a *rest that is not the last segment, or a * anywhere but the end.
text
gio.toml:12: invalid `rate_limits[1].path`: path "api/*rest" must start with '/'Startup warnings
| When | Startup warning |
|---|---|
max_keys_per_client = 0 on a rule with key_header | [[rate_limits]] /api/*: max_keys_per_client = 0 - one client can mint a fresh budget for every key_header value it sends |
[server] rate_limit_max_buckets = 0 with any rule | [server] rate_limit_max_buckets = 0: rate-limit buckets are never evicted - clients rotating addresses grow memory without bound |
Examples
Slow down login attempts
gio.toml
[[rate_limits]]
path = "/login"
per_ip = 10
window_seconds = 900 # 10 per 15 minutes
burst = 0Per API key budgets
gio.toml
[[rate_limits]]
path = "/api/*rest"
per_ip = 600
window_seconds = 60
key_header = "x-api-key"
max_keys_per_client = 256 # a partner's gateway sends many keys from one addressProtect the image optimizer
gio.toml
[[rate_limits]]
path = "/_gio/image"
per_ip = 120
window_seconds = 60Good to know
per_ip = 0is not an off switch: withburst = 0it refuses every request to the path. Remove the rule to stop limiting.- Buckets that have refilled are dropped every minute; a new bucket starts full, so nothing is lost.
- Limits are per server instance. Behind a load balancer each instance counts on its own.
Related
- Rate limits on the overview
[server]-trusted_proxiesandrate_limit_max_buckets[prefetch]- budgets for prefetch requests- Metrics
Version history
| Version | Changes |
|---|---|
v0.1.0-beta.8 | Added max_keys_per_client. path takes the rule pattern syntax (:param, *rest; a /api/*rest rule used to match nothing), and a path that cannot be parsed stops startup. Paths match the canonical request path, and /api/* also covers /api. Clients are resolved through trusted proxies, and the bucket store is capped by [server] rate_limit_max_buckets. X-RateLimit-Limit is per_ip + burst (it was per_ip, below what X-RateLimit-Remaining could show), and a refusal carries X-Gio-Cache: bypass. |
v0.1.0-beta.6 | /_gio/image honors the rules. |
v0.1.0-beta.1 | Introduced with path, per_ip, window_seconds, burst and key_header. |