Ge
Applications Azure

Go-Ethereum (Geth) on Ubuntu 24.04 on Azure User Guide

| Product: Go-Ethereum (Geth) 1.17 on Ubuntu 24.04 LTS on Azure

Overview

This guide covers the deployment and configuration of Go-Ethereum (Geth) on Ubuntu 24.04 on Azure using cloudimg Azure Marketplace images. Geth is the most widely used Ethereum execution layer client: it runs a full Ethereum node, speaks the devp2p peer to peer protocol, executes the EVM, and serves the Ethereum JSON-RPC API used by wallets, indexers, and applications. Paired with a consensus client it can also power a validator.

The image installs the pinned official Geth 1.17.4 static binary (commit 36a7dc72) downloaded from the official geth.ethereum.org build store and verified against its SHA256 checksum. It runs as a single Go binary under systemd as the unprivileged geth user, with no Docker. The node is configured to snap sync Ethereum mainnet out of the box, with its chaindata on a dedicated Azure data disk.

Security by design, not an open RPC. An open Ethereum JSON-RPC endpoint on the public internet is a well known abuse and denial of service vector, and if any account were unlocked it would be a theft vector. This image binds the HTTP JSON-RPC (8545), the WebSocket RPC (8546), and the engine API (8551) to 127.0.0.1 only, restricts the exposed HTTP API to the minimal eth,net,web3 namespaces (never personal, admin, or debug), and the Network Security Group does not open those ports at all. The one port that is meant to be open, and must be, is the peer to peer port 30303 (TCP and UDP), which is how the node discovers and connects to peers. The image ships no Ethereum account or keystore and never uses insecure account unlocking.

Security by design, a per instance node identity and JWT. A node identity or an engine API secret baked into an image would be cloned onto every customer VM. This image ships neither. On the very first boot of every VM a fresh per instance engine API JSON Web Token (jwt.hex, openssl rand -hex 32, mode 0640) is generated, and Geth then generates a fresh per instance peer to peer node key in the data directory. The per VM details are written to /root/geth-notes.txt (mode 0600, root only).

What is included:

  • Geth 1.17.4 (pinned official static binary, SHA256 verified), run under systemd as the unprivileged geth user (geth.service)

  • JSON-RPC, WebSocket, and engine API all bound to 127.0.0.1 only, with the HTTP API limited to eth,net,web3, so the node is never an open RPC

  • Snap sync configured against Ethereum mainnet, with a sensible cache and peer count, and --nat extip set from the VM public IP on first boot so peering advertises the correct address

  • A per instance engine API JWT and a per instance peer to peer node key generated on first boot, documented in /root/geth-notes.txt (0600)

  • Chaindata, the node key, and the JWT on a dedicated 20 GiB Azure data disk mounted at /var/lib/geth, so chain state survives independently of the OS disk and the volume is independently resizable

  • A tiny nginx on port :80 serving only an unauthenticated /healthz endpoint (HTTP 200) for Azure Load Balancer health probes, which is not an RPC proxy or a management UI

  • Ubuntu 24.04 LTS base with latest security patches applied at build time, and unattended security upgrades kept enabled

  • Azure Linux Agent for seamless cloud integration and SSH key injection

  • 24/7 cloudimg support with a guaranteed 24 hour response SLA

Prerequisites

  • Active Azure subscription, SSH public key, VNet and subnet in the target region

  • Subscription to the Go-Ethereum (Geth) listing on Azure Marketplace

  • Network Security Group rules allowing TCP 22 (admin) and TCP and UDP 30303 (peer to peer). Do not open 8545, 8546, or 8551.

  • A large data disk for a full mainnet node (see Step 8). A full mainnet archive of chaindata needs hundreds of GiB; the 20 GiB disk that ships with the image suits the image itself, a testnet, or the very start of a snap sync.

Recommended virtual machine size: Standard_D4s_v4 (4 vCPU, 16 GB RAM) as a practical minimum for a mainnet execution node. A serious mainnet node benefits from more RAM and fast SSD storage; testnets are comfortable on smaller sizes.

Step 1: Deploy from the Azure Portal

Search Go-Ethereum in Marketplace, select the cloudimg publisher, and click Create. Configure the Network Security Group to allow TCP 22 for administration and TCP and UDP 30303 for peering. Leave the JSON-RPC ports closed; the node is designed to be queried locally on the VM over loopback, or through a private, authenticated path that you control (see Step 9).

