Installation
Step-by-step installation guide for Cheqd - Cosmovisor (recommended) or manual setup, your choice below. Looking for a faster option? Check the auto-install scripts further down.
Recommended for most operators. Cosmovisor automatically switches binaries at scheduled on-chain upgrade heights and preserves previous versions for rollback if needed.
Core Setup
Setup Server & Dependencies
Update the system and install all packages required to build the node.
sudo apt update && sudo apt upgrade -y && sudo apt install curl tar wget jq build-essential git chrony lz4 -yInstall Go
Go version
Only change this if the chain requires a different minimum Go version than our default.
ver="1.23.8"
cd $HOME
wget "https://golang.org/dl/go$ver.linux-amd64.tar.gz"
sudo rm -rf /usr/local/go
sudo tar -C /usr/local -xzf "go$ver.linux-amd64.tar.gz"
rm "go$ver.linux-amd64.tar.gz"
echo "export PATH=$PATH:/usr/local/go/bin:$HOME/go/bin" >> ~/.bash_profile
source ~/.bash_profile
go versionClone & Build Binary
Version tag to build
Defaults to the last known-good tag from our config.
if [ ! -d "cheqd-node" ]; then
git clone https://github.com/cheqd/cheqd-node.git
else
echo "cheqd-node already exists, skipping clone."
fi
cd cheqd-node
git checkout v4.2.1
make install
cheqd-noded version --longInstall & Initialize Cosmovisor
Installs Cosmovisor and sets up its genesis/ and upgrades/ folder structure around the binary you just built.
Install Cosmovisor
if ! command -v cosmovisor &> /dev/null; then
go install cosmossdk.io/tools/cosmovisor/cmd/cosmovisor@latest
else
echo "Cosmovisor already installed, skipping."
fiInitialize
export DAEMON_NAME=cheqd-noded
export DAEMON_HOME=$HOME/.cheqdnode
cosmovisor init $(which cheqd-noded)Node Configuration
Initialize Node
Set your moniker
Your moniker is your node's public display name on the network.
cheqd-noded init $(hostname) --chain-id cheqd-mainnet-1Custom Port
optionalOnly needed if you're running multiple nodes on the same server and need to avoid port conflicts. Comes before Configure App below, since that step needs to point at whatever port you set here.
Shift default ports
CUSTOM_PORT=26
sed -i.bak -e "s%^proxy_app = \"tcp://127.0.0.1:26658\"%proxy_app = \"tcp://127.0.0.1:${CUSTOM_PORT}658\"%; s%^proxy_app = \"tcp://localhost:26658\"%proxy_app = \"tcp://localhost:${CUSTOM_PORT}658\"%; s%^laddr = \"tcp://127.0.0.1:26657\"%laddr = \"tcp://127.0.0.1:${CUSTOM_PORT}657\"%; s%^laddr = \"tcp://localhost:26657\"%laddr = \"tcp://localhost:${CUSTOM_PORT}657\"%; s%^laddr = \"tcp://0.0.0.0:26656\"%laddr = \"tcp://0.0.0.0:${CUSTOM_PORT}656\"%; s%^pprof_laddr = \"localhost:6060\"%pprof_laddr = \"localhost:${CUSTOM_PORT}060\"%; s%^pprof_laddr = \"127.0.0.1:6060\"%pprof_laddr = \"127.0.0.1:${CUSTOM_PORT}060\"%; s%^prometheus_listen_addr = \":26660\"%prometheus_listen_addr = \":${CUSTOM_PORT}660\"%" $HOME/.cheqdnode/config/config.toml
sed -i.bak -e "s%^address = \"tcp://0.0.0.0:1317\"%address = \"tcp://0.0.0.0:${CUSTOM_PORT}317\"%; s%^address = \"tcp://localhost:1317\"%address = \"tcp://localhost:${CUSTOM_PORT}317\"%; s%^address = \"0.0.0.0:9090\"%address = \"0.0.0.0:${CUSTOM_PORT}090\"%; s%^address = \"localhost:9090\"%address = \"localhost:${CUSTOM_PORT}090\"%; s%^address = \"0.0.0.0:9091\"%address = \"0.0.0.0:${CUSTOM_PORT}091\"%; s%^address = \"localhost:9091\"%address = \"localhost:${CUSTOM_PORT}091\"%; s%^address = \":8080\"%address = \":${CUSTOM_PORT}080\"%" $HOME/.cheqdnode/config/app.tomlConfigure App
Point the CLI to this chain's ID, keyring backend, and local node.
cheqd-noded config chain-id cheqd-mainnet-1
cheqd-noded config keyring-backend file
cheqd-noded config node tcp://localhost:26657Set Minimum Gas Price & Node Defaults
Set minimum gas price, enable Prometheus, and disable / enable indexing.
# set minimum gas price, enable prometheus and disable indexing
sed -i 's|minimum-gas-prices =.*|minimum-gas-prices = "5000ncheq"|g' $HOME/.cheqdnode/config/app.toml
sed -i -e "s/prometheus = false/prometheus = true/" $HOME/.cheqdnode/config/config.toml
sed -i -e "s/^indexer *=.*/indexer = \"null\"/" $HOME/.cheqdnode/config/config.tomlConfigure Price Feeder
This chain's Oracle Keeper module needs its own price-feeder.toml, plus an entry in app.toml pointing at it - without both, the node fails to start with failed to read node config: open .../price-feeder.toml: no such file or directory.
cat > $HOME/.cheqdnode/config/price-feeder.toml << EOF
gas_adjustment = 1
provider_timeout = "1000000s"
[server]
listen_addr = "0.0.0.0:7171"
read_timeout = "20s"
verbose_cors = true
write_timeout = "20s"
[rpc]
grpc_endpoint = "localhost:26090"
rpc_timeout = "100ms"
tmrpc_endpoint = "http://localhost:26657"
[telemetry]
enable-hostname = true
enable-hostname-label = true
enable-service-label = true
enabled = true
global-labels = [["chain_id", "cheqd-mainnet-1"]]
service-name = "price-feeder"
prometheus-retention-time = 100
[[provider_endpoints]]
name = "mexc"
rest = "https://api.mexc.com/"
websocket = "wbs-api.mexc.com"
[[provider_endpoints]]
name = "coinbase"
rest = "https://api.exchange.coinbase.com"
websocket = "ws-feed.exchange.coinbase.com"
EOFcat >> $HOME/.cheqdnode/config/app.toml << EOF
[pricefeeder]
config_path = "$HOME/.cheqdnode/config/price-feeder.toml"
log_level = "info"
enable = true
EOFDownload Genesis & Addrbook
wget -O $HOME/.cheqdnode/config/genesis.json "https://snapshot.kynraze.com/cheqd/genesis.json"
wget -O $HOME/.cheqdnode/config/addrbook.json "https://snapshot.kynraze.com/cheqd/addrbook.json"Set Seeds & Peers
Loading peers…
Finishing Touches
Configure Pruning
These defaults are suited for a regular RPC/full node. Use 'nothing' for an archive node, or 'everything' to minimize disk usage.
Pruning strategy
pruning="custom"
pruning_keep_recent="100"
pruning_keep_every="0"
pruning_interval="10"
sed -i -e "s/^pruning *=.*/pruning = \"$pruning\"/" $HOME/.cheqdnode/config/app.toml
sed -i -e "s/^pruning-keep-recent *=.*/pruning-keep-recent = \"$pruning_keep_recent\"/" $HOME/.cheqdnode/config/app.toml
sed -i -e "s/^pruning-keep-every *=.*/pruning-keep-every = \"$pruning_keep_every\"/" $HOME/.cheqdnode/config/app.toml
sed -i -e "s/^pruning-interval *=.*/pruning-interval = \"$pruning_interval\"/" $HOME/.cheqdnode/config/app.tomlDownload Snapshot
recommendedSkip syncing from genesis - download and extract a recent pruned snapshot so the node starts near the chain tip instead of block 0.
# Install dependencies
sudo apt-get install -y lz4
# Download and extract snapshot
curl -o - -L https://snapshot.kynraze.com/cheqd/cheqd_latest.tar.lz4 | lz4 -c -d - | tar -x -C $HOME/.cheqdnodeCreate Service & Start
This creates a systemd service that runs the node through Cosmovisor, so it restarts automatically on crash/reboot AND swaps binaries automatically at upgrade height.
sudo tee /etc/systemd/system/cheqd-noded.service > /dev/null << EOF
[Unit]
Description=cheqd-noded (via cosmovisor)
After=network-online.target
[Service]
User=$USER
Environment="DAEMON_NAME=cheqd-noded"
Environment="DAEMON_HOME=$HOME/.cheqdnode"
Environment="DAEMON_ALLOW_DOWNLOAD_BINARIES=false"
Environment="DAEMON_RESTART_AFTER_UPGRADE=true"
ExecStart=$(which cosmovisor) run start --home $HOME/.cheqdnode
Restart=on-failure
RestartSec=3
LimitNOFILE=65535
[Install]
WantedBy=multi-user.target
EOF
sudo systemctl daemon-reload
sudo systemctl enable cheqd-noded
sudo systemctl restart cheqd-noded && sudo journalctl -fu cheqd-noded -o catA few basic hardening steps worth doing on any fresh server - not specific to this chain, and not required for the node to run. Read each command before running it and do them one at a time, not chained together - the SSH steps in particular can lock you out if a key isn't already working.
1. Create a non-root admin user
Most fresh servers only have root SSH access - do this before disabling root login below, or you'll have nothing left to log in as. First, generate an SSH key on your own computer (skip this if you already have one):
ssh-keygen -t ed25519Print the public key and copy it - you'll paste it into the next command:
cat ~/.ssh/id_ed25519.pubThen, on the server, create the user and install that key:
sudo adduser admin --disabled-password -q
sudo mkdir -p /home/admin/.ssh
echo "PASTE_YOUR_PUBLIC_KEY_HERE" | sudo tee -a /home/admin/.ssh/authorized_keys
sudo chown -R admin: /home/admin/.ssh
echo "admin ALL=(ALL) NOPASSWD:ALL" | sudo tee -a /etc/sudoersNOPASSWD is a deliberate trade-off, not an oversight - this account has no password at all (login is key-only), so sudo has nothing to prompt for. Log out and back in as admin@your-server-ip and confirm it works before moving on.
2. SSH: key-only login, no root
Make sure you can already log in as the admin user above before running this - it disables password login and root login entirely.
sudo sed -i.bak -e 's/^#*PermitRootLogin.*/PermitRootLogin no/' \
-e 's/^#*ChallengeResponseAuthentication.*/ChallengeResponseAuthentication no/' \
-e 's/^#*PasswordAuthentication.*/PasswordAuthentication no/' \
-e 's/^#*PermitEmptyPasswords.*/PermitEmptyPasswords no/' \
-e 's/^#*PubkeyAuthentication.*/PubkeyAuthentication yes/' /etc/ssh/sshd_config
sudo systemctl restart sshd3. Firewall
Default-deny inbound, only open SSH and this node's P2P port. RPC/API are left closed here on purpose - only open 26657/26317 if you actually want them reachable directly from the internet; otherwise keep them on localhost and put a reverse proxy (with rate limiting) in front instead.
sudo apt-get install -y ufw
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow OpenSSH
sudo ufw allow 26656/tcp comment 'P2P'
sudo ufw enable4. Ban repeated SSH login failures
fail2ban automatically firewalls off an IP after too many failed SSH attempts.
sudo apt-get install -y fail2ban
sudo systemctl enable --now fail2ban5. Security updates: now and automatic
Applies any patches already outstanding, then sets up automatic security updates going forward so you don't need to remember to check again.
sudo apt update && sudo apt upgrade -y
sudo apt-get install -y unattended-upgrades
sudo dpkg-reconfigure -plow unattended-upgrades6. Lock down the validator key file
Only needed if this node is (or will become) a validator - restricts priv_validator_key.json so only the account running the node can read it.
chmod 600 ~/.cheqdnode/config/priv_validator_key.jsonOnce the service is running, this polls your node's own height against Cheqd's live network height every 5s and reports how many blocks are left - stops on its own once caught up.
#!/bin/bash
# Compares this node's local height against a live network RPC to track sync progress.
trap 'exit 0' INT
while true; do
local_status=$(curl -s "localhost:26657/status")
local_height=$(echo "$local_status" | jq -r '.result.sync_info.latest_block_height // empty')
catching_up=$(echo "$local_status" | jq -r '.result.sync_info.catching_up // empty')
network_height=$(curl -s "https://cheqd-rpc.kynraze.com/status" | jq -r '.result.sync_info.latest_block_height // empty')
if [[ -z "$local_height" || -z "$network_height" ]]; then
echo "Could not read a height from the local or network RPC - retrying..."
sleep 5
continue
fi
blocks_left=$((network_height - local_height))
if [[ "$catching_up" == "false" ]]; then
echo "Synced - local height $local_height (network $network_height)."
break
fi
echo "Local: $local_height | Network: $network_height | Blocks left: $blocks_left"
sleep 5
done