Security and network exposure
A node needs only its peer-to-peer ports reachable from the internet. Everything else it listens on is for the machine itself or for a network you trust, and the table below says which listener is which. Before a binary runs on a node that holds value, verify where the archive came from.
Ports and listeners¶
| Listener | Flag and default | Default address | Starts by default | May face the internet |
|---|---|---|---|---|
| Peer connections, TCP | --port, 30303 | every interface | yes | yes |
| Peer discovery, UDP | --discovery.port, 30303; without it, follows --port | every interface | yes, unless --nodiscover | yes |
| HTTP JSON-RPC, TCP | --http.port, 8545 | localhost (--http.addr) | no: --http | no |
| GraphQL | none: it answers at /graphql on the HTTP listener | the HTTP listener’s | no: --graphql, which needs --http | no |
| WebSocket JSON-RPC, TCP | --ws.port, 8546 | localhost (--ws.addr) | no: --ws | no |
| Metrics, TCP | --metrics.port, 6060 | none: --metrics.addr sets it | no: --metrics and --metrics.addr | no |
| Profiling (pprof), TCP | --pprof.port, 6060 | 127.0.0.1 (--pprof.addr) | no: --pprof | no |
| IPC | --ipcpath, geth.ipc | a socket in the data directory; a named pipe on Windows | yes, unless --ipcdisable | no |
The Engine API, the authenticated interface Ethereum consensus clients use, starts only on a chain configured for the merge, which no network built into the client is. There it listens on localhost, port 8551, set by --authrpc.addr and --authrpc.port.
localhost means the node listens on 127.0.0.1. Metrics and pprof share a default port, so a node that runs both needs one of them moved. On Linux and macOS the IPC socket can be opened only by the user the node runs as, and by root.
On Linux, ss lists what each process listens on:
1 | |
A node with the default settings has two lines, for peer connections and discovery on every interface:
1 2 | |
HTTP, WebSocket and pprof each add a line on 127.0.0.1, and metrics adds one on the address --metrics.addr names. These four lines come from a node that ran all of them, with metrics on 127.0.0.1 and every port moved off its default: metrics on 19514, pprof on 19515, HTTP on 19512 and WebSocket on 19513.
1 2 3 4 | |
With the default --nat any, a node behind a router that supports UPnP or NAT-PMP asks the router to forward its peer-to-peer ports, and no other port. --nat none turns that off.
A host firewall¶
This nftables ruleset drops every new inbound connection except peer-to-peer traffic and SSH. Replies to connections the host opened still arrive, and so does the host’s own traffic on the loopback interface, so RPC on 127.0.0.1 keeps answering. Save it as core-geth.nft:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | |
Load it as root:
1 | |
- Other machines then reach only TCP and UDP 30303 and SSH. That holds for an RPC listener bound to every interface too.
- Loading the file again replaces the
core-gethtable and leaves any other table in place. - An existing firewall still applies. A packet this table accepts is dropped if another table on the same host drops it, so open the peer-to-peer ports there as well.
- Match the node’s ports. If you set
--port, use that port on both peer-to-peer lines; if you set--discovery.port, use it on theudpline. - Load it at boot with your distribution’s nftables service.
RPC exposure¶
The JSON-RPC and GraphQL listeners have no TLS, authentication or rate limiting of their own. Keep them on 127.0.0.1, and put a proxy that provides those in front of any that other machines must reach. Before you widen one:
- Docker shows how publishing a container’s RPC port can expose it on every interface of the host.
- 4. Re-check RPC exposure lists what to check in
--http.addr,--http.api,--http.corsdomain,--ws.addrand--ws.api. - Using JSON-RPC APIs says which namespaces each interface serves, including why an empty
--http.apiis refused. - Public RPC endpoint covers which namespaces to serve to other machines, and what a proxy in front must supply.
Keys¶
The node key¶
<datadir>/geth/nodekey holds the node’s private key for the peer-to-peer network, and the node’s enode ID comes from it. The node reads the file at every start, and writes a new key there if it cannot load one. The key is stored unencrypted, in a file only its owner can read or write.
A copy of a data directory carries its nodekey, so two hosts started from copies of one data directory have the same enode ID. --nodekey loads the key from another file instead.
A node upgraded from a v1.12.x release needs a new key: 3. Rotate the P2P node key explains why and how.
Account keys¶
Account keys are keyfiles in <datadir>/keystore, or in the directory --keystore names, each encrypted with its passphrase. Key derivation on a small machine covers how hard that encryption is to break.
clef is a signer that runs as its own process and holds account keys outside the node. Started with --signer and the path of clef’s IPC socket, the node sends signing requests to clef and opens no keystore of its own. Start clef first: a node that cannot reach its signer stops as it starts.
1 | |
--unlock with --password unlocks keystore accounts as the node starts, and they stay unlocked until it stops. While an account is unlocked, any caller that reaches the eth namespace can make it sign, with eth_sign or eth_sendTransaction, and no passphrase. On a node whose RPC other machines can reach, leave accounts locked, never set --allow-insecure-unlock, and sign with clef. Start the signers shows the refusal geth gives when --unlock meets HTTP or WebSocket RPC.
Peer restrictions¶
--netrestrict takes a comma-separated list of networks in CIDR notation. The node then dials only peers in those networks, closes incoming connections from any other address as they arrive, and drops nodes outside those networks from discovery replies and from the bootnode list. Its discovery port still answers packets from any address; --nodiscover closes that port.
1 | |
--maxpeers caps the number of peers, 50 by default. --maxpeers 0 does not close the peer-to-peer port, which still listens.
Clock¶
Peer discovery depends on the clock. Every discovery packet carries an expiry time, set a fixed interval ahead of the sender’s clock, and a node drops any packet whose expiry time has already passed by its own clock. When two nodes’ clocks differ by more than that interval, the node whose clock is behind sends packets the other drops, so the request and reply that discovery needs between them never complete.
Keep the clock synchronized with NTP. On a host that runs systemd, this reports whether it is:
1 2 | |
Reporting a vulnerability¶
To report a vulnerability, follow SECURITY.md. geth version-check checks go-ethereum’s advisory feed, which does not track this client, so its result says nothing about this client’s vulnerabilities.