Ethereum Node Deployment
Running an Ethereum node today is easier than two years ago, but there are nuances that turn "simple task" into a lost day. The main one — Ethereum after The Merge requires two clients running simultaneously: Execution Layer (EL) and Consensus Layer (CL). They interact via Engine API (authenticated JWT), and without CL the node won't sync.
Choosing Clients
Execution Layer:
- Geth — reference client, Go, largest market share. Time-tested, large community. Snap sync is fast.
- Nethermind — .NET, good performance on Windows server, actively developed
- Besu — Java, enterprise-friendly, supports private networks
- Erigon — Go, smallest database (~2TB vs ~1TB for Geth snap), but snapshot sync much slower
Consensus Layer:
- Prysm — Go, most popular, good documentation
- Lighthouse — Rust, excellent performance and memory consumption
- Teku — Java, from ConsenSys, good in enterprise
Recommended combination to start: Geth + Lighthouse — both stable, well documented.
Hardware Requirements
| Node Type | Disk | RAM | CPU |
|---|---|---|---|
| Full node (snap sync) | 1.2 TB NVMe | 16 GB | 4 cores |
| Archive node | 14+ TB NVMe | 32 GB | 8 cores |
| Validator | 1.2 TB NVMe | 16 GB | 4 cores |
Critically important: NVMe SSD only. HDD and SATA SSD won't provide necessary IOPS — node will constantly lag behind chain head.
Deployment via Docker Compose
# docker-compose.yml
version: "3.8"
services:
geth:
image: ethereum/client-go:latest
restart: unless-stopped
volumes:
- ./data/geth:/data
- ./jwt.hex:/jwt.hex:ro
ports:
- "30303:30303" # P2P
- "30303:30303/udp"
- "8545:8545" # HTTP RPC (localhost only!)
- "8546:8546" # WebSocket RPC
command:
- --datadir=/data
- --http
- --http.addr=0.0.0.0
- --http.vhosts=*
- --http.api=eth,net,web3,txpool
- --ws
- --ws.addr=0.0.0.0
- --ws.api=eth,net,web3
- --authrpc.addr=0.0.0.0
- --authrpc.vhosts=*
- --authrpc.jwtsecret=/jwt.hex
- --authrpc.port=8551
- --maxpeers=50
- --cache=4096
lighthouse:
image: sigp/lighthouse:latest
restart: unless-stopped
volumes:
- ./data/lighthouse:/data
- ./jwt.hex:/jwt.hex:ro
ports:
- "9000:9000" # P2P
- "9000:9000/udp"
- "5052:5052" # Beacon API
command:
- lighthouse
- bn
- --network=mainnet
- --datadir=/data
- --http
- --http-address=0.0.0.0
- --execution-endpoint=http://geth:8551
- --execution-jwt=/jwt.hex
- --checkpoint-sync-url=https://mainnet.checkpoint.sigp.io
- --disable-deposit-contract-sync
JWT Secret: Mandatory Step
# Generate JWT secret for Engine API
openssl rand -hex 32 > jwt.hex
chmod 600 jwt.hex
Without this file (and matching content in both clients) they can't communicate.
Checkpoint Sync vs Genesis Sync
--checkpoint-sync-url in Lighthouse — difference between 15 minutes and weeks of CL sync. Checkpoint sync downloads finalized beacon state from trusted source and syncs from there, not from genesis. Public checkpoint endpoints: https://mainnet.checkpoint.sigp.io, https://beaconstate.ethstaker.cc.
Geth on first run automatically uses snap sync — downloads only "snapshot" of state, not all historical blocks. Takes 4–12 hours depending on internet and disk speed.
Monitoring
# Geth sync status
curl -s -X POST http://localhost:8545 \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","method":"eth_syncing","params":[],"id":1}' | jq
# Lighthouse status
curl -s http://localhost:5052/eth/v1/node/syncing | jq
Add to Prometheus via ethereum_exporter (EL metrics) and lighthouse_beacon (CL metrics). Grafana dashboards for both exist in official repos.
Security
- HTTP RPC (8545, 8546) should not be open to internet without auth — anyone can drain unlocked accounts or load node with requests
- Nginx reverse proxy with rate limiting and, if external access needed — Bearer token auth
- Engine API (8551) — localhost only or internal docker network, no external access needed
- Firewall: open only P2P ports (30303, 9000)







