Rate Limiting

Configure API rate limiting

Rackd includes built-in rate limiting to prevent API abuse and ensure fair resource usage across clients.

Overview

Rate limiting is enabled by default (RATE_LIMIT_ENABLED=true). It tracks request rates per client IP and returns HTTP 429 (Too Many Requests) when limits are exceeded. Disable it with RATE_LIMIT_ENABLED=false for local development.

Configuration

Rate limiting is configured via environment variables:

# Rate limiting (enabled by default)
RATE_LIMIT_ENABLED=true

# Maximum requests per window (default: 100)
RATE_LIMIT_REQUESTS=100

# Time window for rate limiting (default: 1m)
RATE_LIMIT_WINDOW=1m

Example Configurations

Strict limits for public API:

RATE_LIMIT_ENABLED=true
RATE_LIMIT_REQUESTS=60
RATE_LIMIT_WINDOW=1m

Relaxed limits for internal use:

RATE_LIMIT_ENABLED=true
RATE_LIMIT_REQUESTS=1000
RATE_LIMIT_WINDOW=1m

Burst protection:

RATE_LIMIT_ENABLED=true
RATE_LIMIT_REQUESTS=10
RATE_LIMIT_WINDOW=10s

How It Works

Client Identification

Rate limits are applied per client, identified by IP address:

  1. Forwarded headers (only when TRUST_PROXY=true and the direct peer matches a TRUSTED_PROXIES CIDR) - uses the rightmost X-Forwarded-For entry (the address appended by your own proxy) or X-Real-IP
  2. RemoteAddr (default) - the direct socket address

Headers from untrusted peers are ignored, so clients cannot spoof X-Forwarded-For to rotate rate-limit buckets. Keying deliberately does not use the raw Authorization header: anonymous clients could otherwise rotate fake Bearer tokens for fresh buckets.

Token Bucket Algorithm

Rackd uses a token bucket algorithm:

  • Each client gets a bucket with RATE_LIMIT_REQUESTS tokens
  • Each request consumes one token
  • Buckets refill completely after RATE_LIMIT_WINDOW expires
  • Requests are blocked when bucket is empty

No Localhost Bypass

There is no localhost exemption: requests from 127.0.0.1 are limited like any other client. Health endpoints (/healthz, /readyz) are lightweight and not rate-limited in practice for typical monitoring intervals; raise RATE_LIMIT_REQUESTS if a local monitoring agent trips the limiter.

Response Headers

All API responses include rate limit headers:

X-RateLimit-Limit: 100
X-RateLimit-Remaining: 42
X-RateLimit-Reset: 2026-02-03T16:45:00Z

When rate limited:

HTTP/1.1 429 Too Many Requests
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 2026-02-03T16:45:00Z
Retry-After: 2026-02-03T16:45:00Z
Content-Type: application/json

{
  "error": "Rate limit exceeded",
  "code": "RATE_LIMIT_EXCEEDED"
}

Usage Examples

Checking Rate Limit Status

curl -i http://localhost:8080/api/devices

HTTP/1.1 200 OK
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 99
X-RateLimit-Reset: 2026-02-03T16:45:00Z

Handling Rate Limits

# Make requests until rate limited
for i in {1..150}; do
  curl -s -o /dev/null -w "%{http_code}\n" http://localhost:8080/api/devices
done

# Output:
# 200
# 200
# ...
# 200  (100th request)
# 429  (101st request - rate limited)
# 429
# ...

Using API Keys

# Different API keys have separate rate limits
curl -H "Authorization: Bearer key1" http://localhost:8080/api/devices
curl -H "Authorization: Bearer key2" http://localhost:8080/api/devices

Best Practices

For API Consumers

  1. Monitor headers - Check X-RateLimit-Remaining to avoid hitting limits
  2. Implement backoff - Respect Retry-After header when rate limited
  3. Use API keys - Get dedicated rate limits instead of sharing IP-based limits
  4. Batch operations - Use bulk endpoints to reduce request count

For Administrators

  1. Keep it enabled - It is on by default; leave it on in production
  2. Monitor usage - Check logs for rate limit events
  3. Adjust limits - Tune based on actual usage patterns
  4. Use reverse proxy - Consider nginx/Caddy rate limiting for additional protection

Monitoring

Rate limit events are logged at DEBUG level:

level=debug msg="Rate limit exceeded" client=192.168.1.100 path=/api/devices

To see rate limit logs:

LOG_LEVEL=debug rackd server

Performance

Rate limiting has minimal overhead:

  • Memory: ~100 bytes per active client
  • CPU: Negligible (simple token bucket operations)
  • Cleanup: Automatic cleanup of inactive clients every 2x window duration

Limitations

  1. In-memory only - Rate limits reset on server restart
  2. Single instance - No coordination across multiple Rackd instances
  3. No per-endpoint limits - All endpoints share the same limit

For multi-instance deployments, consider using a reverse proxy (nginx, Caddy) or API gateway for distributed rate limiting.

Troubleshooting

Rate limiting not working

Check configuration:

# Verify rate limiting is enabled
curl http://localhost:8080/api/config | jq .rate_limit

# Check server logs for rate limit configuration
rackd server
# Output should include:
# level=info msg="Rate limiting enabled" requests=100 window=1m0s

Localhost being rate limited

This should never happen. If it does, check:

  • Server is binding to correct address
  • Request is actually coming from localhost
  • No proxy is rewriting RemoteAddr

Different rate limits than expected

Rate limits are per-client:

  • API key clients get separate limits
  • IP-based clients share limits per IP
  • Localhost always bypasses limits

Security Considerations

  1. Not a DDoS solution - Rate limiting helps but isn’t sufficient for DDoS protection
  2. IP spoofing - Set TRUST_PROXY=true together with TRUSTED_PROXIES (e.g. 127.0.0.1); forwarded headers are honored only from those peers
  3. API key sharing - Shared keys share rate limits
  4. Localhost bypass - Ensure server isn’t exposed with localhost access

See Also