Installation
Step-by-step installation guide for Celestia. Looking for a faster option? Check the auto-install scripts further down.
Cosmovisor isn't used for Celestia. You do not need to use a tool like Cosmovisor to upgrade the binary. Please upgrade your binary before signaling support for the new version.
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.21.2"
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 "celestia-app" ]; then
git clone https://github.com/celestiaorg/celestia-app.git
else
echo "celestia-app already exists, skipping clone."
fi
cd celestia-app
git checkout v9.0.6-mocha
make install
celestia-appd version --longNode Configuration
Initialize Node
Set your moniker
Your moniker is your node's public display name on the network.
celestia-appd init $(hostname) --chain-id mocha-5Custom 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/.celestia-app/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/.celestia-app/config/app.tomlConfigure App
Point the CLI to this chain's ID, keyring backend, and local node.
celestia-appd config chain-id mocha-5
celestia-appd config keyring-backend file
celestia-appd 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 = "0.025utia"|g' $HOME/.celestia-app/config/app.toml
sed -i -e "s/prometheus = false/prometheus = true/" $HOME/.celestia-app/config/config.toml
sed -i -e "s/^indexer *=.*/indexer = \"null\"/" $HOME/.celestia-app/config/config.tomlDownload Genesis & Addrbook
wget -O $HOME/.celestia-app/config/genesis.json "https://snapshot.kynraze.com/testnet/celestia/genesis.json"
wget -O $HOME/.celestia-app/config/addrbook.json "https://snapshot.kynraze.com/testnet/celestia/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/.celestia-app/config/app.toml
sed -i -e "s/^pruning-keep-recent *=.*/pruning-keep-recent = \"$pruning_keep_recent\"/" $HOME/.celestia-app/config/app.toml
sed -i -e "s/^pruning-keep-every *=.*/pruning-keep-every = \"$pruning_keep_every\"/" $HOME/.celestia-app/config/app.toml
sed -i -e "s/^pruning-interval *=.*/pruning-interval = \"$pruning_interval\"/" $HOME/.celestia-app/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/testnet/celestia/celestia_latest.tar.lz4 | lz4 -c -d - | tar -x -C $HOME/.celestia-appCreate Service & Start
This creates a systemd service so the node restarts automatically on crash or server reboot.
sudo tee /etc/systemd/system/celestia-appd.service > /dev/null << EOF
[Unit]
Description=celestia-appd
After=network-online.target
[Service]
User=$USER
ExecStart=$(which celestia-appd) start --home $HOME/.celestia-app
Restart=on-failure
RestartSec=3
LimitNOFILE=65535
[Install]
WantedBy=multi-user.target
EOF
sudo systemctl daemon-reload
sudo systemctl enable celestia-appd
sudo systemctl restart celestia-appd && sudo journalctl -fu celestia-appd -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 ~/.celestia-app/config/priv_validator_key.jsonOnce the service is running, this polls your node's own height against Celestia'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://celestia-testnet-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