gaiad from Install gaiad. At the end you have a full node on cosmoshub-4, configured through gaiad config rather than by hand-edited TOML. A validator starts as exactly this node.
Commands below use the default home ~/.gaia. Add --home <path> to every command if you keep the node elsewhere. The units on Run gaiad as a service pass --home for the same reason. Do not rely on GAIAD_HOME, because not every subcommand honours it on v28.3.1.
1
Initialize the home directory
~/.gaia/config with config.toml, app.toml, client.toml, a placeholder genesis.json that the next step replaces, node_key.json, and priv_validator_key.json, plus ~/.gaia/data/priv_validator_state.json. The moniker is a display name. Use ASCII only, since a Unicode moniker makes the node unreachable.Two of those files are keys. The node_key.json file is the peer to peer (P2P) identity and can be regenerated at any time. The priv_validator_key.json file is the consensus key, which cannot be rotated on the Hub today. If this machine will become a validator, back it up now and never copy it to a second running node. Security covers key handling.Check:2
Install the genesis file
7fe946e6bb3c378da546767f4d078585c38f256c8ec17888d71aeee3b7edd5c7, as published in the cosmos/mainnet genesis checksum file. The file is about 21 MB compressed. It is the exported state of cosmoshub-3, which is why initial_height is 5200791 and genesis_time is a 2019 date.Check:cosmoshub-4, 5200791, and 2019-12-11T16:11:34Z. Do not run gaiad genesis validate on this file. On v28.3.1 it reports section is missing in the app_state because the file predates several module formats, yet the node bootstraps from it without trouble. The checksum is the real check.3
Set the client defaults
chain-id = "cosmoshub-4" to client.toml, so later gaiad query and gaiad tx commands need no --chain-id flag. The get subcommand reads a key back, and view client prints the whole file.4
Set the minimum gas price
minimum-gas-prices in app.toml. Set it first: on a fresh home every other gaiad config set app change is rejected with set min gas price in app.toml or flag or env variable until it has a value. The feemarket module’s floor is 0.005uatom, and a node that accepts cheaper transactions only holds spam that no block will include. One query returns the live price:0.005000000000000000.5
Add seeds
seeds in the [p2p] section of config.toml from the chain registry, which listed ten seeds on 2026-10-15. Seeds hand your node a peer list and disconnect, and the node fills its 40 inbound and 10 outbound slots on its own. Reserve p2p.persistent_peers for nodes you must stay connected to, such as your own sentries.The -s flag (--skip-validate) is required for every config.toml key on v28.3.1. Without it gaiad config set config fails with cometbft config is not supported. Reading a key back with gaiad config get config <key> needs no flag.6
Choose pruning and the database backend
Decide both before the first sync. Neither changes in place later.The setting
pruning = "default" keeps the last 362880 application states (about three weeks) and prunes every 10 blocks. That fits a validator or a general remote procedure call (RPC) node. The nothing value makes an archive node, everything keeps only the two latest states, and custom takes pruning-keep-recent and pruning-interval. The pruning-keep-every setting no longer exists. A pruned node cannot answer queries for heights it has pruned.The db_backend key defaults to goleveldb. To run on PebbleDB, set gaiad config set config db_backend pebbledb -s and gaiad config set app app-db-backend pebbledb now. Then restore a PebbleDB snapshot in the next step. Database backends explains the choice.7
Sync the node
A new node has three ways to reach the chain tip: a snapshot from a provider, state sync from public RPCs, or block sync from genesis. Block sync from genesis is not practical on the Hub because every historical upgrade must be replayed with its own binary. Use one of the first two. Sync a node has the provider table, the state sync configuration, and the troubleshooting for both. Come back here when the data directory is in place or state sync is configured.
8
Start the node
Run it in the foreground once to watch the first minutes of logs:The node starts CometBFT, connects to peers, and either restores the state sync snapshot or begins applying blocks on top of the restored data. The log lines to look for are
Discovered new snapshot and Applied snapshot chunk during state sync, then executed block height=... module=state and committed state as it catches up. Stop it with Ctrl-C once it is applying blocks. Then move it under systemd on Run gaiad as a service.Old guides add --x-crisis-skip-assert-invariants. The crisis module is gone, and v28.3.1 refuses to start with that flag.Optional servers
The REST API is off by default. gRPC is on and bound tolocalhost:9090. Turn either on only on a machine that is meant to serve queries, never on a validator.
[api] and [grpc] in app.toml. For Prometheus metrics, set gaiad config set config instrumentation.prometheus true -s, which opens :26660. Endpoints covers running these publicly.
Export state
Thegaiad export command writes the full application state as JSON, which is how a new network or a migration test starts from Hub state. The node must be stopped first, or the command fails with resource temporarily unavailable.
--height flag exports the state after that block, --for-zero-height rewrites it to start a new chain at height 0, and --modules-to-export limits the output to a comma separated module list. With --height the command prints a fastnode notice to stdout before the JSON, so keep --log_level error (or pass --output-document new_genesis.json) or jq rejects the file. Check with jq -r .chain_id new_genesis.json.
Verify
With the node running, from the same machine:network field must be cosmoshub-4. The catching_up field turns false once the node reaches the tip. Compare height with the height on Mintscan or with curl -s https://cosmos-rpc.polkachu.com/status | jq -r .result.sync_info.latest_block_height. Peer count comes from curl -s localhost:26657/net_info | jq .result.n_peers. A healthy node shows ten or more.