11 KiB
Rate Limiting Guide
LogWisp provides configurable rate limiting to protect against abuse, prevent resource exhaustion, and ensure fair access to log streams.
How Rate Limiting Works
LogWisp uses a token bucket algorithm for smooth, burst-tolerant rate limiting:
- Each client (or globally) gets a bucket with a fixed capacity
- Tokens are added to the bucket at a configured rate
- Each request consumes one token
- If no tokens are available, the request is rejected
- The bucket can accumulate tokens up to its capacity for bursts
Configuration
Basic Configuration
[streams.httpserver.rate_limit]
enabled = true # Enable rate limiting
requests_per_second = 10.0 # Token refill rate
burst_size = 20 # Maximum tokens (bucket capacity)
limit_by = "ip" # "ip" or "global"
Complete Options
[streams.httpserver.rate_limit]
# Core settings
enabled = true # Enable/disable rate limiting
requests_per_second = 10.0 # Token generation rate (float)
burst_size = 20 # Token bucket capacity
# Limiting strategy
limit_by = "ip" # "ip" or "global"
# Connection limits
max_connections_per_ip = 5 # Max concurrent connections per IP
max_total_connections = 100 # Max total concurrent connections
# Response configuration
response_code = 429 # HTTP status code when limited
response_message = "Rate limit exceeded" # Error message
# Same options available for TCP
[streams.tcpserver.rate_limit]
enabled = true
requests_per_second = 5.0
burst_size = 10
limit_by = "ip"
Limiting Strategies
Per-IP Limiting (Default)
Each client IP address gets its own token bucket:
[streams.httpserver.rate_limit]
enabled = true
limit_by = "ip"
requests_per_second = 10.0
burst_size = 20
Use cases:
- Fair access for multiple users
- Prevent single client from monopolizing resources
- Public-facing endpoints
Example behavior:
- Client A: Can make 10 req/sec
- Client B: Also can make 10 req/sec
- Total: Up to 10 × number of clients
Global Limiting
All clients share a single token bucket:
[streams.httpserver.rate_limit]
enabled = true
limit_by = "global"
requests_per_second = 50.0
burst_size = 100
Use cases:
- Protect backend resources
- Control total system load
- Internal services with known clients
Example behavior:
- All clients combined: 50 req/sec max
- One aggressive client can consume all tokens
Connection Limits
In addition to request rate limiting, you can limit concurrent connections:
Per-IP Connection Limit
[streams.httpserver.rate_limit]
max_connections_per_ip = 5 # Each IP can have max 5 connections
Behavior:
- Prevents connection exhaustion attacks
- Limits resource usage per client
- Checked before rate limits
Total Connection Limit
[streams.httpserver.rate_limit]
max_total_connections = 100 # Max 100 connections total
Behavior:
- Protects server resources
- Prevents memory exhaustion
- Global limit across all IPs
Response Behavior
HTTP Responses
When rate limited, HTTP clients receive:
{
"error": "Rate limit exceeded",
"retry_after": "60"
}
With these headers:
- Status code: 429 (default) or configured value
- Content-Type: application/json
Configure custom responses:
[streams.httpserver.rate_limit]
response_code = 503 # Service Unavailable
response_message = "Server overloaded, please retry later"
TCP Behavior
TCP connections are silently dropped when rate limited:
- No error message sent
- Connection immediately closed
- Prevents information leakage
Configuration Examples
Light Protection
For internal or trusted environments:
[streams.httpserver.rate_limit]
enabled = true
requests_per_second = 50.0
burst_size = 100
limit_by = "ip"
Moderate Protection
For semi-public endpoints:
[streams.httpserver.rate_limit]
enabled = true
requests_per_second = 10.0
burst_size = 30
limit_by = "ip"
max_connections_per_ip = 5
max_total_connections = 200
Strict Protection
For public or sensitive endpoints:
[streams.httpserver.rate_limit]
enabled = true
requests_per_second = 2.0
burst_size = 5
limit_by = "ip"
max_connections_per_ip = 2
max_total_connections = 50
response_code = 503
response_message = "Service temporarily unavailable"
Debug/Development
Disable for testing:
[streams.httpserver.rate_limit]
enabled = false
Use Case Scenarios
Public Log Viewer
Prevent abuse while allowing legitimate use:
[[streams]]
name = "public-logs"
[streams.httpserver]
enabled = true
port = 8080
[streams.httpserver.rate_limit]
enabled = true
requests_per_second = 5.0 # 5 new connections per second
burst_size = 10 # Allow short bursts
limit_by = "ip"
max_connections_per_ip = 3 # Max 3 streams per user
max_total_connections = 100
Internal Monitoring
Protect against accidental overload:
[[streams]]
name = "internal-metrics"
[streams.httpserver]
enabled = true
port = 8081
[streams.httpserver.rate_limit]
enabled = true
requests_per_second = 100.0 # High limit for internal use
burst_size = 200
limit_by = "global" # Total system limit
max_total_connections = 500
High-Security Audit Logs
Very restrictive access:
[[streams]]
name = "audit"
[streams.httpserver]
enabled = true
port = 8443
[streams.httpserver.rate_limit]
enabled = true
requests_per_second = 0.5 # 1 request every 2 seconds
burst_size = 2
limit_by = "ip"
max_connections_per_ip = 1 # Single connection only
max_total_connections = 10
response_code = 403 # Forbidden (hide rate limit)
response_message = "Access denied"
Multi-Tenant Service
Different limits per stream:
# Free tier
[[streams]]
name = "logs-free"
[streams.httpserver.rate_limit]
enabled = true
requests_per_second = 1.0
burst_size = 5
max_connections_per_ip = 1
# Premium tier
[[streams]]
name = "logs-premium"
[streams.httpserver.rate_limit]
enabled = true
requests_per_second = 50.0
burst_size = 100
max_connections_per_ip = 10
Monitoring Rate Limits
Status Endpoint
Check rate limit statistics:
curl http://localhost:8080/status | jq '.server.features.rate_limit'
Response includes:
{
"enabled": true,
"total_requests": 15234,
"blocked_requests": 89,
"active_ips": 12,
"total_connections": 8,
"config": {
"requests_per_second": 10,
"burst_size": 20,
"limit_by": "ip"
}
}
Debug Logging
Enable debug logs to see rate limit decisions:
logwisp --log-level debug
Look for messages:
Request rate limited ip=192.168.1.100
Connection limit exceeded ip=192.168.1.100 connections=5 limit=5
Created new IP limiter ip=192.168.1.100 total_ips=3
Testing Rate Limits
Test Script
#!/bin/bash
# Test rate limiting behavior
URL="http://localhost:8080/stream"
PARALLEL=10
DURATION=10
echo "Testing rate limits..."
echo "URL: $URL"
echo "Parallel connections: $PARALLEL"
echo "Duration: ${DURATION}s"
echo
# Function to connect and count lines
test_connection() {
local id=$1
local count=0
local start=$(date +%s)
while (( $(date +%s) - start < DURATION )); do
if curl -s -N --max-time 1 "$URL" >/dev/null 2>&1; then
((count++))
echo "[$id] Connected successfully (total: $count)"
else
echo "[$id] Rate limited!"
fi
sleep 0.1
done
}
# Run parallel connections
for i in $(seq 1 $PARALLEL); do
test_connection $i &
done
wait
echo "Test complete"
Load Testing
Using Apache Bench (ab):
# Test burst handling
ab -n 100 -c 20 http://localhost:8080/status
# Test sustained load
ab -n 1000 -c 5 -r http://localhost:8080/status
Using curl:
# Test connection limit
for i in {1..10}; do
curl -N http://localhost:8080/stream &
done
Tuning Guidelines
Setting requests_per_second
Consider:
- Expected legitimate traffic
- Server capacity
- Client retry behavior
Formula: requests_per_second = expected_clients × requests_per_client
Setting burst_size
General rule: burst_size = 2-3 × requests_per_second
Examples:
10 req/s → burst_size = 20-301 req/s → burst_size = 3-5100 req/s → burst_size = 200-300
Connection Limits
Based on available memory:
- Each HTTP connection: ~1-2MB
- Each TCP connection: ~0.5-1MB
Formula: max_connections = available_memory / memory_per_connection
Common Issues
"All requests blocked"
Check if:
- Rate limits too strict
- Burst size too small
- Using global limiting with many clients
"Memory growth"
Possible causes:
- No connection limits set
- Slow clients holding connections
- Too high burst_size
Solutions:
max_connections_per_ip = 5
max_total_connections = 100
"Legitimate users blocked"
Consider:
- Increasing burst_size for short spikes
- Using per-IP instead of global limiting
- Different streams for different user tiers
Security Considerations
Information Disclosure
Rate limit responses can reveal information:
# Default - informative
response_code = 429
response_message = "Rate limit exceeded"
# Security-focused - generic
response_code = 503
response_message = "Service unavailable"
# High security - misleading
response_code = 403
response_message = "Forbidden"
DDoS Protection
Rate limiting helps but isn't complete DDoS protection:
- Use with firewall rules
- Consider CDN/proxy rate limiting
- Monitor for distributed attacks
Resource Exhaustion
Protect against:
- Connection exhaustion
- Memory exhaustion
- CPU exhaustion
# Comprehensive protection
[streams.httpserver.rate_limit]
enabled = true
requests_per_second = 10.0
burst_size = 20
max_connections_per_ip = 5
max_total_connections = 100
limit_by = "ip"
Best Practices
- Start Conservative: Begin with strict limits and relax as needed
- Monitor Statistics: Use
/statusendpoint to track behavior - Test Thoroughly: Verify limits work as expected under load
- Document Limits: Make rate limits clear to users
- Provide Retry Info: Help clients implement proper retry logic
- Different Tiers: Consider different limits for different user types
- Regular Review: Adjust limits based on usage patterns
See Also
- Configuration Guide - Complete configuration reference
- Security Best Practices - Security hardening
- Performance Tuning - Optimization guidelines
- Troubleshooting - Common issues