Ethereum RPC providers enforce rate limits based on per-method compute-unit costs and concurrency windows. Heavy methods like eth_getLogs over wide ranges and archive-state calls dominate. Mitigation includes batching, caching, narrowing ranges, using WebSocket subscriptions, exponential backoff with Retry-After, and moving sustained load to dedicated endpoints.
Direct Answer: Why You Get 429 Errors on Ethereum RPC
If you are seeing HTTP 429 Too Many Requests from an Ethereum RPC endpoint, it means your application has exceeded the provider's rate limit. Unlike a timeout (which indicates the server did not respond in time) or a JSON-RPC error (which is a valid response with an error code), a 429 is an HTTP-level response that tells you to slow down. Providers meter usage by assigning a compute-unit cost to each JSON-RPC method, and they enforce limits on both requests per second and concurrent requests. Heavy methods like eth_getLogs over a large block range or eth_call on archive nodes can consume hundreds of compute units, quickly exhausting your quota.
The key to resolving 429s is to understand the cost model and adjust your client behavior. This article explains the mechanism, provides a reproducible curl demonstration, and outlines standardized mitigation strategies. For a broader overview of 429 handling, see our generic RPC 429 handling guide.
How Ethereum RPC Providers Meter Usage: Compute Units and Windows
Ethereum RPC providers, including OnFinality, typically use a compute-unit (CU) system to price API access. Each method has a weight based on the computational resources it requires. For example, a simple eth_blockNumber might cost 1 CU, while eth_getLogs over a wide range can cost hundreds or thousands of CUs. Providers also enforce two types of windows: a per-second request limit (e.g., 100 requests per second) and a concurrency limit (e.g., 10 simultaneous requests). When you exceed either, you receive a 429.
The exact CU values are not standardized across providers; they are documented by each provider. For instance, Alchemy and Infura publish their own CU tables. OnFinality's pricing page outlines our approach. The table below shows representative ranges based on documented behavior from major providers; treat these as estimates, not universal constants.
- eth_blockNumber: 1 CU (low)
- eth_getBalance: 2-10 CU (low to medium)
- eth_call: 10-50 CU (medium, depends on complexity)
- eth_getLogs (single block): 10-50 CU (medium)
- eth_getLogs (wide range, e.g., 1000 blocks): 500-2000+ CU (high)
- debug_traceTransaction: 100-500 CU (high, archive only)
- eth_getStorageAt (archive): 20-100 CU (medium to high)
Why eth_getLogs and Archive Methods Dominate
eth_getLogs is notorious for consuming high compute units because it scans a range of blocks and filters logs. A wide range (e.g., 10,000 blocks) can cause the node to process a massive amount of data, leading to high CPU and I/O usage. Similarly, archive methods like eth_getBalance at historical blocks or debug_traceTransaction require the node to replay state, which is computationally expensive. These methods are often the primary cause of 429s in production applications.
To mitigate, you should narrow block ranges, use indexed filters, and cache results. For example, instead of querying logs for the last 10,000 blocks every minute, you can query only the latest block and store the results in a local database. For archive data, consider using a dedicated archive endpoint or a provider that offers cost-effective archive access, such as OnFinality's Ethereum network page.
429 vs Timeout vs JSON-RPC Error Codes
It's important to distinguish between a 429, a timeout, and a JSON-RPC error. A 429 is an HTTP status code returned by the provider's gateway, indicating that you have exceeded the rate limit. A timeout occurs when the server does not respond within a specified time (e.g., 30 seconds), often due to a heavy request. A JSON-RPC error is a valid HTTP 200 response with an error object in the body, such as 'execution reverted' or 'method not found'. These are different failure modes and require different handling.
For example, a 429 should trigger backoff and retry, while a timeout might require reducing the request complexity. A JSON-RPC error might indicate a bug in your contract call. Understanding these differences helps you implement robust error handling.
Reproducible Demonstration: 200 vs 429 with curl
The following curl commands demonstrate a successful request and a rate-limited request. Replace YOUR_API_KEY with your OnFinality API key. The first command sends a simple eth_blockNumber request, which should return a 200 with a JSON result. The second command sends a burst of requests in a loop to trigger a 429; adjust the loop count to exceed your plan's limit.
Note: The exact rate limit depends on your plan. OnFinality's free tier allows a certain number of requests per second; check your pricing for details. To see a 429, you may need to run the loop many times or use a heavy method like eth_getLogs.
curl -X POST https://ethereum-rpc.publicnode.com \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","method":"eth_blockNumber","params":[],"id":1}'
# Expected: HTTP/1.1 200 OK with a JSON result like {"jsonrpc":"2.0","result":"0x...","id":1}
# To trigger a 429, run a loop (adjust count to exceed your limit):
for i in $(seq 1 100); do
curl -s -o /dev/null -w "%{http_code}\n" \
-X POST https://ethereum-rpc.publicnode.com \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","method":"eth_blockNumber","params":[],"id":1}'
done
# Expected: some responses will be 429 if you exceed the rate limit.Expected Results and How to Verify
When you run the first curl, you should see a 200 status and a JSON response with the current block number. When you run the loop, you may see a mix of 200 and 429 responses. The 429 response body typically includes a message like 'Too Many Requests' and may include a Retry-After header. To verify your rate limit, check your provider's dashboard or documentation. For OnFinality, you can monitor usage in the API service console.
If you don't see a 429, increase the loop count or use a heavier method like eth_getLogs with a wide range. Remember that rate limits are per API key, so using a public endpoint may have different limits.
Standardized Mitigation Strategies
To avoid 429s, implement the following strategies:
Batching: Combine multiple requests into a single JSON-RPC batch. This reduces the number of HTTP requests and can lower CU consumption if the provider offers batch discounts. See our batching best-practices sibling for details.
Caching: Cache balances, logs, and other data locally. For example, cache eth_getBalance results for a short TTL (e.g., 15 seconds) to avoid repeated calls.
Narrow block ranges: For eth_getLogs, query smaller ranges (e.g., 100 blocks) and paginate if needed.
Use WebSocket subscriptions: Instead of polling eth_getBalance or eth_blockNumber, use eth_subscribe to receive push updates. This reduces request count.
Exponential backoff with Retry-After: When you receive a 429, wait for the Retry-After header (if present) or use exponential backoff (e.g., 1s, 2s, 4s) before retrying.
Move sustained load to a dedicated endpoint: If you have high-volume production traffic, consider a dedicated endpoint with higher limits. OnFinality offers dedicated endpoints for this purpose.
// Example: Exponential backoff with Retry-After in Node.js
const axios = require('axios');
async function rpcCall(method, params) {
const url = 'https://ethereum-rpc.publicnode.com';
const data = { jsonrpc: '2.0', method, params, id: 1 };
let retries = 0;
while (true) {
try {
const response = await axios.post(url, data);
return response.data;
} catch (error) {
if (error.response && error.response.status === 429) {
const retryAfter = parseInt(error.response.headers['retry-after'] || '0');
const delay = retryAfter > 0 ? retryAfter * 1000 : Math.min(1000 * 2 ** retries, 10000);
await new Promise(resolve => setTimeout(resolve, delay));
retries++;
} else {
throw error;
}
}
}
}
// Usage: rpcCall('eth_blockNumber', [])Common Failures and Fixes
One common failure is not handling 429s in code, leading to crashes or data loss. Another is using a single API key for both development and production, causing production to hit limits. A third is polling too frequently; for example, polling eth_blockNumber every second when the block time is 12 seconds.
Fixes include: implementing retry logic with backoff, using separate API keys for different environments, and reducing polling frequency. Also, consider using WebSocket subscriptions for real-time data. For more advanced troubleshooting, see our RPC Assistant tool.
Tradeoffs and Limitations
While batching reduces HTTP overhead, it can increase the complexity of error handling because a batch may return partial errors. Caching introduces staleness, which may be unacceptable for some applications. Narrowing block ranges can miss logs if you don't handle pagination correctly. WebSocket subscriptions require maintaining a persistent connection, which may not be suitable for serverless environments.
Also, compute-unit costs are not standardized; they vary by provider and can change. Always refer to your provider's documentation for the latest values. For OnFinality, see our pricing page.
Next Steps and Further Reading
Now that you understand Ethereum RPC rate limits, you can implement the mitigation strategies to reduce 429 errors. Start by auditing your current RPC usage to identify heavy methods. Then, apply batching, caching, and backoff. For sustained load, consider a dedicated endpoint.
Explore more resources: Ethereum network page for endpoint details, RPC Assistant to compare providers, API service for monitoring, and our learn hub for more tutorials. Also, read the generic RPC 429 handling guide for a broader perspective.