Rate Limiting
This guide covers configuring rate limiting to protect License Monitor API from abuse and ensure fair usage.
Overview
Section titled “Overview”Rate limiting prevents:
- Denial of Service (DoS) attacks
- Brute force authentication attempts
- API abuse by misbehaving clients
- Resource exhaustion from runaway scripts
Configuration
Section titled “Configuration”Basic Rate Limiting
Section titled “Basic Rate Limiting”Configure rate limits in config.toml:
[api]enabled = truebind_address = "127.0.0.1"bind_port = 8080
# Rate limiting configurationrate_limit_enabled = truerate_limit_requests = 100 # Requests allowedrate_limit_window_seconds = 60 # Per time windowrate_limit_burst = 20 # Allow burst above limitConfiguration Options
Section titled “Configuration Options”| Option | Description | Default |
|---|---|---|
rate_limit_enabled | Enable rate limiting | true |
rate_limit_requests | Max requests per window | 60 |
rate_limit_window_seconds | Time window in seconds | 60 |
rate_limit_burst | Additional burst capacity | 10 |
rate_limit_by | Limit key (ip, api_key, user) | ip |
Rate Limit Strategies
Section titled “Rate Limit Strategies”Per-IP Limiting
Section titled “Per-IP Limiting”Default strategy - limit requests per client IP:
[api.rate_limit]enabled = truestrategy = "ip"requests = 100window_seconds = 60Per-API-Key Limiting
Section titled “Per-API-Key Limiting”Limit requests per API key (useful for multi-tenant):
[api.rate_limit]enabled = truestrategy = "api_key"requests = 1000window_seconds = 60Per-Endpoint Limiting
Section titled “Per-Endpoint Limiting”Different limits for different endpoints:
[api.rate_limit]enabled = truedefault_requests = 100default_window = 60
# Stricter limits for expensive operations[api.rate_limit.endpoints."/api/execute"]requests = 10window_seconds = 60
# More lenient for health checks[api.rate_limit.endpoints."/api/health"]requests = 300window_seconds = 60
# Stricter for authentication[api.rate_limit.endpoints."/api/auth"]requests = 5window_seconds = 300Rate Limit Headers
Section titled “Rate Limit Headers”License Monitor returns rate limit information in response headers:
Response Headers
Section titled “Response Headers”| Header | Description |
|---|---|
X-RateLimit-Limit | Maximum requests allowed |
X-RateLimit-Remaining | Requests remaining in window |
X-RateLimit-Reset | Unix timestamp when window resets |
Retry-After | Seconds to wait (on 429 response) |
Example Response
Section titled “Example Response”HTTP/1.1 200 OKX-RateLimit-Limit: 100X-RateLimit-Remaining: 95X-RateLimit-Reset: 1704067200Content-Type: application/json
{"status": "ok"}Rate Limit Exceeded Response
Section titled “Rate Limit Exceeded Response”HTTP/1.1 429 Too Many RequestsX-RateLimit-Limit: 100X-RateLimit-Remaining: 0X-RateLimit-Reset: 1704067200Retry-After: 45Content-Type: application/json
{ "error": "Rate limit exceeded", "code": "RATE_LIMIT_EXCEEDED", "message": "Too many requests. Please retry after 45 seconds.", "retryAfter": 45}Client Implementation
Section titled “Client Implementation”Handling Rate Limits
Section titled “Handling Rate Limits”async function fetchWithRateLimit(url: string, options: RequestInit = {}) { const response = await fetch(url, options);
// Check rate limit headers const remaining = parseInt(response.headers.get('X-RateLimit-Remaining') || '0'); const reset = parseInt(response.headers.get('X-RateLimit-Reset') || '0');
if (remaining < 10) { console.warn(`Rate limit warning: ${remaining} requests remaining`); }
// Handle 429 response if (response.status === 429) { const retryAfter = parseInt(response.headers.get('Retry-After') || '60'); console.warn(`Rate limited. Retrying after ${retryAfter} seconds`);
await new Promise(resolve => setTimeout(resolve, retryAfter * 1000)); return fetchWithRateLimit(url, options); }
return response;}Exponential Backoff
Section titled “Exponential Backoff”async function fetchWithBackoff( url: string, options: RequestInit = {}, maxRetries = 3) { for (let attempt = 0; attempt < maxRetries; attempt++) { const response = await fetch(url, options);
if (response.status !== 429) { return response; }
const retryAfter = parseInt(response.headers.get('Retry-After') || '0'); const backoff = retryAfter || Math.pow(2, attempt) * 1000;
console.log(`Rate limited. Waiting ${backoff}ms before retry ${attempt + 1}`); await new Promise(resolve => setTimeout(resolve, backoff)); }
throw new Error('Max retries exceeded');}Reverse Proxy Rate Limiting
Section titled “Reverse Proxy Rate Limiting”# Define rate limit zoneslimit_req_zone $binary_remote_addr zone=api_general:10m rate=10r/s;limit_req_zone $binary_remote_addr zone=api_auth:10m rate=1r/s;limit_req_zone $http_x_api_key zone=api_by_key:10m rate=100r/s;
server { # General API endpoints location /api/ { limit_req zone=api_general burst=20 nodelay; limit_req_status 429;
# Custom error page for rate limiting error_page 429 = @rate_limited;
proxy_pass http://license_monitor; }
# Stricter rate limiting for auth location /api/auth { limit_req zone=api_auth burst=5 nodelay; limit_req_status 429;
proxy_pass http://license_monitor; }
# Rate limit by API key for specific clients location /api/v2/ { limit_req zone=api_by_key burst=50 nodelay; limit_req_status 429;
proxy_pass http://license_monitor; }
location @rate_limited { default_type application/json; return 429 '{"error":"Rate limit exceeded","code":"RATE_LIMIT_EXCEEDED"}'; }}HAProxy
Section titled “HAProxy”frontend http_front bind *:80
# Define rate limit table stick-table type ip size 100k expire 1m store http_req_rate(1m)
# Track requests per IP http-request track-sc0 src
# Deny if rate exceeds 100 req/min http-request deny deny_status 429 if { sc_http_req_rate(0) gt 100 }
default_backend license_monitor
backend license_monitor server monitor1 127.0.0.1:8080Monitoring Rate Limits
Section titled “Monitoring Rate Limits”Metrics
Section titled “Metrics”# View rate limit metricscurl -H "X-API-Key: $API_KEY" \ http://localhost:8080/api/metrics | jq '.rateLimits'
# Output:# {# "totalRequests": 15234,# "limitedRequests": 45,# "currentWindowRequests": 23,# "topLimitedClients": [# {"ip": "192.168.1.100", "limitedCount": 20},# {"ip": "192.168.1.101", "limitedCount": 15}# ]# }Logging
Section titled “Logging”# Find rate-limited requests in logsgrep "RATE_LIMIT_EXCEEDED" /var/log/license-monitor/license_monitor.log
# Count rate-limited requests by IPgrep "RATE_LIMIT_EXCEEDED" /var/log/license-monitor/license_monitor.log | \ awk '{print $NF}' | sort | uniq -c | sort -rnAlerting
Section titled “Alerting”# Prometheus alerting rulegroups: - name: rate_limits rules: - alert: HighRateLimitHits expr: rate(license_monitor_rate_limit_exceeded_total[5m]) > 10 for: 5m labels: severity: warning annotations: summary: High rate of rate-limited requests description: More than 10 requests/sec are being rate limitedBest Practices
Section titled “Best Practices”Setting Appropriate Limits
Section titled “Setting Appropriate Limits”| Use Case | Requests/min | Burst |
|---|---|---|
| Dashboard refresh | 60 | 10 |
| Monitoring integration | 120 | 20 |
| CI/CD automation | 30 | 5 |
| API development | 300 | 50 |
Whitelisting Trusted Clients
Section titled “Whitelisting Trusted Clients”[api.rate_limit]enabled = truerequests = 100window_seconds = 60
# Whitelist monitoring systemswhitelist_ips = [ "10.0.1.50", # Prometheus "10.0.1.51" # Grafana]
# Higher limits for specific API keys[api.rate_limit.api_key_overrides]"monitoring-key-xyz" = { requests = 600, window_seconds = 60 }"ci-cd-key-abc" = { requests = 300, window_seconds = 60 }Graceful Degradation
Section titled “Graceful Degradation”[api.rate_limit]enabled = truerequests = 100window_seconds = 60
# Instead of 429, queue requests when near limitqueue_when_near_limit = truequeue_threshold = 90 # Queue when at 90% of limitqueue_max_wait_seconds = 30Next Steps
Section titled “Next Steps”- Secrets Management - Managing sensitive configuration
- API Authentication - Securing API access
- Network Security - Network-level protection