Go-Ethereum (Geth) on Ubuntu 24.04 on Azure User Guide
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
gethuser (geth.service) -
JSON-RPC, WebSocket, and engine API all bound to
127.0.0.1only, with the HTTP API limited toeth,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 extipset 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
:80serving only an unauthenticated/healthzendpoint (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

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

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

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

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 queryhttp://127.0.0.1:8545locally. -
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.hexis 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-unlockon 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.