Run a Cosmos Hub (cosmoshub-4) full node with gaiad under systemd and bootstrap it with state sync instead of replaying the chain from genesis. It is for node operators and web3 developers with a Linux server.
State sync restores a recent snapshot and uses a light client to verify it.
Prerequisites#
- 16 GB RAM and 1 TB of storage for a node with default pruning, or 2 TB for a full node, per the join mainnet docs (opens in a new tab). The system requirements page (opens in a new tab) asks for 32 GB RAM, or a swap file, for chain upgrades. Use an NVMe SSD.
- A 64-bit Linux server, for example Ubuntu 22.04 or 24.04 LTS. Gaia publishes release binaries for
linux/amd64anddarwin/amd64only. On any other platform, build from source. - Inbound TCP port 26656 open for P2P. The RPC port 26657 listens on
127.0.0.1by default. Keep it there. curl,jqandgnupg. Source builds also needgit,make,gccand Go 1.25.
sudo apt update
sudo apt install -y curl jq gnupg1. Pick the version the network runs today#
State sync restores state at a recent height. Your binary must match the version the chain runs at that height. The newest tag on GitHub is not always the right one.
Ask a public RPC node which app version it runs:
curl -s https://cosmos-rpc.polkachu.com:443/abci_info | jq -r .result.response.versionOn October 22, 2026, mainnet nodes report v28.3.0 or v28.3.1. The Gaia releases page (opens in a new tab) lists v28.3.1 as the latest release on that line.
The releases page marks v29.0.0 as latest. That release belongs to on-chain proposal 1059 (opens in a new tab). The proposal is in voting until October 27, 2026, and sets an upgrade height of 33,618,500. Do not run v29 before the chain reaches that height. The chain registry's recommended_version still shows v28.0.0, so go by the RPC output and the releases page.
2. Install gaiad#
Option A. Install the release binary#
Download the binary, its signature and the checksum file:
VERSION=v28.3.1
mkdir -p ~/gaiad-$VERSION && cd ~/gaiad-$VERSION
BASE=https://github.com/cosmos/gaia/releases/download/$VERSION
curl -fLO $BASE/gaiad-$VERSION-linux-amd64
curl -fLO $BASE/gaiad-$VERSION-linux-amd64.asc
curl -fLO $BASE/SHA256SUMS-$VERSION.txtThe checksum file lists the Linux binary as ./gaiad-linux-amd64/gaiad. Take the hash from that line and check it against your download:
EXPECTED=$(awk '/linux-amd64/ {print $1}' SHA256SUMS-$VERSION.txt)
echo "$EXPECTED gaiad-$VERSION-linux-amd64" | sha256sum -c -The output must end in OK. Then check the signature with the Gaia release key from the cosmos/security repo (opens in a new tab):
curl -fL -o cosmos-release.asc https://raw.githubusercontent.com/cosmos/security/main/release/gaia/signing/keys/cosmos-release.asc
gpg --show-keys --with-fingerprint cosmos-release.asc
gpg --import cosmos-release.asc
gpg --verify gaiad-$VERSION-linux-amd64.asc gaiad-$VERSION-linux-amd64The fingerprint must be E625 9F16 9C7B 913D BB75 DE02 5395 305D 29B8 8B32. The verify step must show using EDDSA key E6259F169C7B913DBB75DE025395305D29B88B32 and Good signature from "Cosmos-Release <nodes@cosmoslabs.io>". You may also see a warning that the key is not certified with a trusted signature. That warning is expected. If you get a bad signature, a different fingerprint or a revoked key, stop and do not install the binary.
Install the binary and check it:
sudo install -m 0755 gaiad-$VERSION-linux-amd64 /usr/local/bin/gaiad
gaiad versionThe output must be v28.3.1.
Option B. Build from source#
Use this path on arm64, or if you would rather compile gaiad yourself. Gaia v28.3.1 declares go 1.25.7 in its go.mod. Go 1.26 and newer cannot build this tag. Its bytedance/sonic v1.14.2 dependency fails to compile with undefined: GoMapIterator. Install the latest Go 1.25 release, 1.25.14, from go.dev/dl (opens in a new tab). Set ARCH to amd64 or arm64:
ARCH=amd64
curl -fLO https://go.dev/dl/go1.25.14.linux-$ARCH.tar.gz
sha256sum go1.25.14.linux-$ARCH.tar.gz
sudo rm -rf /usr/local/go
sudo tar -C /usr/local -xzf go1.25.14.linux-$ARCH.tar.gz
export PATH=$PATH:/usr/local/go/bin
go versionCompare the printed hash with the one on go.dev/dl before you extract the archive. Add the export line to ~/.profile so go stays on your PATH after you log in again. If you keep a newer Go instead, run the build as GOTOOLCHAIN=go1.25.14 make install. The go command then downloads and verifies that toolchain, as described in the toolchain docs (opens in a new tab).
sudo apt install -y build-essential git
git clone https://github.com/cosmos/gaia.git
cd gaia
git checkout v28.3.1
make install
sudo install -m 0755 $(go env GOPATH)/bin/gaiad /usr/local/bin/gaiad
gaiad versionA source build links the CosmWasm library from your Go module cache by absolute path. Do not clear that cache while the node uses this binary.
3. Initialize the node#
Replace <your-moniker> with a public name for your node. The docs allow ASCII characters only. A Unicode moniker makes the node unreachable.
gaiad init <your-moniker> --chain-id cosmoshub-4The command creates ~/.gaia with config and data folders. You edit config/app.toml and config/config.toml in the next steps.
4. Download the genesis file#
The genesis URL comes from the Cosmos chain registry (opens in a new tab). The file is about 21 MB compressed and 121 MB unpacked.
cd ~
curl -fL -o genesis.cosmoshub-4.json.gz https://github.com/cosmos/mainnet/raw/master/genesis/genesis.cosmoshub-4.json.gz
gzip -d genesis.cosmoshub-4.json.gz
mv genesis.cosmoshub-4.json ~/.gaia/config/genesis.json
jq -r .chain_id ~/.gaia/config/genesis.jsonThe last command must print cosmoshub-4.
5. Set the minimum gas price#
gaiad refuses to start while minimum-gas-prices is empty. Set it to 0.005uatom. The on-chain fee market sets a floor of 0.005 uatom in its min_base_gas_price parameter, and the chain registry lists the same value as fixed_min_gas_price. The join mainnet page still shows 0.0025uatom, which is below that floor.
sed -i 's|^minimum-gas-prices = ""|minimum-gas-prices = "0.005uatom"|' ~/.gaia/config/app.toml
grep '^minimum-gas-prices' ~/.gaia/config/app.toml6. Add seeds from the chain registry#
Seed nodes give your node its first peers. Pull the current list from the chain registry:
SEEDS=$(curl -s https://raw.githubusercontent.com/cosmos/chain-registry/master/cosmoshub/chain.json | jq -r '[.peers.seeds[] | "\(.id)@\(.address)"] | join(",")')
echo "$SEEDS"
sed -i "s|^seeds = \".*\"|seeds = \"$SEEDS\"|" ~/.gaia/config/config.toml7. Configure state sync#
Pick two RPC servers#
The light client needs at least two RPC servers. Pick two you trust from the apis.rpc list in the chain registry (opens in a new tab). This example uses two entries from that list:
RPC1=https://cosmos-rpc.polkachu.com:443
RPC2=https://cosmos-rpc.publicnode.com:443Get a trust height and hash#
The trust height must not be higher than the snapshot your node restores. If it is, the light client verifies headers backward one at a time and can hit the 30 second limit for that check. Gaia's default snapshot-interval is 1,000 blocks. The join mainnet docs suggest a trust height close to the tip minus 1,000. Peers can set other intervals, so this guide subtracts 2,000 to leave a margin.
LATEST=$(curl -s $RPC1/block | jq -r .result.block.header.height)
TRUST_HEIGHT=$((LATEST - 2000))
TRUST_HASH=$(curl -s "$RPC1/block?height=$TRUST_HEIGHT" | jq -r .result.block_id.hash)
echo "$TRUST_HEIGHT $TRUST_HASH"Ask the second server for the hash at the same height. The two hashes must match.
curl -s "$RPC2/block?height=$TRUST_HEIGHT" | jq -r .result.block_id.hashWrite the values to config.toml#
The enable key appears only once in config.toml, inside [statesync], so these anchored patterns change only that section:
1CONFIG=~/.gaia/config/config.toml
2sed -i \
3 -e 's|^enable = false|enable = true|' \
4 -e "s|^rpc_servers = \".*\"|rpc_servers = \"$RPC1,$RPC2\"|" \
5 -e "s|^trust_height = .*|trust_height = $TRUST_HEIGHT|" \
6 -e "s|^trust_hash = \".*\"|trust_hash = \"$TRUST_HASH\"|" \
7 $CONFIG
8grep -E '^(enable|rpc_servers|trust_height|trust_hash|trust_period) =' $CONFIGThe output should look like this, with your own height and hash:
enable = true
rpc_servers = "https://cosmos-rpc.polkachu.com:443,https://cosmos-rpc.publicnode.com:443"
trust_height = <trust-height>
trust_hash = "<trust-hash>"
trust_period = "168h0m0s"Leave trust_period at its 168 hour default. The CometBFT state sync docs (opens in a new tab) say it should be much smaller than the unbonding period, which is 21 days on the Hub.
8. Run gaiad as a systemd service#
Create the unit file. Your shell fills in $USER and $HOME when you run the command:
1sudo tee /etc/systemd/system/gaiad.service > /dev/null <<EOF
2[Unit]
3Description=Cosmos Hub node (gaiad)
4After=network-online.target
5Wants=network-online.target
6
7[Service]
8User=$USER
9ExecStart=/usr/local/bin/gaiad start --home $HOME/.gaia
10Restart=on-failure
11RestartSec=5
12LimitNOFILE=65535
13
14[Install]
15WantedBy=multi-user.target
16EOFThe unit raises LimitNOFILE because each peer connection uses a file descriptor, and the CometBFT production notes ask for a sizable open file limit. Start the service and follow the logs:
sudo systemctl daemon-reload
sudo systemctl enable --now gaiad
journalctl -u gaiad -f -o catThe log should show Discovering snapshots, then Discovered new snapshot and Snapshot accepted, restoring. A run of Applied snapshot chunk to ABCI app lines follows, each with the chunk number and the total. The restore ends with Snapshot restored. The node then fetches the blocks it missed.
Verify it works#
Check the sync status on the node:
gaiad status | jq .sync_infocatching_up is true during the restore and while the node fetches recent blocks. It turns false once the node reaches the tip. Compare latest_block_height with a public RPC:
curl -s https://cosmos-rpc.polkachu.com:443/status | jq -r .result.sync_info.latest_block_heightCheck that you have peers:
curl -s localhost:26657/net_info | jq -r .result.n_peersThe node keeps no blocks below the snapshot height, so it cannot serve older history. You can leave state sync enabled after it succeeds. On restart, CometBFT logs Found local state with non-zero height, skipping state sync and continues from local data. If you see that line on the first start, the data folder was not empty. Reset it with the unsafe-reset-all commands under Troubleshooting.
Troubleshooting#
"set min gas price in app.toml or flag or env variable"#
The minimum-gas-prices line is still empty. Repeat step 5, then run sudo systemctl restart gaiad.
The log repeats "Discovering snapshots for 15s"#
No peer has offered a snapshot yet. Check the peer count with the net_info command above, and give the node a few minutes to find more peers. If the loop continues, find a provider that serves Cosmos Hub snapshots and add its peers to persistent_peers in config.toml. Use the form <node-id>@<host>:<port>, then restart the service.
"failed to fetch and verify app hash" with "context deadline exceeded", then "Snapshot rejected"#
One of the RPC servers was too slow while the light client fetched headers. This happens more often when the trust height is above the snapshot height. Replace the slow server with another entry from the registry and fetch a fresh trust height and hash. Then reset the node and start it again:
sudo systemctl stop gaiad
gaiad comet unsafe-reset-all --home ~/.gaia --keep-addr-book
sudo systemctl start gaiadThe reset deletes the data folder and resets priv_validator_state.json. That is safe on a fresh full node. Never run it on a signing validator unless you understand the double-sign risk.
"appHash verification failed" or "wrong Block.Header.AppHash"#
Your binary does not match the network version. Run the abci_info check from step 1 again. Install the matching release, then reset and start again with the commands above.
Next steps#
- Plan for v29. If proposal 1059 passes, nodes stop at height 33,618,500 and log
UPGRADE "v29.0.0" NEEDED. Thegaiadprocess can keep running after the halt, so stop the service before you swap binaries. Verify the v29 binary the same way as in step 2, replace/usr/local/bin/gaiadand start the service. If you build from source, v29 declaresgo 1.26.7in itsgo.mod. The v29 upgrading guide (opens in a new tab) covers manual and Cosmovisor (opens in a new tab) upgrades. - Add monitoring. Set
prometheus = trueunder[instrumentation]inconfig.toml. The node then serves metrics on port 26660 on all interfaces, so block that port in your firewall. - Watch disk use. Gaia takes its own snapshots every 1,000 blocks and keeps the latest 10. The
snapshot-intervalandsnapshot-keep-recentsettings inapp.tomlcontrol this. - Move to a validator. Read the validator overview (opens in a new tab) and the KMS guide (opens in a new tab), then Validator security: sentry nodes, remote signing and firewalls. Keep your mnemonic offline, and enter it only into your own wallet or CLI.