Skip to main content
Ory Kratos includes built-in rate limiting to protect against brute force attacks, credential stuffing, and API abuse.

Rate limit buckets

Kratos uses multiple rate limit buckets with different thresholds based on the endpoint’s sensitivity:

Low

60 requests/minuteFor high-frequency operations like session checks

Medium

30 requests/minuteFor standard self-service operations

High

10 requests/minuteFor sensitive operations like admin API

Endpoint rate limits

Public API

GET  /sessions/whoami
GET  /sessions
GET  /.well-known/ory/webauthn.js
These endpoints are called frequently and have higher limits.

Admin API

All Admin API endpoints use the high sensitivity bucket (10 requests/minute):
POST   /admin/identities
GET    /admin/identities
DELETE /admin/identities/{id}
GET    /admin/sessions
...

Rate limit headers

Kratos includes rate limit information in response headers:
HTTP/1.1 200 OK
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 59
X-RateLimit-Reset: 1640000000
X-RateLimit-Limit
number
Maximum requests allowed in the time window
X-RateLimit-Remaining
number
Number of requests remaining in current window
X-RateLimit-Reset
number
Unix timestamp when the rate limit resets

Rate limit responses

When a rate limit is exceeded:
HTTP/1.1 429 Too Many Requests
Retry-After: 42
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1640000042

{
  "error": {
    "id": "rate_limit_exceeded",
    "code": 429,
    "message": "Rate limit exceeded. Please try again in 42 seconds.",
    "reason": "Too many requests"
  }
}
Retry-After
number
Seconds to wait before retrying

Client-side handling

JavaScript example

async function makeRequest(url, options = {}) {
  const response = await fetch(url, {
    ...options,
    credentials: 'include'
  });
  
  // Check rate limit headers
  const remaining = parseInt(response.headers.get('X-RateLimit-Remaining'));
  const reset = parseInt(response.headers.get('X-RateLimit-Reset'));
  
  if (response.status === 429) {
    const retryAfter = parseInt(response.headers.get('Retry-After'));
    console.warn(`Rate limited. Retry after ${retryAfter} seconds`);
    
    // Wait and retry
    await new Promise(resolve => setTimeout(resolve, retryAfter * 1000));
    return makeRequest(url, options);
  }
  
  // Warn when approaching limit
  if (remaining < 5) {
    console.warn(`Approaching rate limit. ${remaining} requests remaining`);
  }
  
  return response;
}

// Usage
const response = await makeRequest('/self-service/login/browser');

Exponential backoff

async function fetchWithRetry(url, options = {}, maxRetries = 3) {
  for (let i = 0; i < maxRetries; i++) {
    const response = await fetch(url, options);
    
    if (response.status !== 429) {
      return response;
    }
    
    // Exponential backoff: 2^i seconds
    const delay = Math.pow(2, i) * 1000;
    console.log(`Rate limited. Retrying in ${delay/1000}s...`);
    await new Promise(resolve => setTimeout(resolve, delay));
  }
  
  throw new Error('Max retries exceeded');
}

Additional protection

Reverse proxy rate limiting

Add additional rate limiting at the reverse proxy level:
http {
    # Define rate limit zones
    limit_req_zone $binary_remote_addr zone=kratos_public:10m rate=10r/s;
    limit_req_zone $binary_remote_addr zone=kratos_admin:10m rate=1r/s;
    
    server {
        # Public API
        location / {
            limit_req zone=kratos_public burst=20 nodelay;
            limit_req_status 429;
            
            proxy_pass http://kratos:4433;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        }
        
        # Admin API
        location /admin {
            limit_req zone=kratos_admin burst=5 nodelay;
            limit_req_status 429;
            
            # Only allow from internal network
            allow 10.0.0.0/8;
            deny all;
            
            proxy_pass http://kratos:4434;
        }
    }
}

WAF and DDoS protection

Use a Web Application Firewall for advanced protection:

Cloudflare

  • Rate limiting rules
  • Bot detection
  • DDoS protection
  • Challenge pages

AWS WAF

  • IP rate limiting
  • Geo-blocking
  • Bot control
  • Custom rules

Brute force protection

Account lockout

Implement account lockout after failed attempts:
selfservice:
  flows:
    login:
      after:
        password:
          hooks:
            - hook: revoke_active_sessions

CAPTCHA integration

Add CAPTCHA for suspicious activity:
// Detect rate limit and show CAPTCHA
if (response.status === 429 || failedAttempts > 3) {
  await showCaptcha();
}
Popular CAPTCHA solutions:
  • reCAPTCHA v3 (Google)
  • hCaptcha
  • Cloudflare Turnstile
  • FriendlyCaptcha

Monitoring

Track rate limit events

Monitor rate limit metrics:
# Prometheus metrics (if enabled)
kratos_http_requests_total{status="429"}
kratos_http_request_duration_seconds{status="429"}

Alert on suspicious activity

# Example Prometheus alert
groups:
  - name: kratos_security
    rules:
      - alert: HighRateLimitHitRate
        expr: rate(kratos_http_requests_total{status="429"}[5m]) > 10
        for: 5m
        annotations:
          summary: "High rate limit hit rate detected"
          description: "{{ $value }} rate limit errors per second"

Log analysis

Analyze logs for patterns:
# Find IPs hitting rate limits
grep '"status":429' kratos.log | jq -r '.remote_addr' | sort | uniq -c | sort -rn

# Top rate-limited endpoints
grep '"status":429' kratos.log | jq -r '.path' | sort | uniq -c | sort -rn

IP allowlisting

Exempt trusted IPs from rate limiting (configure at reverse proxy level):
geo $limit {
    default 1;
    10.0.0.0/8 0;      # Internal network
    203.0.113.0/24 0;  # Office IP range
}

map $limit $limit_key {
    0 "";
    1 $binary_remote_addr;
}

limit_req_zone $limit_key zone=kratos:10m rate=10r/s;

Best practices

1

Respect rate limits

Check X-RateLimit-* headers and implement exponential backoff in clients.
2

Layer defenses

Combine Kratos rate limiting with reverse proxy limits and WAF rules.
3

Monitor and alert

Track rate limit hits and investigate unusual patterns.
4

Document limits

Clearly communicate rate limits to API consumers.
5

Protect admin API

Use stricter limits and IP restrictions for admin endpoints.

Troubleshooting

Solutions:
  • Increase limits at reverse proxy level for trusted IPs
  • Implement caching for frequently accessed data
  • Optimize client-side code to reduce requests
  • Use batch operations where possible
Check:
  • Kratos is receiving correct client IP (check X-Forwarded-For)
  • No reverse proxy caching bypassing rate limits
  • Clock synchronization (rate limit windows are time-based)
Solutions:
  • Use user-based rate limiting (requires authentication)
  • Implement CAPTCHA instead of hard blocks
  • Allowlist known shared IP ranges (offices, VPNs)

Next steps

Best practices

Complete security hardening guide

Encryption

Learn about data encryption

Build docs developers (and LLMs) love