Step 2: Deploy from the Azure CLI

RG="eth-prod"; LOCATION="eastus"; VM_NAME="geth1"
GALLERY_IMAGE_ID="/subscriptions/<sub-id>/resourceGroups/azure-cloudimg/providers/Microsoft.Compute/galleries/cloudimgGallery/images/geth-ubuntu-24-04/versions/<version>"
SSH_KEY="$(cat ~/.ssh/id_rsa.pub)"
az group create --name "$RG" --location "$LOCATION"
az vm create \
  --resource-group "$RG" --name "$VM_NAME" \
  --image "$GALLERY_IMAGE_ID" \
  --size Standard_D4s_v4 \
  --admin-username azureuser \
  --ssh-key-values "$SSH_KEY" \
  --public-ip-sku Standard

# Open peering (UDP+TCP 30303) and admin (TCP 22). Do NOT open the RPC ports.
az vm open-port --resource-group "$RG" --name "$VM_NAME" --port 30303 --priority 1001
az vm open-port --resource-group "$RG" --name "$VM_NAME" --port 22 --priority 1002

Peering uses UDP 30303 for discovery and TCP 30303 for the connection, so open both.

Step 3: First boot

On first boot the image resolves the VM public IP and sets --nat extip so peering advertises the correct address, generates a fresh per instance engine API jwt.hex, asserts that the RPC is bound to loopback with the minimal API set, starts Geth (which generates a fresh per instance node key), waits for the loopback JSON-RPC to answer, starts nginx, and writes /root/geth-notes.txt. This completes within about a minute. SSH in as azureuser and read the details:

sudo cat /root/geth-notes.txt

Step 4: Confirm the service is running

geth.service (the node) and nginx.service (the health probe) are both active. geth version confirms the pinned 1.17.4 stable binary.

geth version
systemctl is-active geth.service nginx.service
systemctl status geth.service --no-pager

geth version reports 1.17.4 stable at commit 36a7dc72 built with Go 1.25; geth and nginx are both active; and systemctl status shows geth.service active and running as the unprivileged geth user

Step 5: Query the node over the loopback JSON-RPC

The JSON-RPC is bound to 127.0.0.1 only, so you query it from a shell on the VM. net_version returns 1 for mainnet, eth_blockNumber returns the current head (0x0 until sync has progressed), web3_clientVersion identifies the Geth build, and net_peerCount shows how many peers are connected.

curl -s -X POST -H 'Content-Type: application/json' \
  --data '{"jsonrpc":"2.0","method":"net_version","params":[],"id":1}' \
  http://127.0.0.1:8545

curl -s -X POST -H 'Content-Type: application/json' \
  --data '{"jsonrpc":"2.0","method":"eth_blockNumber","params":[],"id":1}' \
  http://127.0.0.1:8545

Loopback JSON-RPC calls to 127.0.0.1 port 8545 return net_version 1 for mainnet, eth_blockNumber 0x0 before sync has progressed, web3_clientVersion Geth v1.17.4 stable, and net_peerCount 0x3 for three connected peers

You can also attach an interactive JavaScript console over the local IPC socket, which exposes the full API including admin:

sudo -u geth /usr/local/bin/geth attach /var/lib/geth/geth.ipc

Step 6: Watch peer discovery and sync

Read the Geth log to see devp2p peer discovery and snap sync progress. The node finds peers on port 30303 and begins downloading state. Add -f to follow the log live.

sudo journalctl -u geth.service --no-pager -o cat | tail -20

The Geth journal shows the peer to peer node starting, snap sync enabled, the enode identity advertised on port 30303, and peercount climbing as the node discovers peers, along with a note to pair a consensus client to follow the chain head

Because Ethereum is proof of stake, the execution client needs a consensus client to tell it the chain head. Until you pair one (Step 7), you will see a note that no consensus updates have been received. The node still discovers peers and serves the loopback RPC in the meantime.

Step 7: Verify the RPC is not network exposed

This is the core responsible default. ss confirms that 8545, 8546, and 8551 listen on 127.0.0.1 only, while 30303 is the one open port. A request to the JSON-RPC from a remote host on the internet is refused or times out, because the NSG never opens the port and Geth never binds it to a public address.

sudo ss -tlnp | grep -E '8545|8546|8551|30303'
sudo ss -ulnp | grep 30303

From a remote host on the internet, a request to the JSON-RPC must fail (the port is never opened and Geth never binds it to a public address):

