Skip to content

Connecting your CL client ​

The gateway keeps one libp2p session with your beacon node, on TCP 33212. This page is the setup for that session and the fixes when it drops. Prysm, Lighthouse, Teku, Nimbus, and Lodestar are all supported.

Current stable releases

Peering flags are the same on these releases. For Hoodi and mainnet, run:

  • Prysm v7.2.1
  • Lighthouse v8.2.3 — v8.3.0-rc.0 is a Sepolia release candidate
  • Teku 26.9.1 — stay on 26.4.0 or newer; older Teku throws NoSuchElementException at MetadataDasPeerCustodyTracker while exchanging metadata
  • Nimbus v26.10.0
  • Lodestar v1.49.0 — --directPeers has been in the client since v1.40.0

Which ID goes where ​

ValueWhere you read itWhere it goes
Gateway peer_idGET /api/v1/self_info → .peer_idThe CL flag below. Lighthouse --trusted-peers takes this ID alone. The other clients take it inside the multiaddr.
CL peer IDGET /eth/v1/node/identity → .data.peer_idGateway direct_cl_peers only
mump2p.peer_idsself_info.mump2p.peer_idsThe Optimum mesh. No CL flag uses it.
libp2p.peer_idsself_info.libp2p.peer_idsPeers already connected to the gateway, usually your CL after the session is up

Two different peer IDs

--trusted-peers, --peer, --p2p-direct-peers, --direct-peer, and --directPeers take the gateway peer_id. The beacon node's own .data.peer_id is the value you put in direct_cl_peers. Pasting the beacon ID into the CL flag leaves cl_peers at 0.

Do you set direct_cl_peers ​

The gateway uses that list for two things, and only when it is non-empty.

  • It dials each entry and keeps retrying. That is what brings the session back after a gateway restart.
  • It treats the list as an allowlist. Any other libp2p peer is disconnected as soon as it connects.

An empty list means the gateway dials nobody and applies no peer-ID filter. The CL has to dial the gateway, and the firewall on 33212 is the only restriction. 33212 stays private to the CL. 33213 is the port the Optimum mesh uses from the internet.

The field is a list. A second beacon node is fine, and once any entry is set, every beacon node that should stay connected has to be on the list. One that is missing is disconnected.

ClientSet direct_cl_peers?Why
PrysmOptionalPrysm re-dials --peer on its own. If you do set the list, Prysm's peer ID has to be on it.
LighthouseYesLighthouse does not re-dial after a gateway restart. The gateway's retry is what brings it back.
TekuYesTeku treats --p2p-direct-peers as reciprocal. This list is the gateway's half, and the retry.
NimbusYesSame reciprocal direct peer, and Nimbus does not re-dial after a gateway restart.
LodestarYesLodestar treats --directPeers as reciprocal. This list is the gateway's half.

On Docker you can leave the list empty until the CL identity exists, then add it and restart the gateway. On Helm the chart rejects an empty gateway.directClPeers, so the order below is reversed: read the CL identity first, install with that list, then point the CL at the gateway.

Order ​

The gateway ID and the CL ID do not both have to exist before either process starts.

  1. Mount identity_libp2p_dir and identity_mump2p_dir as volumes. Without the libp2p volume the gateway peer_id changes on every restart and the CL flag goes stale.

  2. Start the gateway.

  3. Read the gateway ID and build a multiaddr on an address the CL can route:

    sh
    curl -s http://localhost:48123/api/v1/self_info | jq -r '.peer_id, .libp2p.multiaddrs[]'
    text
    /ip4/<address-the-cl-can-route>/tcp/33212/p2p/<gateway-peer-id>
  4. Start the CL with the flag for that client.

  5. Add the CL's own multiaddr to direct_cl_peers and restart the gateway. Prysm can skip this. Lighthouse, Teku, Nimbus, and Lodestar should not. On Helm this step already happened at install.

1. Gateway uppersisted peer_id2. CL dials the gatewaygateway peer_id in the CL flagTCP 332123. Gateway retriesdirect_cl_peers

Address the CL can actually route

libp2p.multiaddrs[0] is often an address the CL cannot dial: a Docker bridge address in 172.16.0.0/12, a Podman address in 10.89.0.0/24, or a Kubernetes pod IP. Put an /ip4/ address the CL can route into the CL flag. A /dns4/<name>/... multiaddr works in that flag when the CL itself resolves the name, and it works in direct_cl_peers because the gateway parses it as a libp2p multiaddr. See Kubernetes.

Open TCP 33212 only toward the CL. Leave it off the public internet. 33213 is the mesh port.

Same host ​

Gateway and CL on one machine can both use 127.0.0.1 when the CL is on the host network and the gateway publishes 33212 to the host, as in the Quick Start.

