Rule Gallery
Explore a curated collection of example configurations spanning from common to unconventional use cases for the Traffic Policy module.
A number of these examples come from a longer article about how ngrok makes policy management accessible to developers, including a simple Go-based application for testing these and other configurations.
See the following categories for specific expressions and actions:
Authentication
Add JWT authentication and key-based rate limiting
Building from our Auth0 guide, these rules also add rate limiting based on your consumers' JWTs.
- YAML
- JSON
# snippet
---
inbound:
- expressions: []
name: "Add JWT authentication and rate limiting"
actions:
- type: "rate-limit"
config:
name: "Only allow 30 requests per minute"
algorithm: "sliding_window"
capacity: 30
rate: "60s"
bucket_key:
- "req.Headers['x-api-key']"
- type: "jwt-validation"
config:
issuer:
allow_list:
- value: "https://<YOUR-AUTH-PROVIDER>"
audience:
allow_list:
- value: "<YOUR-NGROK-DOMAIN>"
http:
tokens:
- type: "jwt"
method: "header"
name: "Authorization"
prefix: "Bearer "
jws:
allowed_algorithms:
- "RS256"
keys:
sources:
additional_jkus:
- "https://<YOUR-AUTH-PROVIDER>/.well-known/jwks.json"
outbound: []
// snippet
{
"inbound": [
{
"expressions": [],
"name": "Add JWT authentication and rate limiting",
"actions": [
{
"type": "rate-limit",
"config": {
"name": "Only allow 30 requests per minute",
"algorithm": "sliding_window",
"capacity": 30,
"rate": "60s",
"bucket_key": [
"req.Headers['x-api-key']"
]
}
},
{
"type": "jwt-validation",
"config": {
"issuer": {
"allow_list": [
{
"value": "https://<YOUR-AUTH-PROVIDER>"
}
]
},
"audience": {
"allow_list": [
{
"value": "<YOUR-NGROK-DOMAIN>"
}
]
},
"http": {
"tokens": [
{
"type": "jwt",
"method": "header",
"name": "Authorization",
"prefix": "Bearer "
}
]
},
"jws": {
"allowed_algorithms": [
"RS256"
],
"keys": {
"sources": {
"additional_jkus": [
"https://<YOUR-AUTH-PROVIDER>/.well-known/jwks.json"
]
}
}
}
}
}
]
}
],
"outbound": []
}
Rate limiting
Rate limit for specific endpoint
This rule applies rate limiting of 30
requests per second to the endpoint
/api/videos
.
- YAML
- JSON
# snippet
---
inbound:
- expressions:
- "req.url.contains('/api/specific_endpoint')"
actions:
- type: "rate-limit"
config:
name: "Only allow 30 requests per minute"
algorithm: "sliding_window"
capacity: 30
rate: "60s"
bucket_key:
- "conn.client_ip"
// snippet
{
"inbound": [
{
"expressions": [
"req.url.contains('/api/specific_endpoint')"
],
"actions": [
{
"type": "rate-limit",
"config": {
"name": "Only allow 30 requests per minute",
"algorithm": "sliding_window",
"capacity": 30,
"rate": "60s",
"bucket_key": [
"conn.client_ip"
]
}
}
]
}
]
}
Rate limit API consumers based on authentication status
Create a low rate limit for unauthenticated (likely free) users, while allowing authenticated users a higher level of capacity.
- YAML
- JSON
# snippet
---
inbound:
- expressions:
- "!('Authorization' in req.Headers)"
name: "Unauthorized rate limiting tier"
actions:
- type: "rate-limit"
config:
name: "Allow 10 requests per minute"
algorithm: "sliding_window"
capacity: 10
rate: "60s"
bucket_key:
- "conn.ClientIP"
- expressions:
- "('Authorization' in req.Headers)"
name: "Authorized rate limiting tier"
actions:
- type: "rate-limit"
config:
name: "Allow 100 requests per minute"
algorithm: "sliding_window"
capacity: 100
rate: "60s"
bucket_key:
- "conn.ClientIP"
outbound: []
// snippet
{
"inbound": [
{
"expressions": [
"!('Authorization' in req.Headers)"
],
"name": "Unauthorized rate limiting tier",
"actions": [
{
"type": "rate-limit",
"config": {
"name": "Allow 10 requests per minute",
"algorithm": "sliding_window",
"capacity": 10,
"rate": "60s",
"bucket_key": [
"conn.ClientIP"
]
}
}
]
},
{
"expressions": [
"('Authorization' in req.Headers)"
],
"name": "Authorized rate limiting tier",
"actions": [
{
"type": "rate-limit",
"config": {
"name": "Allow 100 requests per minute",
"algorithm": "sliding_window",
"capacity": 100,
"rate": "60s",
"bucket_key": [
"conn.ClientIP"
]
}
}
]
}
],
"outbound": []
}
Rate limit API consumers based on pricing tiers
Using a naming scheme in your upstream servers, and API calls using a tier
header, you can quickly customize access to your API based on any number of pricing tiers.
- YAML
- JSON
# snippet
---
inbound:
- expressions:
- "!('Tier' in req.Headers)"
name: "Free rate limiting tier"
actions:
- type: "rate-limit"
config:
name: "Allow 10 requests per minute"
algorithm: "sliding_window"
capacity: 10
rate: "60s"
bucket_key:
- "conn.ClientIP"
- expressions:
- "getReqHeader('tier').exists(v, v.matches('(?i)bronze'))"
name: "Bronze rate limiting tier"
actions:
- type: "rate-limit"
config:
name: "Allow 100 requests per minute"
algorithm: "sliding_window"
capacity: 100
rate: "60s"
bucket_key:
- "conn.ClientIP"
- expressions:
- "getReqHeader('tier').exists(v, v.matches('(?i)silver'))"
name: "Bronze rate limiting tier"
actions:
- type: "rate-limit"
config:
name: "Allow 1000 requests per minute"
algorithm: "sliding_window"
capacity: 1000
rate: "60s"
bucket_key:
- "conn.ClientIP"
- expressions:
- "getReqHeader('tier').exists(v, v.matches('(?i)gold'))"
name: "Gold rate limiting tier"
actions:
- type: "rate-limit"
config:
name: "Allow 10000 requests per minute"
algorithm: "sliding_window"
capacity: 10000
rate: "60s"
bucket_key:
- "conn.ClientIP"
outbound: []
// snippet
{
"inbound": [
{
"expressions": [
"!('Tier' in req.Headers)"
],
"name": "Free rate limiting tier",
"actions": [
{
"type": "rate-limit",
"config": {
"name": "Allow 10 requests per minute",
"algorithm": "sliding_window",
"capacity": 10,
"rate": "60s",
"bucket_key": [
"conn.ClientIP"
]
}
}
]
},
{
"expressions": [
"getReqHeader('tier').exists(v, v.matches('(?i)bronze'))"
],
"name": "Bronze rate limiting tier",
"actions": [
{
"type": "rate-limit",
"config": {
"name": "Allow 100 requests per minute",
"algorithm": "sliding_window",
"capacity": 100,
"rate": "60s",
"bucket_key": [
"conn.ClientIP"
]
}
}
]
},
{
"expressions": [
"getReqHeader('tier').exists(v, v.matches('(?i)silver'))"
],
"name": "Bronze rate limiting tier",
"actions": [
{
"type": "rate-limit",
"config": {
"name": "Allow 1000 requests per minute",
"algorithm": "sliding_window",
"capacity": 1000,
"rate": "60s",
"bucket_key": [
"conn.ClientIP"
]
}
}
]
},
{
"expressions": [
"getReqHeader('tier').exists(v, v.matches('(?i)gold'))"
],
"name": "Gold rate limiting tier",
"actions": [
{
"type": "rate-limit",
"config": {
"name": "Allow 10000 requests per minute",
"algorithm": "sliding_window",
"capacity": 10000,
"rate": "60s",
"bucket_key": [
"conn.ClientIP"
]
}
}
]
}
],
"outbound": []
}
Block unwanted requests
Disallow bots and crawlers with a robots.txt
This rule returns a custom response with a robots.txt
file to deny search engine or AI crawlers on all paths.
- YAML
- JSON
# snippet
---
inbound:
- name: "Add `robots.txt` to deny all bots and crawlers"
expressions:
- "req.url.contains('/robots.txt')"
actions:
- type: "custom-response"
config:
status_code: 200
content: "User-agent: *\r\nDisallow: /"
headers:
content-type: "text/plain"
// snippet
{
"inbound": [
{
"name": "Add `robots.txt` to deny all bots and crawlers",
"expressions": [
"req.url.contains('/robots.txt')"
],
"actions": [
{
"type": "custom-response",
"config": {
"status_code": 200,
"content": "User-agent: *\r\nDisallow: /",
"headers": {
"content-type": "text/plain"
}
}
}
]
}
]
}
You can also extend the expression above to create specific rules for crawlers based on their user agent strings, like ChatGPT-User
and GPTBot
.
- YAML
- JSON
# snippet
---
inbound:
- name: "Add `robots.txt` to deny specific bots and crawlers"
expressions:
- "req.url.contains('/robots.txt')"
actions:
- type: "custom-response"
config:
status_code: 200
content: "User-agent: ChatGPT-User\\r\\nDisallow: /"
headers:
content-type: "text/plain"
// snippet
{
"inbound": [
{
"name": "Add `robots.txt` to deny specific bots and crawlers",
"expressions": [
"req.url.contains('/robots.txt')"
],
"actions": [
{
"type": "custom-response",
"config": {
"status_code": 200,
"content": "User-agent: ChatGPT-User\\r\\nDisallow: /",
"headers": {
"content-type": "text/plain"
}
}
}
]
}
]
}
Block bots and crawlers by user agent
In addition to, or instead of, denying bots and crawlers with a robots.txt
file, you can also take action on only incoming requests that contain specific strings in the req.user_agent
request variable.
You can extend the expression to include additional user agents by extending (chatgpt-user|gptbot)
like so: (chatgpt-user|gptbot|anthropic|claude|any|other|user-agent|goes|here)
.
- YAML
- JSON
# snippet
---
inbound:
- name: "Block specific bots by user agent"
expressions:
- "req.user_agent.matches('(?i).*(chatgpt-user|gptbot)/\\\\d+.*')"
actions:
- type: "deny"
config:
status_code: 404
// snippet
{
"inbound": [
{
"name": "Block specific bots by user agent",
"expressions": [
"req.user_agent.matches('(?i).*(chatgpt-user|gptbot)/\\\\d+.*')"
],
"actions": [
{
"type": "deny",
"config": {
"status_code": 404
}
}
]
}
]
}
Deny non-GET requests
This rule denies all inbound traffic that is not a GET request.
- YAML
- JSON
# snippet
---
inbound:
- expressions:
- "req.method != 'GET'"
actions:
- type: "deny"
// snippet
{
"inbound": [
{
"expressions": [
"req.method != 'GET'"
],
"actions": [
{
"type": "deny"
}
]
}
]
}
Custom response for unauthorized requests
This rule sends a custom response with status code 401
and body Unauthorized
for requests without an Authorization header.
- YAML
- JSON
# snippet
---
inbound:
- expressions:
- "!('authorization' in req.headers)"
actions:
- type: "custom-response"
config:
status_code: 401
content: "Unauthorized"
// snippet
{
"inbound": [
{
"expressions": [
"!('authorization' in req.headers)"
],
"actions": [
{
"type": "custom-response",
"config": {
"status_code": 401,
"content": "Unauthorized"
}
}
]
}
]
}
Block traffic from specific countries
Remain compliant with data regulations or sanctions by blocking requests originating from one or more countries using their respective ISO country codes.
- YAML
- JSON
# snippet
---
inbound:
- expressions:
- "conn.Geo.CountryCode in ['<COUNTRY-01>', '<COUNTRY-02>']"
name: "Block traffic from unwanted countries"
actions:
- type: "custom-response"
config:
status_code: 401
content: "Unauthorized request due to country of origin"
outbound: []
// snippet
{
"inbound": [
{
"expressions": [
"conn.Geo.CountryCode in ['<COUNTRY-01>', '<COUNTRY-02>']"
],
"name": "Block traffic from unwanted countries",
"actions": [
{
"type": "custom-response",
"config": {
"status_code": 401,
"content": "Unauthorized request due to country of origin"
}
}
]
}
],
"outbound": []
}
Limit request sizes
Prevent excessively large user uploads, like text or images, that might cause performance or availability issues for your upstream service.
- YAML
- JSON
# snippet
---
inbound:
- expressions:
- "req.Method == 'POST' || req.Method == 'PUT'"
- "req.ContentLength >= 1000"
name: "Block POST/PUT requests of excessive length"
actions:
- type: "custom-response"
config:
status_code: 400
content: "Error: content length"
outbound: []
// snippet
{
"inbound": [
{
"expressions": [
"req.Method == 'POST' || req.Method == 'PUT'",
"req.ContentLength >= 1000"
],
"name": "Block POST/PUT requests of excessive length",
"actions": [
{
"type": "custom-response",
"config": {
"status_code": 400,
"content": "Error: content length"
}
}
]
}
],
"outbound": []
}
Other
User agent filtering
We deliver tailored content to Microsoft Edge users by examining the
User-Agent
header for the case-insensitive string (?i)edg/
succeeded by
digits \d
. To see how this works in practice, explore the following
regex101 demonstration.
To ensure correct decoding from YAML/JSON, it's necessary to properly escape the
\d
sequence. In YAML, if your string is not enclosed in quotes, use a single
escape: \\d
. However, when your string is wrapped in quotes, either in YAML or
JSON, you need to double-escape: \\\\d
for accurate decoding.
- YAML
- JSON
# snippet
---
inbound:
- expressions:
- "'user-agent' in req.headers"
- "size(req.headers['user-agent'].filter(x,
x.matches('(?i).*Edg/\\\\d+.*'))) > 0"
actions:
- type: "custom-response"
config:
status_code: 200
content: "Hello Edge User!"
// snippet
{
"inbound": [
{
"expressions": [
"'user-agent' in req.headers",
"size(req.headers['user-agent'].filter(x, x.matches('(?i).*Edg/\\\\d+.*'))) > 0"
],
"actions": [
{
"type": "custom-response",
"config": {
"status_code": 200,
"content": "Hello Edge User!"
}
}
]
}
]
}
Deprecate an API version
By include a X-Api-Version
header in your API reference or developer documentation, you can quickly return a helpful error message, which encourages them to explore usage of the new version.
- YAML
- JSON
# snippet
---
inbound:
- expressions:
- "'2' in req.Headers['X-Api-Version']"
name: "Deprecate API v2"
actions:
- type: "custom-response"
config:
status_code: 400
content: "{\"error\":{\"message\":\"Version 2 of the API is no longer supported.
Use Version 3 instead.\"}}"
outbound: []
// snippet
{
"inbound": [
{
"expressions": [
"'2' in req.Headers['X-Api-Version']"
],
"name": "Deprecate API v2",
"actions": [
{
"type": "custom-response",
"config": {
"status_code": 400,
"content": "{\"error\":{\"message\":\"Version 2 of the API is no longer supported. Use Version 3 instead.\"}}"
}
}
]
}
],
"outbound": []
}
Manipulate request headers
Add new headers to requests to give your upstream service more context about the consumer, which in turn allows for richer functionality, such as localized languages and pricing.
- YAML
- JSON
# snippet
---
inbound:
- expressions: []
name: "Add headers to requests"
actions:
- type: "add-headers"
config:
headers:
is-ngrok: "1"
country: "${.ngrok.geo.country_code}"
outbound: []
// snippet
{
"inbound": [
{
"expressions": [],
"name": "Add headers to requests",
"actions": [
{
"type": "add-headers",
"config": {
"headers": {
"is-ngrok": "1",
"country": "${.ngrok.geo.country_code}"
}
}
}
]
}
],
"outbound": []
}
Add compression
Quickly ensure all JSON responses are compressed en route to your API consumer. If your upstream service already handles compression, ngrok skips this step.
- YAML
- JSON
# snippet
---
inbound: []
outbound:
- expressions: []
name: "Add compression"
actions:
- type: "compress-response"
config:
algorithms:
- "gzip"
- "br"
- "deflate"
- "compress"
// snippet
{
"inbound": [],
"outbound": [
{
"expressions": [],
"name": "Add compression",
"actions": [
{
"type": "compress-response",
"config": {
"algorithms": [
"gzip",
"br",
"deflate",
"compress"
]
}
}
]
}
]
}
Enforce TLS version
Prevent obsolete and potentially vulnerable browsers, SDKs, or CLI tools like curl
from attempting to access your API.
- YAML
- JSON
# snippet
---
inbound:
- expressions:
- "req.ClientTLS.Version > '1.3'"
name: "Reject requests using old TLS versions"
actions:
- type: "custom-response"
config:
status_code: 401
content: "Unauthorized: bad TLS version"
outbound: []
// snippet
{
"inbound": [
{
"expressions": [
"req.ClientTLS.Version > '1.3'"
],
"name": "Reject requests using old TLS versions",
"actions": [
{
"type": "custom-response",
"config": {
"status_code": 401,
"content": "Unauthorized: bad TLS version"
}
}
]
}
],
"outbound": []
}
Log unsuccessful events
Connect your API to ngrok's event logging system for smarter troubleshooting of your API gateway and upstream services.
- YAML
- JSON
# snippet
---
inbound: []
outbound:
- expressions:
- "res.StatusCode < '200' && res.StatusCode >= '300'"
name: "Log unsuccessful requests"
actions:
- type: "log"
config:
metadata:
message: "Unsuccessful request"
edge_id: "<YOUR-NGROK-DOMAIN>"
success: false
// snippet
{
"inbound": [],
"outbound": [
{
"expressions": [
"res.StatusCode < '200' && res.StatusCode >= '300'"
],
"name": "Log unsuccessful requests",
"actions": [
{
"type": "log",
"config": {
"metadata": {
"message": "Unsuccessful request",
"edge_id": "<YOUR-NGROK-DOMAIN>",
"success": false
}
}
}
]
}
]
}