curl --max-time 8 http://<GETH_HOST>:8545     # times out / connection refused

ss shows the HTTP JSON-RPC, WebSocket, and engine API all bound to 127.0.0.1 only while devp2p listens on port 30303; a curl to the VM public IP on port 8545 from a remote host times out and is not reachable; and the checklist confirms the RPC is loopback only, the HTTP API is limited to eth net web3, and the NSG opens only 22, 30303, and 80

Step 8: Pair a consensus client (complete the node)

Post Merge, a complete Ethereum node is the pair of an execution client (this image, Geth) and a consensus client (Lighthouse, Prysm, Teku, or Nimbus). The consensus client drives Geth through the engine API on 127.0.0.1:8551, authenticated with the per instance JWT at /var/lib/geth/jwt.hex. Install a consensus client on the same VM (or reach it over the private loopback) and point it at:

execution endpoint:  http://127.0.0.1:8551
jwt secret file:     /var/lib/geth/jwt.hex

Until a consensus client is attached, Geth peers and serves RPC but does not advance the chain head, which is expected for the execution client alone.

Step 9: Sync, disk sizing, and testnets

The node ships in snap sync mode against Ethereum mainnet. A full mainnet node needs hundreds of GiB of fast SSD storage. Attach a large data disk and mount it at /var/lib/geth before the sync fills the shipped 20 GiB volume:

# attach and mount a larger disk at /var/lib/geth, then move existing chaindata onto it
sudo systemctl stop geth
# ... format and mount your new disk at /var/lib/geth, preserving ownership geth:geth ...
sudo systemctl start geth

To point at a testnet instead of mainnet, set GETH_NETWORK in /etc/default/geth (use a fresh empty data disk so you never mix chaindata across networks):

sudo sed -i 's/^GETH_NETWORK=.*/GETH_NETWORK=--sepolia/' /etc/default/geth
sudo systemctl restart geth

Cache and peer count are also tunable in /etc/default/geth (GETH_CACHE, GETH_MAXPEERS).

Step 10: Expose the RPC safely to your own applications

The RPC is loopback only by design. When your own application tier needs to reach it, do so without binding Geth to a public address:

  • SSH tunnel from a trusted admin host: ssh -L 8545:127.0.0.1:8545 azureuser@<GETH_HOST>, then query http://127.0.0.1:8545 locally.

  • Private VNet with an authenticated, TLS terminated reverse proxy in front of the RPC, reachable only from your application subnet, never from the public internet.

Never set --http.addr 0.0.0.0 on a public VM, and never expose the personal, admin, or debug namespaces over the network.

Step 11: Security recommendations

  • Restrict the NSG. Open TCP and UDP 30303 for peering and TCP 22 for administration only. Keep 8545, 8546, and 8551 closed.

  • Keep the RPC loopback only. If you must expose it, use an SSH tunnel or a private, authenticated reverse proxy (Step 10), never a public bind.

  • Protect the engine JWT. /var/lib/geth/jwt.hex is the shared secret between Geth and your consensus client. It is per VM and mode 0640; keep it that way.

  • Do not create unlocked accounts. For signing, use a dedicated remote signer or a hardware wallet, never --allow-insecure-unlock on a networked node.

  • Keep the OS patched. Unattended security upgrades remain enabled on the running VM.

  • Back up /var/lib/geth (the data disk). Chaindata can be resynced, but the node key and JWT are per instance.

Step 12: Support and Licensing

Go-Ethereum (Geth) is developed by the go-ethereum project and distributed under the GNU Lesser General Public License v3.0 (LGPL-3.0) for the library and the GNU General Public License v3.0 (GPL-3.0) for the command line binaries. This cloudimg image bundles the unmodified official Geth binary. cloudimg provides the packaging, the responsible loopback only RPC hardening, the per instance node identity and JWT automation, the data disk layout, and 24/7 support with a guaranteed 24 hour response SLA.

cloudimg is not affiliated with or endorsed by the go-ethereum project or the Ethereum Foundation. Geth, Go-Ethereum, and Ethereum are marks of their respective owners and are used here only to identify the software.

Deploy on Azure

Find Go-Ethereum (Geth) on Ubuntu 24.04 LTS on the Azure Marketplace, published by cloudimg. Deploy from the Portal or the Azure CLI as shown above.

Need Help?

Email support@cloudimg.co.uk for deployment help, configuration questions, or licensing enquiries.