Rate_limiting
Chapter 22: Rate Limiting & Load Balancing
Section titled “Chapter 22: Rate Limiting & Load Balancing”Overview
Section titled “Overview”Rate limiting protects your RPC nodes from abuse and ensures fair resource allocation.
22.1 Why Rate Limit?
Section titled “22.1 Why Rate Limit?”- Prevent DDoS attacks
- Protect node resources
- Ensure fair access
- Manage costs
22.2 Geth’s Built-In Limits
Section titled “22.2 Geth’s Built-In Limits”geth has no --http.ratelimit / --http.ratelimitburst / --ws.ratelimit / --ws.ratelimitburst flags — per-client rate limiting is not a geth feature and belongs at the reverse proxy (22.3). What geth does provide are structural caps:
# Caps on JSON-RPC batch requests (defaults: 1000 requests / 25 MB per batch)geth --rpc.batch-request-limit 1000 --rpc.batch-response-max-size 25000000
# Per-call execution guards on the expensive methodsgeth --rpc.gascap 50000000 # gas cap for eth_call / eth_estimateGas --rpc.txfeecap 100 # tx fee cap (in ether) for RPC-submitted txs --rpc.evmtimeout 5s # per-call EVM execution timeoutThese stop a single oversized batch or an infinite eth_call loop from stalling the node — but they do not rate-limit by client IP. That is the job of the reverse proxy.
22.3 Nginx Rate Limiting
Section titled “22.3 Nginx Rate Limiting”http { # Define rate limit zone limit_req_zone $binary_remote_addr zone=api_limit:10m rate=100r/s; limit_req_zone $binary_remote_addr zone=write_limit:10m rate=10r/s;
server { location / { # Apply rate limit limit_req zone=api_limit burst=200 nodelay; }
location /eth_sendTransaction { limit_req zone=write_limit burst=50 nodelay; } }}22.4 Load Balancing
Section titled “22.4 Load Balancing”HAProxy Configuration
Section titled “HAProxy Configuration”frontend eth_rpc_https bind :443 ssl crt /etc/ssl/certs/server.pem default_backend geth_nodes
backend geth_nodes balance roundrobin option httpchk GET /health
server geth1 10.0.0.1:8545 check inter 5s rise 2 fall 3 server geth2 10.0.0.2:8545 check inter 5s rise 2 fall 3 server geth3 10.0.0.3:8545 check inter 5s rise 2 fall 322.5 Rate Limiting Algorithms
Section titled “22.5 Rate Limiting Algorithms”| Algorithm | How it works | Behavior under burst | Typical use |
|---|---|---|---|
| Fixed window | Counter resets every N seconds | Burst allowed at window edge | Simple per-IP caps |
| Sliding window | Counter over a rolling time window | Smooth but more state | Better fairness |
| Token bucket | Bucket fills at rate r, capacity b; each request takes a token | Limited burst b, then throttled | Most common for RPC (Nginx limit_req / proxy) |
| Leaky bucket | Requests queue and drain at fixed rate | Never bursts; drops/queues overflow | Protecting slow backends |
Nginx limit_req_zone is the standard token-bucket implementation for RPC: rate= sets tokens/sec, burst= sets the bucket capacity.
Choosing Limits
Section titled “Choosing Limits”| Endpoint | Reasoning |
|---|---|
eth_* reads (getBalance, getBlockByNumber) | Cheap; allow high rates |
eth_call / eth_estimateGas | Expensive (executes code); the main abuse vector — cap hard |
eth_getLogs | Can scan huge ranges; cap fromBlock-toBlock span at the proxy if possible |
eth_sendRawTransaction | Volume-limited by the mempool; rate-limit to prevent spam |
debug_* / trace_* | Never expose publicly — they are CPU/disk hogs |
22.6 Load Balancing Strategies for RPC
Section titled “22.6 Load Balancing Strategies for RPC”| Strategy | Behavior | Best for |
|---|---|---|
| Round-robin | Distributes evenly | Uniform workloads, HTTP only |
| Least-connections | Sends to the least busy backend | Mixed workloads, WS |
| IP hash / sticky | Pins a client to one backend | WebSocket subscriptions |
| Weighted | More traffic to bigger nodes | Heterogeneous hardware |
Health Checks Behind the LB
Section titled “Health Checks Behind the LB”The health check must test the actual RPC interface, not just TCP:
# /health on each backend via a small script (or haproxy http-check)curl -s -X POST http://10.0.0.1:8545 \ -H "Content-Type: application/json" \ -d '{"jsonrpc":"2.0","method":"eth_blockNumber","params":[],"id":1}'# healthy when a valid hex result is returned# haproxy.cfg — least-conn + RPC health check + failoverfrontend eth_rpc_https bind :443 ssl crt /etc/ssl/certs/server.pem default_backend geth_nodes
backend geth_nodes balance leastconn option httpchk POST / HTTP/1.1\r\nContent-Type:\ application/json\r\n\r\n{"jsonrpc":"2.0","method":"eth_blockNumber","params":[],"id":1} http-check expect string 0x server geth1 10.0.0.1:8545 check inter 5s rise 2 fall 3 server geth2 10.0.0.2:8545 check inter 5s rise 2 fall 3 server geth3 10.0.0.3:8545 check inter 5s rise 2 fall 3Failover Semantics
Section titled “Failover Semantics”- Sticky sessions: WS subscriptions must not migrate mid-connection; on backend failure the client reconnects and resubscribes (which is why the app layer needs reconnect logic — see Chapter 21).
- Retries: non-idempotent calls (
eth_sendRawTransaction— ambiguous outcome after a timeout) should be retried carefully: re-send with the same tx hash is safe (same-tx dedup is handled by the node), but retryingeth_callis fine. Never blindly retryeth_estimateGasafter a timeout. - Read-after-write: eth_sendRawTransaction may be routed to a node that hasn’t seen the tx in its pool yet — keep the tx hash and expect
eth_getTransactionReceiptto be empty until inclusion.
22.7 Abuse Detection & Protection
Section titled “22.7 Abuse Detection & Protection”┌─────────────────────────────────────────────────────────────────┐│ ABUSE DETECTION LAYERS │├─────────────────────────────────────────────────────────────────┤│ ││ 1. PER-IP CAPS — token bucket (Nginx / reverse proxy) ││ 2. PER-KEY/PROJECT — API-key quotas (Infura-style) ││ 3. COST-BASED — heavy methods (eth_call, getLogs) low ││ 4. BEHAVIORAL — rapid connect/disconnect, parallel ││ bursts, error floods → temporary ban ││ 5. TRAFFIC SHAPING — WAF/rate-limit at edge (Cloudflare, ││ AWS WAF) ││ │└─────────────────────────────────────────────────────────────────┘Concrete signals to watch (see Chapter 36 metrics):
increase(rpc_duration_count{...}[5m])spike from a single IP → query blasts (eth_getLogsover wide ranges).- WS connect/disconnect churn → subscription abuse (leaked WS clients).
eth_sendRawTransactionflood from one key → mempool spam.- Error-rate explosion (4xx/5xx) → protocol misuse or DDoS.
Counter-measures: per-IP ban after N violations at the proxy, API keys with per-project quotas, and heavy-method caps. Never put debug_/trace_ behind a public proxy without authentication.
22.8 Tracing RPC Traffic
Section titled “22.8 Tracing RPC Traffic”Observing who calls what, how often, and how slow is what turns a proxy into an RPC platform:
- Access logs with latency: log every JSON-RPC method, status, duration, and client IP (Nginx
log_formator HAProxy logs). - Method latency histograms: capture per-method p50/p99 —
eth_callandeth_getLogswill dominate; this guides rate-limit tuning. - Distributed tracing (OpenTelemetry): propagate a
traceparentheader from your gateway into downstream services and attach it to node logs; correlate a slow client request with node-side activity (trie reads, EVM execution). - Request IDs: require clients to send
idand echo it in responses; it makes debugging “which request timed out” trivial.
# nginx — capture per-method timing into Prometheus-friendly logslog_format rpc '$remote_addr - $request_time - $upstream_response_time - ' '"$request_body"';access_log /var/log/nginx/rpc-access.log rpc;Summary
Section titled “Summary”- Rate limit at application level (Geth)
- Use Nginx for additional protection
- Load balance across multiple nodes
- Heavy methods (
eth_call,eth_getLogs,debug_*) need tighter caps than cheap reads - WS backends need sticky sessions; clients need reconnect/resubscribe logic
- Abuse detection = per-IP caps + per-key quotas + cost-based limits + behavioral signals
- Trace every request (method, latency, IP) to tune limits and debug incidents
Last Updated: 2026-10-07