The CL flag takes:

text
/ip4/127.0.0.1/tcp/33212/p2p/<gateway-peer-id>

After the CL is up, direct_cl_peers takes the CL's own P2P port:

text
/ip4/127.0.0.1/tcp/<cl-p2p-port>/p2p/<cl-peer-id>

Restart the gateway after adding that line. On Helm, skip this and follow Kubernetes: the CL peer ID goes into gateway.directClPeers before install.

Prysm ​

sh
./beacon-chain \
  --peer=/ip4/YOUR_GATEWAY_IP/tcp/33212/p2p/YOUR_GATEWAY_PEER_ID

--peer may be repeated. Each value is a trusted peer. It accepts a multiaddr, an ENR, or an enode. Prysm re-dials trusted peers on its own, so direct_cl_peers is optional. If you set the list, Prysm's peer ID has to be on it or the gateway disconnects Prysm. Prysm's own P2P port in that multiaddr is 13000, and its identity HTTP port is 3500.

Prysm v7.2

Partial data columns are on by default. That does not change the --peer flag. --disable-partial-data-columns turns them off.

Lighthouse ​

sh
lighthouse beacon_node \
  --boot-nodes=/ip4/YOUR_GATEWAY_IP/tcp/33212/p2p/YOUR_GATEWAY_PEER_ID \
  --trusted-peers=YOUR_GATEWAY_PEER_ID

--boot-nodes takes the multiaddr (or an ENR), comma-separated if you have more than one. --trusted-peers takes the gateway peer ID alone, also comma-separated. --libp2p-addresses is the old name for --boot-nodes and is deprecated.

--trusted-peers keeps that peer at the top of Lighthouse's score, so the peer-table prune leaves it alone. One trusted gateway against the default target is the normal setup.

PeerDAS, separate from the peering flags

--semi-supernode makes the beacon node custody half of the data columns so it can reconstruct blobs. It does not create the gateway session.

--target-peers is the size of the peer table. Lighthouse prunes down toward it. Raise it (for example --target-peers=500) only when the gateway is being dropped because the table is full. If the number of trusted peers reaches the target, Lighthouse warns that peer selection gets worse.

After a gateway restart

Lighthouse does not re-dial. Once Lighthouse has a stable identity, add it to direct_cl_peers so the gateway keeps retrying. Lighthouse's P2P port is 9000 unless you changed it. Its identity HTTP port is 5052.

Teku ​

sh
teku \
  --p2p-direct-peers=/ip4/YOUR_GATEWAY_IP/tcp/33212/p2p/YOUR_GATEWAY_PEER_ID

--p2p-direct-peers is a static peer that keeps exchanging full messages. Teku's own docs treat it as reciprocal: the gateway side of that pair is direct_cl_peers, added once you have Teku's peer ID. The first session can come up with only the flag above.

Static peers are pruned

--p2p-static-peers without --p2p-direct-peers can be dropped when Teku's peer table is full. Use --p2p-direct-peers.

Teku's P2P port is 9000. Its identity HTTP port is 5051.

Nimbus ​

sh
nimbus_beacon_node \
  --direct-peer=/ip4/YOUR_GATEWAY_IP/tcp/33212/p2p/YOUR_GATEWAY_PEER_ID \
  --netkey-file=/data/netkey

A random netkey drops the direct peer

--direct-peer is ignored when --netkey-file is random, which is the default. Point it at a file Nimbus can reuse. The peer ID in direct_cl_peers is then stable. Nimbus treats the direct peer as reciprocal, so add Nimbus to direct_cl_peers as well. Open 33212 on the gateway to Nimbus, and open Nimbus's P2P port to the gateway.

sh
curl -s http://localhost:5052/eth/v1/node/peers/YOUR_GATEWAY_PEER_ID | jq '.data | {state, agent}'

That curl needs --rest on Nimbus. The REST server is off by default, and when it is on it listens on 5052.

A connected gateway shows "state": "connected" and an agent containing optimum-gateway.

Warmup

Nimbus can take a few hours to settle. It drops a peer when the connection closes or the score falls, then reconnects. A short flap in cl_peers during that window is expected. A peer that stays disconnected is the netkey or the firewall, not the warmup.

Lodestar ​

sh
lodestar beacon \
  --directPeers=/ip4/YOUR_GATEWAY_IP/tcp/33212/p2p/YOUR_GATEWAY_PEER_ID

--directPeers keeps a GossipSub direct peer, as a multiaddr or an ENR. Lodestar documents it as reciprocal, so add Lodestar's peer ID to direct_cl_peers once you have it. The flag has been in the client since v1.40.0. Lodestar's P2P port is 9000. Its REST port is 9596. Nimbus is not on that port.

