Create a service user
~/.gaia to /var/lib/gaia/.gaia. Then run chown -R gaia:gaia on it. The two key files deserve stricter modes than the rest:
priv_validator_key.json file is the consensus key. It cannot be rotated on the Hub today, so the service user is the only account that should be able to read it. A validator should move signing off this machine entirely with remote signing. The node_key.json file holds no on-chain state and can be regenerated if lost. However, a stable node id keeps your peers’ persistent peer lists valid.
From here on the node home is /var/lib/gaia/.gaia and belongs to gaia. Later pages write node commands against ~/.gaia. On a service node, run them as the service user with the explicit home, for example sudo -u gaia gaiad comet show-validator --home /var/lib/gaia/.gaia. Without --home, a command reads the ~/.gaia of whoever runs it, which is a different key or no key at all.
Put gaiad where the units expect it
Both units run/usr/local/bin/gaiad, because systemd has no shell PATH. The release install on Install gaiad already puts it there. A source build leaves it in $(go env GOPATH)/bin, so copy it:
cosmovisor init takes its first binary from /usr/local/bin/gaiad.
Plain gaiad unit
ExecStart line uses absolute paths because systemd has no shell PATH. The value LimitNOFILE=50000 is what the gaia upgrade guide uses. The old 4096 default runs out under peer load, and the node crashes with too many open files. The KillSignal=SIGINT setting with a two minute stop timeout lets gaiad flush its stores instead of being killed mid write. The Restart=on-failure setting brings the node back after a crash but not after a clean stop, so a stop you asked for stays stopped.
Cosmovisor unit
Cosmovisor is the Cosmos SDK process manager that watches for an upgrade height and swaps thegaiad binary. Install it and lay out the directories once. Upgrade a node covers what happens at each upgrade.
go install step needs Go 1.25 on the machine. Release archives for linux-amd64 and linux-arm64 are on the cosmovisor v1.7.3 release if you would rather not build it. The cosmovisor init command copies the current binary to cosmovisor/genesis/bin/gaiad, points the cosmovisor/current symlink at genesis, and writes cosmovisor/config.toml. Cosmovisor v1.7.3 prints v1.7.3 for cosmovisor version. It errors if DAEMON_NAME and DAEMON_HOME are unset, which is why the environment is passed inline.
cosmovisor run start. The cosmovisor start form from older guides no longer exists. The Restart=no setting is deliberate and comes from the gaia upgrade guide. Cosmovisor itself restarts gaiad after a successful upgrade. A systemd restart loop on a failed upgrade would keep relaunching a binary that cannot apply the block. Setting DAEMON_ALLOW_DOWNLOAD_BINARIES=false means you place every upgrade binary yourself and verify it. Auto download trusts whatever URL the proposal named. Setting UNSAFE_SKIP_BACKUP=true skips the pre upgrade copy of data/, which on a Hub node would take longer than the upgrade and double the disk. Take your own backup before upgrade day instead.
Confirm the environment Cosmovisor sees:
DAEMON_* value and the derived paths, including the monitored file data/upgrade-info.json.
Enable and start
Use one of the two units, never both, since they start the same node on the same ports.daemon-reload command makes systemd read the new unit. The enable --now command starts it and arranges a start at boot. The status command shows active (running) with the last log lines. Substitute gaiad for the plain unit.
Logs
/etc/systemd/journald.conf (SystemMaxUse=2G) so a chatty node cannot fill the root disk.
Stop and restart safely
TimeoutStopSec. Let it finish. A restart on a validator costs the blocks missed while the node reconnects to peers, typically under a minute. That is why configuration changes get batched into one restart.
Verify
active, enabled, a climbing height, and a current symlink pointing at genesis until the first upgrade moves it to upgrades/<name>.