RPC Node Setup
Dependence on public RPC — Alchemy, Infura, QuickNode — is dependence on their uptime, rate limits, and pricing. When request volume reaches 100k/day, the economics of your own node becomes favorable. Beyond cost: your own node gives full debug_* and trace_* namespace, which public providers often disable or charge separately for.
Choosing Ethereum Client
Two main execution clients:
Geth (go-ethereum) — most widespread, largest documentation, stable. Archive mode takes ~16 TB. Slowest at eth_getLogs over large block ranges.
Reth (Paradigm) — written in Rust, significantly faster than Geth on historical queries. Archive mode ~2.5 TB (better compression). Recommended for new installations.
Erigon — archive node ~3 TB, fast historical queries, but more complex in setup and updates.
| Client | Disk (archive) | Sync | Historical Queries |
|---|---|---|---|
| Geth | ~16 TB | 2–4 weeks | Slow |
| Reth | ~2.5 TB | 3–7 days | Fast |
| Erigon | ~3 TB | 3–7 days | Fast |
Installing Reth + Lighthouse (Ethereum Mainnet)
Ethereum PoS requires two clients: execution layer (Reth) + consensus layer (Lighthouse/Prysm):
# Reth
curl -L https://github.com/paradigmxyz/reth/releases/latest/download/reth-x86_64-unknown-linux-gnu.tar.gz | tar xz
sudo mv reth /usr/local/bin/
# Lighthouse (consensus client)
curl -L https://github.com/sigp/lighthouse/releases/latest/download/lighthouse-x86_64-unknown-linux-gnu.tar.gz | tar xz
sudo mv lighthouse /usr/local/bin/
# JWT secret for communication between clients (Engine API)
openssl rand -hex 32 > /etc/ethereum/jwt.hex
Running execution layer (Reth):
reth node \
--chain mainnet \
--datadir /data/reth \
--http \
--http.addr 127.0.0.1 \
--http.port 8545 \
--http.api eth,net,web3,txpool,debug,trace \
--ws \
--ws.addr 127.0.0.1 \
--ws.port 8546 \
--authrpc.addr 127.0.0.1 \
--authrpc.port 8551 \
--authrpc.jwtsecret /etc/ethereum/jwt.hex \
--full # full node, for archive add --full=false
Running consensus layer (Lighthouse):
lighthouse beacon_node \
--network mainnet \
--datadir /data/lighthouse \
--execution-endpoint http://127.0.0.1:8551 \
--execution-jwt /etc/ethereum/jwt.hex \
--checkpoint-sync-url https://mainnet.checkpoint.sigp.io \
--disable-deposit-contract-sync
--checkpoint-sync-url — consensus client sync starts from final checkpoint instead of genesis. Reduces time from weeks to hours.
Configuring Nginx as Reverse Proxy
Direct RPC port access externally is bad. Nginx + auth + rate limiting:
upstream ethereum_rpc {
server 127.0.0.1:8545;
keepalive 32;
}
server {
listen 443 ssl;
server_name rpc.yourdomain.com;
ssl_certificate /etc/letsencrypt/live/rpc.yourdomain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/rpc.yourdomain.com/privkey.pem;
# Basic auth or IP whitelist
satisfy any;
allow 10.0.0.0/8; # internal network
deny all;
location / {
proxy_pass http://ethereum_rpc;
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_set_header Host $host;
proxy_read_timeout 300s;
# Rate limiting
limit_req zone=rpc_limit burst=100 nodelay;
}
}
# Define rate limit zone
limit_req_zone $binary_remote_addr zone=rpc_limit:10m rate=100r/s;
WebSocket for subscriptions — separate location:
location /ws {
proxy_pass http://127.0.0.1:8546;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_read_timeout 3600s; # long-lived connections
}
BSC, Polygon, Other EVM Networks
Most EVM networks are Geth forks. Principle is same, client is different:
# BNB Smart Chain — BSC Geth
geth --config config.toml \
--datadir /data/bsc \
--http --http.api eth,net,web3,txpool \
--ws --ws.api eth,net,web3,txpool
# Polygon — Bor (Geth fork) + Heimdall (consensus)
# Solana — separate story, see solana-validator
For Polygon you need to run both: Bor (execution) and Heimdall (PoS consensus layer), similar to Reth+Lighthouse setup.
Node Monitoring
// Check sync status
async function checkNodeHealth(rpcUrl: string): Promise<NodeHealth> {
const provider = new ethers.JsonRpcProvider(rpcUrl);
const [syncStatus, blockNumber, peerCount] = await Promise.all([
provider.send('eth_syncing', []),
provider.getBlockNumber(),
provider.send('net_peerCount', []),
]);
const isSyncing = syncStatus !== false;
return {
blockNumber,
peers: parseInt(peerCount, 16),
isSyncing,
currentBlock: isSyncing ? parseInt(syncStatus.currentBlock, 16) : blockNumber,
highestBlock: isSyncing ? parseInt(syncStatus.highestBlock, 16) : blockNumber,
lag: isSyncing ? parseInt(syncStatus.highestBlock, 16) - parseInt(syncStatus.currentBlock, 16) : 0,
};
}
Alerts: node is healthy if lag < 5 blocks and peers >= 5. If peers = 0 — node is isolated from network, which is worse than just being behind.
Prometheus + Grafana for long-term monitoring: Reth and Geth export metrics natively (--metrics.port 9001). Ready dashboards — in respective client repositories.







