Blog
Published Jul 19, 2026 · Updated Aug 6, 2026
Ethereum client bake-off
“The Fastest Ethereum Client Is One Almost Nobody Runs”
Restart behavior mattered more than sync speed or disk size. We tested every execution and consensus client this project supports, one mainnet sync at a time, recording both numbers. The fastest client has almost no real-world adoption.
Companion post: for agent orchestration, the test harness, and methodology, see How We Ran a Six-Week Ethereum Client Bake-Off With Claude.
At a glance: when & how
When
23-day measurement phase, 2026-06-22 → 2026-07-14; steady-state and restart-resume follow-ups through 2026-08-03.
Where
One shared semi-production host — not a live validator. MEV disabled, no validator keys.
Scope
Seven execution clients and five consensus clients, through the same mainnet sync — one client at a time, a 72-hour cap on each.
What we recorded
Two numbers: final disk footprint and cold-sync duration (syncing from scratch). Footprint is the end-of-run data directory, not the mid-sync peak. Capped runs use the last du disk-usage sample before the cap.
TL;DR
Disk: the field converges, ~1.0–1.2 TiB
Execution-layer (EL) clients with full post-merge history use similar disk space: geth 1.13 TiB, nethermind ~1.06 TiB, besu 1.08 TiB. Retention settings, not client efficiency, determine disk size, so it is a poor way to pick a winner.
Speed winner — ethrex, ~2h16m
Ethrex finished first; geth followed at ~8h28m.
The twist — ethrex's restart-resync cliff
Ethrex resumed through 23 minutes / 124 blocks but stalled at 26 minutes / 132 blocks. Measured 1.5–2-hour gaps forced a fresh snap sync (downloading state again, ~2h). This restart cost is a strong candidate explanation for its near-zero mainnet adoption.
The CL layer looks solved — on the axes we measured
All five consensus-layer (CL) clients checkpoint-synced to a validating head in minutes on all three EL anchors: geth ~6–9 min, nethermind ~7–10 min, ethrex ~22–23 min. Four runs needed fixes or re-reads, detailed below; none exposed a client fault. Disk footprint separates them.
Cold-sync time, at a glance
Ethrex synced fastest. Only completed execution-client syncs are comparable; reth and nimbus_eth1 hit the 72-hour cap, and erigon deadlocked.
- Ethrex
- 2h 16m
- Geth
- 8h 28m
- Nethermind
- ~14h 30m
- Besu
- 19h 18m
Bars use a shared 0–20 hour scale.
Disk footprint, at a glance
Full-history clients converge at ~1.0–1.2 TiB; retention settings determine size. Solid bars show nethermind, besu, and geth. Hatched bars show different outcomes:
- Nimbus-eth1: ~40 GB at only ~21% synced; reth: ~0.98 TiB at the 72h cap, projected to finish in the solid bars' band.
- Erigon: frozen partial data from its no-sync deadlock.
- Ethrex: settled at ~470–476 GiB, serving no history. Its smaller size reflects retention, not greater efficiency; it cannot be ranked against pruned full-history nodes.
A short hatched bar is not a disk win.
- Nimbus-eth1
- ~40 GB (partial)
- Ethrex
- ~475 GiB (no-history)
- Reth
- ~0.98 TiB (partial)
- Nethermind
- ~1.06 TiB
- Besu
- ~1.08 TiB
- Geth
- ~1.13 TiB
- Erigon
- ~1.21 TiB (frozen)
Bars use a shared 0–1300 GiB scale.
EL scorecard
Four execution clients synced. Every run used a fixed Prysm consensus client and a 72-hour cap.
- Sync time
- ~14.5h
- Footprint
- ~1.06 TiB steady-state (~251 GiB pre-backfill)
- Sync mode
- snap + Halite
- Mainnet share
- 36.0%
- Sync time
- ~8h28m
- Footprint
- ~1.13 TiB (pruned)
- Sync mode
- snap + --history.chain postmerge
- Mainnet share
- 44.9%
- Sync time
- ~2h16m — fastest
- Footprint
- ~470 GiB steady-state plateau (no-history)
- Sync mode
- snap (v19.0.0 at sync; v22.0.0 steady-state)
- Mainnet share
- ~0%
- Sync time
- ~19h18m
- Footprint
- ~1.08 TiB
- Sync mode
- snap / Bonsai
- Mainnet share
- 17.4%
- Sync time
- did not finish
- Footprint
- ~0.98 TiB (partial)
- Sync mode
- full-sync-only
- Mainnet share
- 1.5%
- Sync time
- did not finish
- Footprint
- ~40 GB (partial)
- Sync mode
- full-sync-only
- Mainnet share
- ~0%
- Sync time
- deadlocked
- Footprint
- ~1.21 TiB (frozen partial, not a synced datadir)
- Sync mode
- OtterSync
- Mainnet share
- ~0%
Mainnet execution-client share, sampled once during the campaign; not re-checked.
CL scorecard
All five consensus clients synced on all three fixed EL anchors: ethrex, then geth, then nethermind. Repeating the sweep tested whether CL results depend on the paired EL.
- Ethrex anchor
- ~739 MiB — smallest
- Geth anchor
- ~518 MiB
- Nethermind anchor
- ~470 MiB
- Ethrex anchor
- ~827 MiB
- Geth anchor
- ~177 MiB — smallest
- Nethermind anchor
- ~178 MiB — smallest
- Ethrex anchor
- ~946 MiB actual (sparse file)
- Geth anchor
- ~725 MiB actual (sparse file)
- Nethermind anchor
- ~730 MiB actual (sparse file)
- Ethrex anchor
- ~2.1 GiB
- Geth anchor
- ~936 MiB
- Nethermind anchor
- ~835 MiB
- Ethrex anchor
- ~5.0 GiB — largest
- Geth anchor
- ~1.2 GiB — largest
- Nethermind anchor
- ~1.3 GiB — largest
Footprint tracks time following the chain, rather than the paired EL. We sampled the geth and nethermind runs minutes after checkpoint-sync, on fresher data directories (datadirs).
Three footprint tiers held across anchors: lightweight (lodestar, lighthouse), middle (grandine, teku), and heavy (nimbus). Sampling time affects order within tiers; the consensus-layer section explains two instabilities.
Additional run details
These collapsed details add peer counts, configuration checks, re-runs, and per-candidate notes to the scorecards.
Show all 10 per-candidate rows — peers, config-optimality, re-runs, notes
| Candidate | Peers | Config optimal | Re-runs | Notable |
|---|---|---|---|---|
| geth × prysm | — | yes | 0 | Baseline; no large optimistic gap to close |
| nethermind × prysm | 49 | yes | 1 | First attempt: 13.3h 0-peer loopback stall; re-run after ExternalIp fix synced clean. Restart-resume measured and bisected (2026-08-01→03): every gap from 12 min to ~35h resumed by plain block import, no re-snap, no cliff |
| ethrex × prysm | 50 | yes | 0 | Datadir plateaus at ~470–476 GiB (confirmed by a follow-up steady-state measurement run on v22.0.0, 4h09m56s snap, drifting 470.2→475.5 GiB over ~42h after); 1 auto-healed stale-pivot event; serves no history beyond its snap pivot |
| besu × prysm | ~50 | n/a from the harness gate; sync-flag audit: SNAP + Bonsai ✅ | 2 | Un-pruned run synced clean and is the ranked footprint; the pruned re-run deadlocked twice and was abandoned (no verdict) |
| reth × prysm | — | yes | 1 | 578 samples; relaunched after --full fix; 47% by block / ~21% gas-weighted at cap |
| nimbus-eth1 × prysm | 20–25 | yes | 1 | 72h continuous, 0 restarts; supersedes an earlier ~21 GB aborted run |
| erigon × prysm | — | n/a (no-sync) | 0 | CL CPU cap raised 200%→600% mid-run; advanced ~5k blocks then re-froze |
| CL sweep × ethrex anchor (5 CLs) | — | yes (all 5) | 2 | teku: JVM-OOM on first attempt (TEKU_CACHE fix); grandine's first attempt failed on a harness du bug |
| CL sweep × geth anchor (5 CLs) | — | yes (all 5) | 0 | Cross-anchor confirmation re-run; the lightweight/mid/heavy tiers reproduced (lodestar↔lighthouse swapped within the lightweight pair, vs. the ethrex primary) |
| CL sweep × nethermind anchor (5 CLs) | — | yes (all 5) | 2 | Second cross-anchor confirmation; teku re-measured (~667→~835 MiB across two runs on the same anchor, showing how window-sensitive the mid tier is); lodestar was re-measured after the anchor returned to head (~7m36s / ~178 MiB); its first attempt (~76m14s) was an anchor-gap artifact, not a lodestar property |
- Peers
- —
- Config optimal
- yes
- Re-runs
- 0
Baseline; no large optimistic gap to close
- Peers
- 49
- Config optimal
- yes
- Re-runs
- 1
First attempt: 13.3h 0-peer loopback stall; re-run after ExternalIp fix synced clean. Restart-resume measured and bisected (2026-08-01→03): every gap from 12 min to ~35h resumed by plain block import, no re-snap, no cliff
- Peers
- 50
- Config optimal
- yes
- Re-runs
- 0
Datadir plateaus at ~470–476 GiB (confirmed by a follow-up steady-state measurement run on v22.0.0, 4h09m56s snap, drifting 470.2→475.5 GiB over ~42h after); 1 auto-healed stale-pivot event; serves no history beyond its snap pivot
- Peers
- ~50
- Config optimal
- n/a from the harness gate; sync-flag audit: SNAP + Bonsai ✅
- Re-runs
- 2
Un-pruned run synced clean and is the ranked footprint; the pruned re-run deadlocked twice and was abandoned (no verdict)
- Peers
- —
- Config optimal
- yes
- Re-runs
- 1
578 samples; relaunched after --full fix; 47% by block / ~21% gas-weighted at cap
- Peers
- 20–25
- Config optimal
- yes
- Re-runs
- 1
72h continuous, 0 restarts; supersedes an earlier ~21 GB aborted run
- Peers
- —
- Config optimal
- n/a (no-sync)
- Re-runs
- 0
CL CPU cap raised 200%→600% mid-run; advanced ~5k blocks then re-froze
- Peers
- —
- Config optimal
- yes (all 5)
- Re-runs
- 2
teku: JVM-OOM on first attempt (TEKU_CACHE fix); grandine's first attempt failed on a harness du bug
- Peers
- —
- Config optimal
- yes (all 5)
- Re-runs
- 0
Cross-anchor confirmation re-run; the lightweight/mid/heavy tiers reproduced (lodestar↔lighthouse swapped within the lightweight pair, vs. the ethrex primary)
- Peers
- —
- Config optimal
- yes (all 5)
- Re-runs
- 2
Second cross-anchor confirmation; teku re-measured (~667→~835 MiB across two runs on the same anchor, showing how window-sensitive the mid tier is); lodestar was re-measured after the anchor returned to head (~7m36s / ~178 MiB); its first attempt (~76m14s) was an anchor-gap artifact, not a lodestar property
Sourced from CLIENT_BAKEOFF_RESULTS.md, the campaign's source-of-truth data — see the full results on-site or the raw doc on GitHub.
What we measured, and how we kept it honest
Comparable results need controlled pairing and verified settings. Each client ran alone; the campaign card defines the 72-hour cap and end-of-run disk measurement. We held the consensus client constant and checked the running configuration.
Prysm stayed constant for the EL scorecard. The Engine API connects EL and CL; EL footprint and sync time are independent of the CL. The CL matrix confirmed this. Prysm used ~0.65–1.68 GB in bounded runs, reaching ~12.5 GB during reth's full 72h cap. That remains negligible beside an EL's hundreds of gigabytes.
The honesty mechanism
Early in the campaign we corrupted our own results by recording footprints before checking disk settings. A benchmark of our misconfiguration is worse than no benchmark. The harness now checks generated, running settings and stamps every row config_optimal=yes|no. It rejects misconfigured footprints. We needed six gate fixes across three review rounds before trusting it.
Every comparable footprint on this page comes from a synced run with verified disk settings. The harness config_optimal gate verifies most; besu needed a manual sync-flag audit. Its harness token targets a prune lever the ranked run deliberately omitted. Capped, no-sync, and pruned-only runs are labelled and excluded.
Live checks also settled configuration disputes, including nimbus_eth1's history-pruning flag below. The binary's --help and the online docs contradicted each other. See how we tested this with Claude for the full harness engineering process.
The disk story
Matched history settings put nethermind and geth on par. Nethermind's synced-tip snapshot was ~251 GiB, below geth's ~1.13 TiB. FastBlocks had yet to backfill older post-merge block bodies and receipts.
Re-measured 2026-08-01, its steady-state datadir is ~1.06 TiB (~1,088 GiB): ~226–230 GiB of state in compact Halite/Paprika flat storage, ~843 GiB of post-merge bodies and receipts, and ~19 GiB of headers and code. Geth keeps the same history under --history.chain postmerge.
Since 2026-08-03, the shipped default drops that history:
- Default is minimal-history (
NETHERMIND_FULL_HISTORY=false) — a fresh sync drops the post-merge bodies and receipts and holds at ~250–280 GiB (state only), with no backfill. - The cost is served history: pre-sync blocks return
null, like ethrex. It is the same retention lever turned down — not a client that is “smaller.” - To serve a public RPC, set
NETHERMIND_FULL_HISTORY=trueon a fresh or rebuilt datadir; converting an existing minimal datadir needs a rebuild.
The ~1.06 TiB figure and the chart below are that full-history opt-in.
Nethermind is the smallest measured no-history staking node:~250–280 GiB against ethrex's ~470 GiB. Geth has no clean setting to drop post-merge history and cannot reach that floor. This is the one place its compact state engine is a genuine disk win, at the cost of serving no history. Calling it 4× leaner than geth would compare different retention settings.
- State (flat storage)
- ~228 GiB
- Block bodies
- ~595 GiB
- Receipts
- ~249 GiB
- Headers + code
- ~19 GiB
Full-history nethermind ~1.06 TiB. The minimal-history default drops bodies + receipts → ~250 GiB (state + headers), the smaller of the two no-history nodes (ethrex ~475 GiB) — and a tier geth can't reach.
Besu uses ~1.08 TiB. Reth already used ~87% of geth's size at its 72h cap (47% by block / ~21% gas-weighted), projecting to ~1.1–1.2 TiB finished. Those two join geth (1.13) and nethermind (~1.06) in the band retaining full post-merge history.
Snap-sync speed and restart-resume stability separate the clients, as detailed below. Nethermind remains a good choice for its compact flat-storage state and minority-client diversity, just not because it is smaller on disk than geth.
The remaining footprints are not comparable finished results:
- ethrex — synced, ~2h16m, fastest in the field. Its datadir plateaus at ~470–476 GiB. Post-sync growth toward ~465 GiB (+43 GiB/hr) slowed ~300× to +0.13 GiB/hr. It drifted 470.2 → 475.5 GiB over ~42 hours (confirmed 2026-07-28→31). The earlier ~467 GiB sample caught the climb, not unbounded growth. It lands smaller only because it serves no history at all. On a state-only basis it is not even the smallest: nethermind's ~226–230 GiB state is roughly half ethrex's total. This comparison is imperfect: ethrex's total includes headers and recent blocks, and state encodings differ.
- reth — 72h cap at ~21% gas-weighted (47% by block count), ~0.98 TiB partial. Full-sync-only (no snap) — can't reach tip in a practical window, though its trajectory already projects into the converged band above.
- nimbus_eth1 — 72h cap at ~21.6%, ~40 GB partial. Full-sync-only (no snap). Online pruning is logged (below), but it can't finish in 72h.
- erigon — deadlocked, no result. Optimistic-sync deadlock against a checkpoint-synced CL (below).
Ethrex is not a drop-in replacement for a public DeFi-facing RPC, including this repo's nginx/Caddy endpoint feature. A live probe (2026-07-29) placed its history cutoff exactly at the snap-sync pivot, the block chosen to start syncing: eth_getBlockByNumber returns null one block before it and resolves at and after it. Ethrex never backfills older history.
- Current balances, quotes, and allowances work.
- Blocks, logs, and receipts before the pivot fail: effectively all Ethereum history. Indexer/subgraph backfill, portfolio history, and tax/accounting exports cannot work.
- Geth with
--history.chain postmergeserves that history.
null — all of Ethereum history (genesis → pivot)null — pre-pivot blocks/logs/receipts (indexers, portfolio history, tax exports break)served — pivot → head (~4,783 blk at measurement; grows forward, never backfills)state only — last ~128 blk (~25 min)not to scale — the served window is ~0.02% of the chain (4,783 of 25.6M blocks)
null. That's why its ~470 GiB isn't a disk win — it keeps almost no chain.The speed story
ethrex snap-synced to a fully-validating head in ~2h16m, the fastest in the field by nearly 4×. It kept fifty peers throughout, automatically healed one stale pivot in ~4 min, and never crashed.
Two things keep it off the recommended list:
- Disk size reflects missing history. Ethrex prunes nothing. Even at tip with
eth_syncing=false, its datadir grew 286 → 403 → 416 → ~467 GiB on 2026-07-06 before settling at ~470–476 GiB. It serves almost no history: the pivot cutoff above prevents comparison with full-history clients. It is neither compact nor a full-history archive. - The restart cliff — the next section.
Restart resilience
Geth and nethermind resumed cleanly; ethrex stalled or restarted; besu deadlocked mid-sync. Upgrades, config changes, crashes, and host maintenance make restart behavior an everyday concern. Cold-sync numbers describe only day one.
A running node gets interrupted
what happens next depends on one thing: was it already synced, or still syncing?
Gap after it synced
Catches back up — geth, nethermind
imports missed blocks, keeps datadir — minutes
Starts over — ethrex, head freezes past a ~25-min gap
longer (~1.5–2h) gaps discard state, re-snap from scratch — ~2 h
Interrupted during sync
Wedges — alive but frozen — besu
pivot ages out; answers RPC, writes 0 data — manual rebuild
Why: a full node serves only ~128 recent blocks of state (~25 min); cross that and you can't resume by state.
- Graceful resume. The client imports missed blocks and keeps its on-disk state, catching up in minutes without re-downloading. Geth bridged a ~52-hour gap by sequential block import (applying trie differences), kept its full datadir, and reached the validating tip without re-snapping. Nethermind also resumed gracefully; its bisection follows below. Reth never reached a synced datadir, so we could not measure resume.
- Re-snap cliff. Past a downtime threshold, ethrex stalls with a disconnected head. Longer measured gaps caused it to discard synced state and start again. Only ethrex showed this behavior; the bisection pins its onset.
- Mid-sync deadlock. If the CL stops driving the engine during an in-progress snap sync, the EL's pivot ages out of the network's servable-state window and the sync wedges irrecoverably — the process stays alive and answers RPC but makes zero progress. besu is the cautionary tale here.
Both failures involve a limited serving window. Full nodes serve only recent world-state (roughly ~128 blocks). When a head or pivot falls behind it, peers cannot supply the state needed to heal. The client must choose a new pivot. Graceful-resume clients import gap blocks, which remain available.
- Ethrex
- resumed ≤124 blk; stalled at 132 blk (~25 min); ~1.5–2h gaps → full re-snap
- Nethermind
- resumed every tested gap, 69 → 10,607 blk (12 min → ~35h); largest in 35m09s
- Geth
- resumed ~15,400 blk (~52h), no re-snap
nethermind's resume, bisected
Nethermind resumed every tested gap without re-snapping (2026-08-01→03). In an opportunistic catch-up, a CL restart at 13:24:55Z left it 10,607 blocks (~35h of chain) behind the external tip, and it closed the entire gap by ordinary block import in 35m09s (~302 blocks/min) with the datadir intact (1.165 → 1.178 TB, +1.1%: exactly the imported bodies/receipts).
Controlled stop→wait→start tests at 12 min / 30 min / 1 h / 4 h gaps (2026-08-02→03): every gap resumed geth-style — ordinary Engine-API block import, no re-pivot, no snap/state-sync, zero crashes. The tell is the state-dir delta: ~1.0–1.3 MiB per imported block, constant across rungs — linear import, the opposite of a re-snap, which would rewrite the whole ~238 GiB state. Resume time scales gently (121s at 12 min → 483s at 4 h → 35m09s at ~35 h), dominated by the CL re-syncing its missed slots rather than by EL import. nethermind has no servable-window cliff — the direct contrast to ethrex's ~128-block cliff below.
Separate fresh-sync measurement: 2026-07-31
A separate establish run (2026-07-31) snap-synced nethermind fresh in 1h52m51s (~280 GiB at snap, pivot 25,649,064, zero restarts). This was far faster than the ~14.5h Stage-B figure because the pivot was minutes-old and near-tip, and network conditions differed. It is a second data point, not a replacement for the Stage-B number.
ethrex's cliff, bisected
A ~1.5–2h restart gap forced fully-synced ethrex to start again. At mainnet head with 286 GiB, it abandoned state for a fresh snap sync from the current head. The datadir collapsed 286 GiB → ~9 GiB, then climbed. The journal showed SNAP SYNC STARTED from near-genesis, eth_blockNumber at 0x0 throughout. Re-running the entire ~2h cold-sync pipeline took 2h11m.
Controlled stop → wait → start cycles narrowed the threshold, with live Prysm driving forkchoice (selecting the chain head):
68 blocks missed
108 blocks missed
124 blocks missed
132 blocks missed
The cliff edge is ~128 blocks ≈ 24–25 minutes. Bisection identified header-fetch failure beyond the ~128-block serving window as the trigger, rather than “state expiry” itself. Just past the edge, ethrex first stalls with a disconnected head: peers will not serve the gap headers. The stuck head is the onset; larger gaps (~1.5–2h) end in a full datadir-collapse re-snap.
Why it matters: a ~25-minute restart cliff and ~2h re-snaps after longer gaps make operation costly. This is a strong candidate explanation for ethrex's ~0% adoption despite its fastest cold sync.
Fairness caveats: we observed this on ethrex v19.0.0, a young client that may improve. Restart resilience is separate from the recorded cold-sync result; the cliff does not change that time.
besu's mid-sync deadlock
besu's pruned re-run deadlocked twice and was abandoned. The chain:
- A stale prysm v7.1.5 (a PeerDAS/data-column-sidecar bug) stalled the CL for ~28h; besu logged
Execution engine not called in 120 secondscontinuously. - With no
forkchoiceUpdateddriving it, besu's snap-sync pivot aged out of the servable window — the world-state heal became un-completable. - besu threw
IllegalStateException: The pivot block number has not increased, cancelled its snap-sync download (SnapSyncChainDownloader.consumePivotUpdate), and the downloader thread died without restarting. The process stayed alive and still answeredeth_blockNumber— but the sync engine was dead and the datadir frozen. - Restarting resumed on the same stale pivot and re-deadlocked identically.
Three takeaways:
- An in-progress besu snap sync is fragile to a prolonged CL outage — a stale CL binary can poison the EL's pivot irrecoverably.
- besu answering RPC ≠ besu syncing — judge by disk growth and DB writes, not RPC liveness.
- Same root cause as ethrex's cliff — the ~128-block servable-state window, one client hitting it mid-sync, the other post-sync on restart.
Full-sync-only clients
Reth and nimbus_eth1 full-sync from genesis; neither offers snap sync. Both hit 72h far from tip: reth ~21% gas-weighted (47% by block), ~0.98 TiB; nimbus_eth1 ~21.6% by block, ~40 GB. Their design misses our snap-to-tip bar, not a correctness test. Ranking them against snap sync on time or disk would be unfair; reth runs successfully and widely elsewhere.
Nimbus_eth1's pruning flag was disputed. Its config sets prune = true. The binary's --help says it prunes expired bodies and receipts. Online docs require a separate era1 export for pre-merge history, implying the flag is inert; we had marked it “unverified.”
Throughout the 72-hour import, the journal logged continuous online pruning: Pruning history … pruned=N. The flag is not inert. Full pre-merge completeness versus era1 import remains untestable here: the node never reached tip. It ran 72 hours with zero restarts: stable, but slow by design.
Erigon was the EL sweep's only no-sync result. Erigon3's OtterSync and checkpoint-synced Prysm deadlocked in a mutual wait. Raising CL CPU from 200%→600% advanced ~5k blocks before it re-froze, leaving ~1.21 TiB of frozen partial data, not a synced datadir.
Erigon3 · OtterSync
Waits for CL finalization
Checkpoint-synced Prysm
Waits for EL execution
Distribution as predictor
Snap-sync availability and resilience predict syncability better than mainnet share. The data only partly supports our initial “mainnet share predicts syncability” hypothesis. Erigon deadlocked; reth and nimbus_eth1 were slow by design. But minimalist ethrex, at ~0% share, synced fastest. Its snap sync and clock-based stale-pivot self-healing excelled at cold sync. Its adoption gap is far better explained by the restart cliff than by any sync deficiency.
Consensus layer
All five CLs reached a fully-validating head in minutes on every anchor. We swept lighthouse, lodestar, grandine, teku, and nimbus against one fixed EL, then repeated on two more to test EL/CL independence:
Ethrex was slowest, still well under an hour. Footprints are in the CL scorecard above; lodestar's slow first nethermind attempt was an anchor artifact, detailed below.
Every run logged config_optimal=yes with zero crashes. Four runs needed caveats, detailed below; none exposed a client fault. Sync times are effectively tied within each anchor, leaving footprint as the differentiator.
Teku (ethrex anchor) — First attempt JVM-OOM-starved the shared host (64 min sync, briefly blipped the anchor); re-run with a larger JVM heap synced clean in 22 min.
Grandine (ethrex anchor) — First attempt crashed when du hit a datadir file that vanished mid-snapshot (a harness pipefail bug, since fixed); the re-run synced clean.
Lodestar (nethermind anchor) — First run took ~76 min — not a lodestar property; the anchor EL was still importing an unrelated 2-day gap at the time. Clean re-read: ~7m36s.
Teku (nethermind anchor) — Watchdog reported a false anchor-health miss on both runs, latched during teku's JVM warm-up; the anchor was healthy when each measurement was actually taken.
Three tiers held, with two order instabilities. Lodestar↔lighthouse swapped within the lightweight pair between ethrex and geth. Teku's re-read (~667 vs ~835 MiB) crossed grandine and back on one anchor. Grandine < teku held on all three anchors: the second instability is measurement noise, not a real swap. Stable tiers without an identical total order support EL/CL independence and holding CL=prysm constant for the EL scorecard.
All five CLs worked on the measured axes. Choose by footprint and preference; lighthouse is the lean, safe default. Operational risk in an Ethereum node lives in the EL layer, not the CL layer.
Scope matters: we swept the five CLs for checkpoint-sync and footprint, not restart-resume. Only the constant Prysm anchor had restart tests. Its clean resume from its own DB supports “prysm resumes cleanly,” not “the CL layer survives anything.”
Recommendations
Choose geth by default or nethermind for diversity. Every EL's campaign issues and verdict follow:
None found. Cleanest snap sync (~8.5h) and the only EL measured resuming a multi-day (~52h) gap without a re-snap.
One installer bug, fixed: P2P defaulted to the loopback address, so the first run peered with 0 nodes for 13.3h before anyone noticed. Every gap tested after the fix resumed cleanly, up to ~35h.
Synced cleanly once, but a pruned re-run deadlocked twice: if the CL stalls long enough mid-sync, besu's snap pivot ages out and the sync engine dies silently (the process keeps answering RPC).
Fastest cold sync in the field (~2h16m), but a restart gap past ~25 minutes stalls, and a gap of ~1.5–2h triggers a full re-snap from scratch. Serves no history before its sync pivot — not a drop-in public RPC.
No snap-sync path. Never reached tip inside the 72h cap on this host (47% by block count when capped) — a slow bootstrap, not a broken client.
Same limitation as reth: no snap-sync path. Ran a full 72 hours with zero restarts but reached only ~21.6% of tip.
OtterSync deadlocked against a checkpoint-synced consensus client — each side waited on the other and neither issued the forkchoiceUpdated call that would break the tie. Never produced a synced datadir in this campaign, even after the consensus client's CPU cap was raised 3x.
The detail behind each verdict:
- Default: geth. Largest ecosystem, most documentation, cleanest snap sync (~8.5h), and graceful restarts. Its ~1.13 TiB footprint matches other ELs retaining full post-merge history, so it is not a downside unique to geth. Run it unless you have a specific reason to choose another client.
- Diversity pick: nethermind. Compact flat-storage state and minority-client diversity, with measured restart resilience: every 12 min–~35h gap resumed by block import (2026-08-01→03), with no re-snap or cliff. Full-history disk is comparable to geth (~1.06 vs ~1.13 TiB); the ~251 GiB snap-tip snapshot omitted backfill. Sync takes longer (~14.5h vs ~8.5h).
- Consensus client: lighthouse as the lean default; any of the five is operationally fine — pick on footprint and familiarity.
- Watch, don't yet deploy: ethrex. Fastest cold sync, but its ~25-minute restart cliff makes it operationally costly today. Its settled ~470–476 GiB plateau serves no history; using it in place of a full-history nginx/Caddy RPC will silently fail on anything historical. Revisit this fast-moving client: v19.0.0 at first sync, v22.0.0 at the steady-state measurement.
- Enterprise with care: besu. It syncs, but its snap sync is fragile to CL outages; handle upgrades and CL health deliberately.
- Know the design limits: reth and nimbus_eth1 are full-sync-only — excellent clients, but plan for a long initial sync rather than snap-to-tip. Avoid erigon3 + a checkpoint-synced CL until the optimistic-sync deadlock is resolved.
Bottom line
Run geth by default or nethermind for diversity. Both synced and resumed cleanly, with similar full-history disk footprints.Ethrex's ~25-minute restart cliff and besu's vulnerability to a stalled consensus client need caution. Restart behavior reveals risks a cold-sync chart misses. It is the strongest candidate explanation for why almost nobody runs the fastest client.