Rate limiting

Limit how many requests a path accepts per IP, header, cookie or JSON field, per name or for the whole zone, with block, challenge or log actions.

On this page

OpenWAF has three kinds of rate limits. They are counted at the edge, before requests reach your origin.

KindWhereCountsUse it for
Rate limiting rules (rate_rules)Edge setting, up to 25 per listPer IP, header, cookie or JSON body field, in any periodLogins, one-time codes, password changes, expensive APIs. Recommended.
Rate limit rules (kind=rate_limit)Edge rulePer IP, per minuteSimple per-path limits.
Basic rate limit (rate_limit_rpm)Edge settingPer IP, per minute, whole siteA ceiling for everything.

None is set by default.

Rate limiting rules

Each rule counts the requests that match a path (and, optionally, methods, a header or a query parameter) per key. When a key goes over requests in period seconds, the rule's action applies to it, optionally for a mitigation_timeout.

Login: 60 attempts per 15 minutes per IP
{"name": "Login", "path_prefix": "/auth/login", "methods": ["POST"], "key": "ip",
 "requests": 60, "period": 900, "algorithm": "gcra", "action": "block", "mitigation_timeout": 900}

How rules are evaluated

  1. Rate limiting rules run after IP, threat feed and country rules and before the other rate limits, also for allow-listed IPs and in every WAF mode. They never apply to /__waf/ paths.
  2. Every rule that matches counts the request in its own counter, in list order.
  3. The first rule over its limit whose action is block or challenge answers. log rules only record an event and the request continues.

Keys

keyCounted byNotes
ip (default)Client IPIPv6 addresses are grouped by /64.
headerFirst value of the header key_nameE.g. x-api-key.
cookieValue of the cookie key_nameE.g. a session cookie.
body_fieldA field of the JSON body, key_name like email or user.email (up to 4 levels)Compared in lower case. Read only when Content-Type is application/json (or application/*+json) and the declared Content-Length is 1 byte to 8 KiB; the body reaches the origin untouched.

When the key has no value (missing header or cookie, a body that isn't small JSON), the request is counted by IP: removing the header never escapes the rule. Key values never leave the edge node in clear and never appear in events.

Algorithms

  • sliding_window (default): up to requests per period, counted over a sliding window.
  • gcra (steady rate): a burst of requests, then one more every period / requests seconds. Someone who keeps trying gets slowed down evenly instead of waiting for the window to end. Recommended for logins.

Actions

actionAnswer
block (default)429 Too Many Requests with Retry-After (seconds). Browsers get the site's block page; other clients get {"error":"rate_limited","retry_after":N}.
challengeBrowser navigations get the browser challenge and, once solved, pass without being counted while the pass lasts. Anything else (a login POST, an API call) gets the 429 of block.
logOnly an event; the request continues. Use it to tune a limit before enforcing it.

With mitigation_timeout greater than 0, the request that crosses the limit starts a penalty for that key: for that many seconds its matching requests get the action directly, without being counted. With 0, the action applies only while the key is over the limit.

Several edge nodes

Each edge node counts on its own. When several nodes serve your site, a client could spread its requests across them. With per_node_divisor: true (the default) each node allows ceil(requests / entry points), so the limit holds for all of them together. A legitimate client that always reaches the same node then gets only its share.

Set per_node_divisor: false when the edge limit must stay above your application's own per-IP limit, so your application answers first with its own message and the edge is only the safety net.

Templates

The dashboard offers these starting points (Start from a template):

OTP / codes
[{"name": "Send code", "path_prefix": "/auth/send-otp", "methods": ["POST"], "requests": 40, "period": 600,
  "action": "block", "mitigation_timeout": 600},
 {"name": "Verify code", "path_prefix": "/auth/verify-otp", "methods": ["POST"], "requests": 60, "period": 600,
  "action": "block", "mitigation_timeout": 600}]
General API, in log mode first
{"name": "General API", "path_prefix": "/api", "requests": 600, "period": 60, "action": "log"}

Add a rule

Domain → Security → Rate limiting → Add rule, or pick a template under Start from a template. Set Requests that count, Count per, Limit, How to count and Then, apply, order the rules with the arrows and Save changes. The Last 24 h column shows how many requests each rule limited.

Every field is described in the rule reference.

One list per name

A name can have its own list in hosts.<name>.rate_rules: it replaces the zone's list for that name ([] = no rules for it; null = use the zone's list again). Use --host www.example.com with the ratelimit commands, or host with update_settings. The dashboard shows these names but doesn't edit their lists yet. See Per-name settings.

Events

Each action leaves a security event with rule_name rate_rule:<id>, attack_type rate_limit, status 429, 503 (challenge) or 200 (log) and a message such as Rate rule login: over 60 per 900 s (key ip), never the key's value.

Rate limit rules

An edge rule of kind rate_limit counts requests per IP and per calendar minute on a path_pattern (empty or * = every path, ending in * = prefix, otherwise exact) and optional method. A client may make rpm + burst requests per minute; the next ones get 429 with the block page. rpm: 0 blocks every matching request.

When several rules match, a rule with a specific path wins over a catch-all one.

Domain → Security → Firewall → Rules → Rate limits → Add rate limit.

Basic rate limit

rate_limit_rpm caps the requests per minute from one IP to one name (IPv6 by /64). 0 (the default) turns it off. It is off while the WAF is off (mode=off or waf_level=off). The answer is 429 with the block page.

openwaf-cloud ctl zones set example.com rate_limit_rpm=600 --reason "Site-wide ceiling"

In the dashboard: Security → Firewall → Protection → Basic rate limit (requests per minute per IP).

Last updated:

↑↓ to navigate↵ to openesc to close