Rate limiting best practices
The following sections cover typical rate limiting configurations for common use cases. You can combine the provided example rules and adjust them to your own scenario.
The main use cases for rate limiting are the following:
- Enforce granular access control to resources which includes the access control based on criteria such as user agent, IP address, referrer, host, country, and world region.
- Protect against credential stuffing and account takeover attacks.
- Limit the number of operations performed by individual clients. Includes preventing scraping by bots, accessing sensitive data, bulk creation of new accounts, and programmatic buying in e-commerce platforms.
- Protect REST APIs from resource exhaustion (targeted DDoS attacks) and resources from abuse in general.
- Protect GraphQL APIs by preventing server overload and limiting the number of operations.
Enforcing granular access control
You can use rate limiting to control how users and applications access your resources. Rate limiting helps protect your application from abuse by restricting traffic based on attributes such as user agent, IP address, referrer, or host.
Each of the following examples demonstrates how to configure a rate-limiting rule for a specific access control scenario.
Limiting request by user agent
You can restrict the number of requests that are allowed for a specific user agent. The following rule example allows mobile app users to make up to 100 requests every 10 minutes. You might also create a separate rule limiting the rate for desktop browsers.
| Setting | Value |
|---|---|
| Matching criteria | User Agent equals MobileApp |
| Expression | http.user_agent eq "MobileApp" |
| Counting characteristics | IP |
| Rate (Requests/Period) | 100 requests / 10 minutes |
| Action | Managed Challenge |
Allowing specific IP addresses or ASNs
Control access by including or excluding certain IP addresses or Autonomous System Numbers (ASNs) from a rate limiting rule.
The following rate limiting rule example allows up to 10 requests per minute from the same IP address and make a GET request to the /status path, provided the IP address is not included in the IP list entitled partner_ips.
| Setting | Value |
|---|---|
| Matching criteria | URI Path equals /status and Request Method equals GET and IP Source Address is not in list partner_ips |
| Expression | http.request.uri.path eq "/status" and http.request.method eq "GET" and not ip.src in $partner_ips |
| Counting characteristics | IP |
| Rate (Requests/Period) | 10 requests / 1 minute |
| Action | Managed Challenge |
Limiting requests by referrer
You can limit requests that originate from referrer pages, such as third-party advertisements or external websites. This use case helps to reduce the risk of indirect denial-of-service (DDoS) attacks and helps you to manage request quotas.
| Setting | Value |
|---|---|
| Matching criteria | URI Path equals /status and Request Method equals GET |
| Expression | http.request.uri.path eq "/status" and http.request.method eq "GET" |
| Counting characteristics | Header (Referrer). The HTTP header name uses a misspelling of referrer. |
| Rate (Requests/Period) | 100 requests / 10 minutes |
| Action | Block |
This example rule requires Advanced Rate Limiting.
Protecting against credential stuffing
You can use rate limiting to protect login endpoints from credential stuffing attacks. Credential stuffing
occurs when attackers use automated scripts to try multiple username and password combinations on a login form. Rate limiting helps mitigate these attacks by restricting repeated failed login attempts from the same IP address.
The following examples show three rate-limiting rules that increase restrictions and penalties based on the number of failed login attempts.
Rule 1: Initial protection threshold
Rule 1 allows up to four failed login attempts per minute. When the limit is exceeded, the system triggers a Managed Challenge. This configuration helps legitimate users recover from occasional login errors while discouraging automated bots.
| Setting | Value |
|---|---|
| Matching criteria | Hostname equals example.com and URI Path equals /login and Request Method equals POST |
| Expression | http.host eq "example.com" and http.request.uri.path eq "/login" and http.request.method eq "POST" |
| Counting characteristics | IP |
| Increment counter when | URI Path equals /login and Method equals POST and Response code is in (401, 403) |
| Counting expression | http.request.uri.path eq "/login" and http.request.method eq "POST" and http.response.code in {401 403} |
| Rate (Requests/Period) | 4 requests / 1 minute |
| Action | Managed Challenge |
Rule 2: Intermediate protection threshold
If legitimate users pass the challenge when reaching rule 1's rate limit, Rule 2 applies additional protection for clients that continue to make failed login attempts. It allows up to 10 failed attempts over 10 minutes before triggering another Managed Challenge.
| Setting | Value |
|---|---|
| Matching criteria | Hostname equals example.com and URI Path equals /login and Request Method equals POST |
| Expression | http.host eq "example.com" and http.request.uri.path eq "/login" and http.request.method eq "POST" |
| Counting characteristics | IP |
| Increment counter when | URI Path equals /login and Request Method equals POST and Response Status Code is in (401, 403) |
| Counting expression | http.request.uri.path eq "/login" and http.request.method eq "POST" and http.response.code in {401 403} |
| Rate (Requests/Period) | 10 requests / 10 minutes |
| Action | Managed Challenge |
Rule 3: Strict protection threshold
Rule 3 enforces a stronger penalty to clients exceeding the rule 2 threshold by blocking an IP address for one day after 20 failed login attempts within an hour. This rule provides a final defense against persistent attack attempts.
| Setting | Value |
|---|---|
| Matching criteria | Host equals example.com |
| Expression | http.host eq "example.com" |
| Counting characteristics | IP |
| Increment counter when | URI Path equals /login and Request Method equals POST and Response Status Code is in (401, 403) |
| Counting expression | http.request.uri.path eq "/login" and http.request.method eq "POST" and http.response.code in {401 403} |
| Rate (Requests/Period) | 20 requests / 1 hour |
| Action | Block for 1 day |
All these example rules require a business plan or above.
These three rules have a counting expression separate from the rule expression (also known as mitigation expression). When you configure a separate counting expression, the matching criteria is used when an action is triggered. In the counting expression, you can include conditions based on the HTTP response status code and HTTP response headers to integrate the rate limiting with your backend logic.
You can also decide to have two different expressions: a counting expression and a rule/mitigation expression — to define:
- The requests used to compute the rate.
- The requests actually acted upon.
For example, Rule 3 example computes the rate considering POST requests to /login that returned a 401 or 403 HTTP status code. However, when the rate limit is exceeded, CIS blocks every request
to the example.com host generated by the same IP.
Limiting the number of operations
You can use rate limiting to control how many operations a client performs within a specific time period. The rules that you configure depend on your application’s behavior and risk profile.
The following examples show how to prevent content scraping and automated activity that can overload your system or misuse your data. Examples include limiting requests by query string, JSON body parameters, or bot characteristics.
Preventing content scraping by using query string
In this example, clients perform operations (such as price lookups or adding items to a basket) on an e-commerce website through query string parameters. For example, a typical request that is sent by a client might be similar to the following:
GET https://store.com/merchant?action=lookup_price&product_id=215
Cookie: session_id=12345
Your security team might want to consider setting up a limit on the number of times a client can lookup prices to prevent bots — which might have eluded CIS Bot Management — from scraping the store's entire catalog.
| Setting | Value |
|---|---|
| Matching criteria | URI Path equals /merchant and URI Query String contains action=lookup_price |
| Expression | http.request.uri.path eq "/merchant" and http.request.uri.query contains "action=lookup_price" |
| Counting characteristics | IP |
| Rate (Requests/Period) | 10 requests / 2 minutes |
| Action | Managed Challenge |
| Setting | Value |
|---|---|
| Matching criteria | URI Path equals /merchant and URI Query String contains action=lookup_price |
| Expression | http.request.uri.path eq "/merchant" and http.request.uri.query contains "action=lookup_price" |
| Counting characteristics | IP |
| Rate (Requests/Period) | 20 requests / 5 minutes |
| Action | Block |
These two rate limiting rules match requests performing a selected action (look up price, in this example) and use IP as the counting characteristic. Similarly, to the /login example,
the two rules help reduce false positives in case of persistent (but legitimate) visitors.
You can limit the lookup of a specific product_id by using a query string parameter. By adding a query parameter as a counting characteristic, the rate is calculated across all requests, regardless of the client.
The following example limits the number of lookups for each product_id to 50 requests in 10 seconds.
| Setting | Value |
|---|---|
| Matching criteria | URI Path equals /merchant |
| Expression | http.request.uri.path eq "/merchant" |
| Counting characteristics | Query (product_id) |
| Rate (Requests/Period) | 20 requests / 10 seconds |
| Action | Block |
This example rule requires Advanced Rate Limiting.
You might follow the same pattern of rate limiting rules to protect applications handling reservations and bookings.
Preventing content scraping by using request body
Consider an application that handles the operation and its parameters through the request body in JSON format. For example, the lookup_price operation might look like the following:
POST https://api.store.com/merchant
Cookie: session_id=12345
Body:
{
"action": "lookup_price",
"product_id": 215
}
In this scenario, you can create a following rule to limit the number of actions from individual sessions:
| Setting | Value |
|---|---|
| Matching criteria | URI Path equals /merchant and JSON String action equals lookup_price |
| Expression | http.request.uri.path eq "/merchant" and lookup_json_string(http.request.body.raw, "action") eq "lookup_price" |
| Counting characteristics | Cookie (session_id) |
| Rate (Requests/Period) | 10 requests / 2 minutes |
| Action | Managed Challenge |
This example rule requires Advanced Rate Limiting and payload inspection.
You can also limit the number of lookups of each product_id regardless of the client making the requests by deploying a rule like the following:
| Setting | Value |
|---|---|
| Matching criteria | URI Path equals /merchant and JSON field action equals lookup_price |
| Expression | http.request.uri.path eq "/merchant" and lookup_json_string(http.request.body.raw, "action") eq "lookup_price" |
| Counting characteristics | JSON field (product_id) |
| Rate (Requests/Period) | 50 requests / 10 seconds |
| Action | Block |
This example rule requires Advanced Rate Limiting and payload inspection.
If the request body is not JSON format, you can use the http.request.body.raw field and regular expressions (along with the matches operator) to achieve the same goal.
Limiting requests from bots
You can use rate limiting to control automated traffic from bots. A common approach is to monitor requests that return a high number of 403 or 404 response status codes, which often indicate automated scraping activity.
In this situation, you might configure a rule similar to the following:
| Setting | Value |
|---|---|
| Matching criteria | Hostname equals example.com |
| Expression | http.host eq "example.com" |
| Counting characteristics | IP |
| Increment counter when | Response Status Code is in (401, 403) |
| Counting expression | http.response.code in {401 403} |
| Rate (Requests/Period) | 5 requests / 3 minutes |
| Action | Managed Challenge |
This example rule requires a Business plan or above.
To control the rate of actions performed by automated sources, consider use rate limiting rules together with Bot Management. With Bot Management, you can use the bot score as part of the matching criteria to apply the rule only to automated or likely automated traffic. For example, you can use a maximum score (or threshold) of 30 for likely automated traffic and 10 for automated traffic.
You can enhance protection by combining rate limiting with Bot Management. With Bot Management, you can use the bot score as part of the matching criteria to apply the rule only to automated or likely automated traffic.
For example:
- A bot score lesser than
30represents likely automated traffic. - A bot score lesser than
10represents confirmed automated traffic.
Limiting requests per session
If your application uses session cookies, use the cookie as the counting characteristic. This method groups requests from different IPs within the same session—useful for detecting distributed bot attacks.
Rule 1
| Setting | Value |
|---|---|
| Matching criteria | Bot Score less than 30 and URI Query String contains action=delete |
| Expression | cis.bot_management.score lt 30 and http.request.uri.query contains "action=delete" |
| Counting characteristics | Cookie (session_id) |
| Rate (Requests/Period) | 10 requests / 1 minute |
| Action | Managed Challenge |
Rule 2
| Setting | Value |
|---|---|
| Matching criteria | Bot Score less than 10 and URI Query String contains action=delete |
| Expression | cis.bot_management.score lt 10 and http.request.uri.query contains "action=delete" |
| Counting characteristics | Cookie (session_id) |
| Rate (Requests/Period) | 20 requests / 5 minutes |
| Action | Block |
These example rules require Advanced Rate Limiting and Bot Management.
Using JA3 fingerprints
If the application does not use a session cookie, you can use JA3 fingerprints to identify individual clients. A JA3 fingerprint is a unique identifier, available to customers with Bot Management, that allows CIS to identify requests coming from the same client. All clients have an associated fingerprint, whether they are automated or not.
| Setting | Value |
|---|---|
| Matching criteria | URI Path equals /merchant and Bot Score less than 10 |
| Expression | http.request.uri.path eq "/merchant" and cf.bot_management.score lt 10 |
| Counting characteristics | JA3 Fingerprint |
| Rate (Requests/Period) | 10 requests / 1 minute |
| Action | Managed Challenge |
This example rule requires Advanced Rate Limiting and Bot Management.
Protecting REST APIs
REST APIs can create high load on backend systems because API requests often require intensive processing or large data lookups. Uncontrolled API access can cause performance degradation or even downtime. Use Advanced Rate Limiting to prevent abuse, mitigate volumetric attacks, and protect critical resources.
Protecting resources
Even GET requests can strain the application or consume bandwidth when used for large data downloads, such as files or images.
For instance, consider the following endpoint:
GET https://api.store.com/files/<FILE_ID>
Header: x-api-key=9375
To prevent abuse while allowing legitimate downloads, you can define a rule that limits file requests without writing separate rules for each file.
| Setting | Value |
|---|---|
| Matching criteria | Hostname equals api.example.com and Request Method equals GET |
| Expression | http.host eq "api.example.com" and http.request.method eq "GET" |
| Counting characteristics | Path |
| Rate (Requests/Period) | As suggested by API Discovery or assessed by analyzing past traffic. |
| Action | Block |
This example rule requires Advanced Rate Limiting.
This rule limits downloads to 10 requests per 10 minutes per file under https://api.store.com/files/*. By using Path as the counting characteristic, you can avoid creating new rules for every new <FILE_ID>.
The rate is calculated per file, regardless of client IP or session ID.
You can further strengthen protection by combining Path with a client identifier such as x-api-key or IP. This approach allows you to restrict the number of downloads a specific client can make for a
given file.
| Setting | Value |
|---|---|
| Matching criteria | Hostname equals api.store.com and Request Method equals GET |
| Expression | http.host eq "api.example.com" and http.request.method eq "GET" |
| Counting characteristics | Path and Header (x-api-key) |
| Rate (Requests/Period) | As suggested by API Discovery or assessed by analyzing past traffic. |
| Action | Block |
This example rule requires Advanced Rate Limiting.
Protecting GraphQL APIs
Preventing server overload for GraphQL APIs can be different from preventing overload for RESTful APIs. One of the biggest challenges that are posed by applications that are built on GraphQL is that a single path manages all queries to the server,
and every request is usually a POST operation. This prevents creating different rate limits for different API based on the HTTP method and URI path.
However, instead of using the method and path like a RESTful API, the purpose of the request is usually embedded in the body, which has information on what data the client wants to fetch or mutate (according to GraphQL's terminology for server-side data modification), along with any additional data that is required to carry out the action.
To prevent server overload, consider the following approaches:
- Limit the number of times a particular user can call the same GraphQL operation name.
- Limit the total amount of query complexity any given user is allowed to request.
- Limit any individual request's query complexity.
The following examples are based on an application that accepts reviews for movies.
POST https://moviereviews.example.com/graphql
Cookie: session_id=12345
Body:
{
"data": {
"createReview": {
"stars": 5,
"commentary": "This is a great movie!"
}
}
}
Limiting the number of operations
To limit the rate of actions, create the following rule:
| Setting | Value |
|---|---|
| Matching criteria | URI Path equals /graphql and Body contains createReview |
| Expression | http.request.uri.path eq "/graphql" and http.request.body.raw contains "createReview" |
| Counting characteristics | Cookie (session_id) |
| Rate (Requests/Period) | 5 requests / 1 hour |
| Action | Block |
This example rule requires Advanced Rate Limiting and payload inspection.
Limiting the total amount of query complexity
The complexity of handling a GraphQL request can vary significantly. Because the API uses a single endpoint, it can be difficult to determine the complexity of each request before it is processed.
To prevent resource exhaustion on the origin server, limit the total request complexity per client over time, rather than limiting the number of requests. CIS Rate Limiting enables you to create rules that track complexity over time and block requests that exceed a defined complexity budget.
This method requires the origin server to assign a complexity score to each request and include that score in the HTTP response header. The rate-limiting mechanism then uses the score information to update the complexity budget for that specific client.
The following example defines a total complexity budget of 1,000 per hour:
| Setting | Value |
|---|---|
| Matching criteria | URI Path contains /graphql |
| Expression | http.request.uri.path eq "/graphql" |
| Counting characteristics | Cookie (session_id) |
| Score per period | 1,000 |
| Period | 1 hour |
| Response header name | score |
| Action | Block |
This example rule requires Advanced Rate Limiting and payload inspection.
When the origin server processes a request, it adds a score HTTP header to the response with a value which indicates how much work the origin has performed to handle it. For example, 100. In the next hour, the same
client can perform requests up to an additional budget of 900. As soon as this budget is exceeded, later requests are blocked until the timeout expires.
Limiting any individual query’s complexity
API Shield customers can use GraphQL malicious query protection to protect their GraphQL APIs. This feature scans incoming GraphQL traffic for queries that might overload the origin server and cause a denial-of-service condition.
You can create rules to limit the depth and size of incoming GraphQL queries. These rules help block suspicious or excessively complex queries before they impact performance.