Skip to content

Metrics

Monitoring your blockchain node is essential for maintaining performance, detecting issues, and ensuring uptime. This chapter lists the real metric names your client exposes, what each one means, why it matters, and what an abnormal value looks like.


┌─────────────────────────────────────────────────────────────────┐
│ MONITORING OBJECTIVES │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 1. PERFORMANCE │
│ - Track sync progress │
│ - Monitor resource usage │
│ │
│ 2. AVAILABILITY │
│ - Detect node downtime │
│ - Alert on failures │
│ │
│ 3. SECURITY │
│ - Detect unusual patterns │
│ - Monitor access attempts │
│ │
│ 4. CAPACITY PLANNING │
│ - Track storage growth │
│ - Plan upgrades │
│ │
└─────────────────────────────────────────────────────────────────┘

Golden rule: a metric you don’t understand is a metric you can’t alert on. For every metric you scrape, you should be able to answer What is it? Why does it matter? What is abnormal? — that is exactly the format used below.


Terminal window
geth --metrics --metrics.addr 0.0.0.0 --metrics.port 6060
http://localhost:6060/debug/metrics # human-readable
http://localhost:6060/debug/metrics/prometheus # Prometheus text format — use this

⚠️ The Prometheus scrape path is /debug/metrics/prometheus, not /metrics. Configure it in prometheus.yml with metrics_path (below). A wrong path silently scrapes nothing — a classic blackhole.

ClientFlagPrometheus path
Geth--metrics --metrics.addr/debug/metrics/prometheus
Reth--metrics <addr:port>/metrics
Erigon--metrics --metrics.addr/debug/metrics/prometheus
Besu--metrics-enabled --metrics-host --metrics-port/metrics
Nethermindhttp://<host>:<port>/metrics/metrics
Lighthouse (CL)--metrics (MetricsPort default 5054)/metrics
Prysm (CL)--monitoring-port (default 8080)/metrics
CometBFT/gaiad[instrumentation] prometheus = true/metrics (default 26660)
Solanano first-party exporter — use RPC-derived signals (ch50)n/a

36.3 Key Metrics to Track (WHAT / WHY / ABNORMAL)

Section titled “36.3 Key Metrics to Track (WHAT / WHY / ABNORMAL)”
MetricWHATWHYABNORMAL
chain_head_blockNumber of the highest fully-imported blockThe single most important “is my node alive” signalFlat over 10+ min while peers exist → sync stalled
chain_head_headerHighest header seen on the networkTells you how far the node is from the tipDivergence from tip (compare with a public RPC)
chain_head_reorg (counter)Blocks reverted due to chain reorgsFrequent reorgs = peer/connectivity problems or bad peersincrease(...[10m]) > 3 sustained → investigate
MetricWHATWHYABNORMAL
txpool_pendingTxs ready to be included in a blockShows load and health of the mempoolStuck at 0 while network is busy → not seeing txs (sync/peer issue)
txpool_queuedTxs waiting (nonce gaps, low fees)Queue spikes indicate client-side batching or wallet misbehaviorQueued » pending relentlessly → nonce gaps, fee issues
MetricWHATWHYABNORMAL
p2p_peersCurrently connected peersHealthy node needs a stable peer setDrops to < 5 → firewall, NAT, or failing peer ranking
p2p_dials (counter)Peer connection attempts (in+out)High dial rate = flapping connectionsSustained dial storms → peer_table churn, bad bootnodes
p2p_ingress_traffic / p2p_egress_traffic (counters)Bytes received/sentVerifies symmetric P2P flow; egress-heavy = serving lots of stateIngres >> egress consistently → sync bottleneck or attack
MetricWHATWHYABNORMAL
rpc_duration (histogram, label method)Latency of JSON-RPC callsPer-method p99 tells you which API is slow/abusedeth_call/eth_getLogs p99 breaching tolerance → cap or cache them
rpc_failure (counter)Failed RPC invocationsError-rate spikes indicate client bugs, timeouts, or abuseSudden 10x increase → check client + proxy logs

Runtime / Host (Geth Go runtime + node_exporter)

Section titled “Runtime / Host (Geth Go runtime + node_exporter)”
MetricWHATWHYABNORMAL
process_cpu_seconds_totalCumulative CPU secondsSustained high CPU during snapsync is normal; high CPU after sync is suspectPegged at 100% hours after sync → compaction or API abuse
process_resident_memory_bytesRSS memoryWatch for leaks / cache bloatCreeping past your budget with no cache change → investigate
go_goroutinesLive goroutinesSudden spike often precedes OOM> 5,000 and rising → goroutine leak
node_filesystem_avail_bytes (node_exporter)Free diskDisk fills kill nodes; pruning is cheaper than resyncingUnder 10% of disk → alert now (ch38)
node_network_* / node_disk_* (node_exporter)Host NIC/disk statsDetect NIC saturation and IOPS exhaustionDisk %util near 100% while CPU idle → compaction storm

⚠️ Do not use eth_syncing as a Prometheus metric. It is a JSON-RPC method, not a metric. For “sync stalled” alerts use increase(chain_head_block[10m]) == 0 (ch38). Metric names above are geth names — Reth, Erigon, Nethermind, Besu and CL clients export different names; verify with curl localhost:6060/debug/metrics/prometheus | grep before writing rules.


Terminal window
wget https://github.com/prometheus/prometheus/releases/download/v2.45.0/prometheus-2.45.0.linux-amd64.tar.gz
tar xzf prometheus-2.45.0.linux-amd64.tar.gz
./prometheus --config.file=prometheus.yml --web.listen-address=:9090
global:
scrape_interval: 15s
scrape_configs:
- job_name: 'geth'
metrics_path: /debug/metrics/prometheus # ← required, not /metrics
static_configs:
- targets: ['localhost:6060']
- job_name: 'node'
static_configs:
- targets: ['localhost:9100'] # node_exporter for host metrics

  1. Install Grafana
  2. Add Prometheus data source
  3. Import dashboard:
    • Ethereum (geth): community dashboards — check that the metric names match your client’s export before trusting the JSON
    • Generic: create custom panels from the tables in 36.3
DashboardDescription
Block Productionchain_head_block rate, reorg counter
Node HealthCPU, memory, disk (node_exporter)
Networkp2p_peers, p2p_ingress/egress_traffic, txpool
Consensus (CL)Validator attestation/proposal metrics (Lighthouse beacon_*, Prysm validator_*)

groups:
- name: geth-alerts
rules:
- alert: NodeDown
expr: up{job="geth"} == 0
for: 5m
labels:
severity: critical
annotations:
summary: "Geth node is down"
- alert: NoPeers
expr: p2p_peers == 0
for: 5m
labels:
severity: warning
- alert: SyncStalled
expr: increase(chain_head_block[10m]) == 0
for: 15m
labels:
severity: critical
annotations:
summary: "Chain head not advancing"
- alert: MempoolBacklog
expr: txpool_queued > txpool_pending * 3
for: 10m
labels:
severity: info

Full alert design (severity, routing, runbooks) is covered in Chapter 38: Alerting & Notifications.


  • Enable metrics with --metrics; scrape geth from /debug/metrics/prometheus (other clients differ!)
  • Every metric: know what it is, why it matters, what abnormal looks like
  • chain_head_block = the “is my node alive” signal; increase(...[10m]) == 0 = stalled sync
  • eth_syncing is an RPC method, not a metric — never use it in PromQL
  • Use Prometheus + Grafana; verify community dashboards against your client’s real metric names

In Chapter 37: Logging & Log Management, we’ll explore logging.


Last Updated: 2026-09-28