direct_cl_peers ​

This is the CL multiaddr the gateway dials, and keeps dialing after a gateway restart. Lighthouse and Nimbus need it for that reconnect. Teku, Nimbus, and Lodestar also use it as the reciprocal half of a direct peer.

yaml
direct_cl_peers:
  - /ip4/YOUR_CL_IP/tcp/YOUR_CL_P2P_PORT/p2p/YOUR_CL_PEER_ID

Read the CL identity from the beacon REST API, then use the P2P port in the multiaddr, not the HTTP port you queried. Teku's REST API is off until --rest-api-enabled. Nimbus's is off until --rest. A refused curl is the beacon API, not the gateway.

sh
curl -s http://localhost:5052/eth/v1/node/identity | jq '.data.peer_id, .data.p2p_addresses[0]'  # Lighthouse, or Nimbus with --rest
curl -s http://localhost:3500/eth/v1/node/identity | jq '.data.peer_id, .data.p2p_addresses[0]'  # Prysm
curl -s http://localhost:5051/eth/v1/node/identity | jq '.data.peer_id, .data.p2p_addresses[0]'  # Teku with --rest-api-enabled
curl -s http://localhost:9596/eth/v1/node/identity | jq '.data.peer_id, .data.p2p_addresses[0]'  # Lodestar
ClientP2P port in direct_cl_peersIdentity HTTP port
Prysm130003500
Lighthouse90005052
Teku90005051, after --rest-api-enabled
Nimbus90005052, after --rest
Lodestar90009596

Allowlist

When direct_cl_peers is set, the gateway disconnects any libp2p peer whose ID is missing from that list, including a second beacon node you forgot to add. A wrong peer ID means cl_peers stays 0 even though the CL is dialing 33212. An empty list applies no peer-ID filter. Either way, open 33212 only to the CL.

The gateway advertises a custody group count of 8 in its libp2p metadata so PeerDAS clients keep it as a useful peer. That advertisement is in the binary. There is no custody flag to set on the gateway. See the Fulu p2p metadata spec.

Verify ​

sh
curl -s http://localhost:48123/health | jq '{status, cl_peers: .checks.cl_peers, cl_health: .checks.cl_health}'
curl -s http://localhost:48123/api/v1/self_info | jq '{peer_id, total: .libp2p.total_peers, peers: .libp2p.peer_ids}'

A peered CL is cl_peers.value of at least 1 and libp2p.total_peers of at least 1. Expect that within a couple of minutes of the CL flag being loaded. On a stream_only gateway both CL checks are skipped.

cl_health is not the peer count

cl_health: 1 means some CL-path gossip arrived in the last 30 seconds. Publishes from the mesh can tick it while libp2p.total_peers is 0. Alert on mump2p_gateway_cl_peers == 0, not on cl_health. The rules are in Telemetry.

cl_peers can be ok while last_block_age_sec fails. The session is up and the beacon node is not delivering blocks, usually because the execution client is still syncing. Prysm can sit in optimistic sync in that state. Wait for the execution client, then confirm the beacon node is serving. Teku logs Payload marked as invalid by Execution Client for the same wait.

What you seeWhat it isWhat to change
cl_peers stays 0CL is not dialing a reachable address, or the allowlist rejects itRebuild the multiaddr. Open TCP 33212 to the CL only. If direct_cl_peers is set, every CL peer ID in it has to match .data.peer_id.
CL flag was copied from .data.peer_id or from mump2p.peer_idsWrong IDReplace it with self_info.peer_id.
Worked, then died after a gateway restartLighthouse or Nimbus did not re-dial, or the peer ID changedPersist the identity volume. Add direct_cl_peers.
Worked, then the peer ID in self_info changedIdentity directory was not a volumeMount identity_libp2p_dir and update the CL flag.
Lighthouse says goodbye, or drops the gatewayPeer table prune, or a custody metadata mismatch on an old gateway build--boot-nodes plus --trusted-peers as above, on gateway v1.3.2. --semi-supernode does not fix the session.
Teku NoSuchElementException at MetadataDasPeerCustodyTrackerTeku older than v26.4.0Upgrade. Current stable is 26.9.1.
Teku connects, then the gateway disappears--p2p-static-peers onlySwitch to --p2p-direct-peers.
Nimbus logs Adding privileged direct peer and never connectsFirewall, or the netkey is randomStable --netkey-file, both ports open, public or routable IP in direct_cl_peers.
Nimbus REST shows the gateway disconnectedSame netkey or allowlist problem--netkey-file plus direct_cl_peers.
cl_peers ok, no blocksExecution client still syncingWait. This is not a peering flag.