OshVanK Node ServiceOshVanK Node Service
Monad
Testnet

Installation

A comprehensive guide to running a Monad Full Node and Validator on Testnet.

đŸ’ĸ Full Node Installation

đŸ’ĸ Components

Monad nodes are running services using systemd:

  • monad-bft - Consensus client
  • monad-execution - Execution client
  • monad-rpc - RPC server
  • monad-mpt - One-time execution for initializing the TrieDB disk
  • monad-cruft - Cleanup service running hourly
  • otelcol - OTEL collector service for metric collection

The systemd services are running using monad service user. The configuration and data structure is:

  • /home/monad/.env - contains the environment variables to configure monad services
  • /home/monad/monad-bft/config/node.toml - contains configurable consensus parameters
  • /home/monad/monad-bft/config/forkpoint/ - contains the forkpoint
  • /home/monad/monad-bft/config/validators/ - contains validator sets
  • /home/monad/monad-bft/ledger/ - contains consensus block headers and bodies
  • /dev/triedb - TrieDB database device

đŸ’ģ Prerequisites

  • Bare-metal server
  • Ubuntu 24.04+ Operating system
  • Linux Kernel >= 6.8.0.60 and not 6.8.0.136
  • Disabled HyperThreading (HT) or Simultaneous MultiThreading (SMT) via BIOS settings.

đŸ’ĸ Prepare the Node

â„šī¸ Info The following instructions assume the commands are executed as root user.

đŸ’ĸ Check Linux kernel

Get the current active Linux kernel version:

uname -r

âš ī¸ Warning There is a known bug affecting Linux kernel versions v6.8.0.56-generic to v6.8.0.59-generic (inclusive) that causes Monad clients to hang. We recommend v6.8.0.60-generic or higher. There is also a known bug affecting v6.8.0.136-generic that causes Monad clients to fail to start. We recommend v6.8.0.134-generic if not using 6.8.0.60+.

Ignore unsupported Linux kernel versions, to prevent auto-upgrade:

cat <<'EOF' > /etc/apt/preferences.d/block-kernel-6.8.0-136
Package: linux-image-6.8.0-136-generic linux-headers-6.8.0-136-generic linux-modules-6.8.0-136-generic linux-modules-extra-6.8.0-136-generic
Pin: release *
Pin-Priority: -1
EOF

đŸ’ĸ Update the system

apt update
apt upgrade -y

Reboot the machine if required. Install additional dependencies:

apt install -y curl nvme-cli aria2 jq

đŸ’ĸ Install monad package

Configure the APT repository:

cat <<EOF > /etc/apt/sources.list.d/category-labs.sources
Types: deb
URIs: https://pkg.category.xyz/
Suites: noble
Components: main
Signed-By: /etc/apt/keyrings/category-labs.gpg
EOF

curl -fsSL https://pkg.category.xyz/keys/public-key.asc \
  | gpg --dearmor --yes -o /etc/apt/keyrings/category-labs.gpg

Install monad package for Testnet (v0.15.2):

apt update
apt install -y monad=0.15.2
apt-mark hold monad

đŸ’ĸ Create monad user

useradd -m -s /bin/bash monad
mkdir -p /home/monad/monad-bft/config \
         /home/monad/monad-bft/ledger \
         /home/monad/monad-bft/config/forkpoint \
         /home/monad/monad-bft/config/validators

đŸ’ĸ Configure TrieDB Device

Set the NVMe drive (e.g. /dev/nvme1n1) to be used for TrieDB device, create a new partition table and a partition that spans the entire drive.

âš ī¸ IMPORTANT: Identify drives carefully Formatting the wrong drive will destroy your operating system! Before proceeding, verify which drive to use:

nvme list
lsblk -o NAME,SIZE,TYPE,MOUNTPOINT,MODEL
TRIEDB_DRIVE=/dev/nvme1n1 # CHANGE THIS TO YOUR NVME DRIVE

parted $TRIEDB_DRIVE mklabel gpt
parted $TRIEDB_DRIVE mkpart triedb 0% 100%

Create a udev rule to set permissions and create a symlink for the partition:

PARTUUID=$(lsblk -o PARTUUID $TRIEDB_DRIVE | tail -n 1)
echo "Disk PartUUID: ${PARTUUID}"

echo "ENV{ID_PART_ENTRY_UUID}==\"$PARTUUID\", MODE=\"0666\", SYMLINK+=\"triedb\"" \
  | tee /etc/udev/rules.d/99-triedb.rules

udevadm trigger
udevadm control --reload
udevadm settle
ls -l /dev/triedb

Verify LBA Configuration, and enable 512 byte LBA if not enabled:

nvme id-ns -H $TRIEDB_DRIVE | grep 'LBA Format' | grep 'in use'

(Data Size should be set to 512 bytes. If not, use nvme format --lbaf=0 $TRIEDB_DRIVE)

Format the partition by executing the monad-mpt service:

systemctl start monad-mpt
journalctl -u monad-mpt -n 14 -o cat

đŸ’ĸ Configure Firewall rules

ufw allow ssh
ufw allow 8000
ufw allow 8001
ufw enable
ufw status

sudo iptables -I INPUT -p udp --dport 8000 -m length --length 0:1400 -j DROP

đŸ’ĸ Configure OTEL Collector

OTEL_VERSION="0.139.0"
OTEL_CHECKSUM="1a1576dde7d51fa7094f4963ceaff37c91ac7b9c9593ba735a3a328ec6f8acd9"
OTEL_PACKAGE="https://github.com/open-telemetry/opentelemetry-collector-releases/releases/download/v${OTEL_VERSION}/otelcol_${OTEL_VERSION}_linux_amd64.deb"
OTEL_DEB="/tmp/otelcol_linux_amd64.deb"

curl -fsSL "$OTEL_PACKAGE" -o "$OTEL_DEB"

if echo "${OTEL_CHECKSUM}  ${OTEL_DEB}" | sha256sum --check --quiet; then
  dpkg -i "$OTEL_DEB"
  cp /opt/monad/scripts/otel-config.yaml /etc/otelcol/config.yaml
  systemctl restart otelcol
else
  echo "Checksum verification failed — aborting install." >&2
fi

đŸ’ĸ Configure the Node

đŸ’ĸ Get configuration files

Configuration files for Testnet full nodes:

MF_BUCKET=https://bucket.monadinfra.com
curl -o /home/monad/.env $MF_BUCKET/config/testnet/latest/.env.example
curl -o /home/monad/monad-bft/config/node.toml $MF_BUCKET/config/testnet/latest/full-node-node.toml

(For validators, replace full-node-node.toml with node.toml)

đŸ’ĸ Define Keystore password

sed -i "s|^KEYSTORE_PASSWORD=$|KEYSTORE_PASSWORD='$(openssl rand -base64 32)'|" /home/monad/.env
source /home/monad/.env

mkdir -p /opt/monad/backup/
echo "Keystore password: ${KEYSTORE_PASSWORD}" > /opt/monad/backup/keystore-password-backup

đŸ’ĸ Generate Keystores

bash <<'EOF'
set -e
source /home/monad/.env

if [[ -z "$KEYSTORE_PASSWORD" || \
      -f /home/monad/monad-bft/config/id-secp || \
      -f /home/monad/monad-bft/config/id-bls ]]; then
  echo "Skipping: missing KEYSTORE_PASSWORD or keys already exist."
  exit 1
fi

monad-keystore create \
  --key-type secp \
  --keystore-path /home/monad/monad-bft/config/id-secp \
  --password "${KEYSTORE_PASSWORD}" > /opt/monad/backup/secp-backup

monad-keystore create \
  --key-type bls \
  --keystore-path /home/monad/monad-bft/config/id-bls \
  --password "${KEYSTORE_PASSWORD}" > /opt/monad/backup/bls-backup

grep "public key" /opt/monad/backup/secp-backup /opt/monad/backup/bls-backup \
  | tee /home/monad/pubkey-secp-bls

echo "Success: New keystores generated"
EOF

âš ī¸ External Backup Ensure that /opt/monad/backup/secp-backup and /opt/monad/backup/bls-backup are stored securely off-server.

đŸ’ĸ Update node.toml

  1. Edit /home/monad/monad-bft/config/node.toml using a text editor (e.g., nano).
  2. Update the beneficiary field (burn address for full node, wallet address for validator).
  3. Update the node_name field with your unique provider name.
  4. Ensure enable_client = true and expand_to_group = true.

đŸ’ĸ Node signature record

Generate the node record signature for Testnet:

source /home/monad/.env
monad-sign-name-record \
  --ip $(curl -s4 ifconfig.me) \
  --tcp-port 8000 \
  --udp-port 8000 \
  --authenticated-udp-port 8001 \
  --keystore-path /home/monad/monad-bft/config/id-secp \
  --password "${KEYSTORE_PASSWORD}" \
  --self-record-seq-num 1

Update the [peer_discovery] section in node.toml with the generated values. self_address should be "<self_ip>:<self_tcp_port>".

đŸ’ĸ Remote Configuration Fetching

In /home/monad/.env:

REMOTE_VALIDATORS_URL='https://bucket.monadinfra.com/validators/testnet/validators.toml'
REMOTE_FORKPOINT_URL='https://bucket.monadinfra.com/forkpoint/testnet/forkpoint.toml'

đŸ’ĸ Monad Cruft service

You can configure artifact retention times by setting environment variables in /home/monad/.env (all values in minutes):

RETENTION_LEDGER=600
RETENTION_WAL=300
RETENTION_FORKPOINT=300
RETENTION_VALIDATORS=43200

đŸ’ĸ Start the Node

chown -R monad:monad /home/monad/
systemctl enable monad-bft monad-execution monad-rpc
systemctl start monad-bft monad-execution monad-rpc

đŸ’ĸ Validator Installation

Setting up a validator requires completing the Full Node installation first, syncing, and then registering your node via the staking precompile.

đŸ’ĸ Requirements

  1. Minimum self-stake: 100,000 MON
  2. Total stake (including delegations): 10,000,000 MON
  3. Must be in the top 200 validators by stake weight.

đŸ’ĸ Staking CLI

Use staking-sdk-cli to interface with the staking precompile. Follow the onboarding workflow on GitHub.


đŸ’ĸ General Operations

đŸ’ĸ Version Information

monad-node --version

đŸ’ĸ Service Management

systemctl status monad-bft monad-execution monad-rpc --no-pager -l
journalctl -u monad-bft -f

đŸ’ĸ Monitoring Block Height

curl http://localhost:8080/ \
  -X POST \
  -H "Content-Type: application/json" \
  --data '{"method":"eth_blockNumber","params":[],"id":1,"jsonrpc":"2.0"}'

đŸ’ĸ Viewing MonadDB Disk Usage

monad-mpt --storage /dev/triedb

đŸ’ĸ Log Analysis with monlog

usermod -a -G systemd-journal monad
cd /home/monad
curl -sSL https://pub-b0d0d7272c994851b4c8af22a766f571.r2.dev/scripts/monlog -O
chmod u+x ./monlog
./monlog

đŸ’ĸ Node Status with monad-status

curl -sSL https://bucket.monadinfra.com/scripts/monad-status.sh -o /usr/local/bin/monad-status
chmod +x /usr/local/bin/monad-status
monad-status

đŸ’ĸ Hard Reset Instructions

If you need to wipe local state and download a fresh snapshot on Testnet:

# 1. Stop services and reset workspace
bash /opt/monad/scripts/reset-workspace.sh

# 2. Download and import snapshot (Monad Foundation)
MF_BUCKET=https://bucket.monadinfra.com
curl -sSL $MF_BUCKET/scripts/testnet/restore-from-snapshot.sh | bash

# 3. Fetch latest forkpoint and validators (if not using Auto Fetch)
VALIDATORS_FILE=/home/monad/monad-bft/config/validators/validators.toml
curl -sSL $MF_BUCKET/scripts/testnet/download-forkpoint.sh | bash
curl $MF_BUCKET/validators/testnet/validators.toml -o $VALIDATORS_FILE
chown monad:monad $VALIDATORS_FILE

# 4. Restart services
systemctl start monad-bft monad-execution monad-rpc

đŸ’ĸ Node Migration

To promote a full node to a validator with minimal downtime:

  1. Sync a full node to the tip of the network.
  2. Backup id-secp, id-bls, and node.toml from the existing validator and transfer them to the synced full node.
  3. On the full node, generate a new name-record signature:
source /home/monad/.env
monad-sign-name-record --ip $(curl -s4 ifconfig.me) \
  --tcp-port 8000 --udp-port 8000 \
  --node-config /home/monad/monad-bft/config/node.toml \
  --authenticated-udp-port 8001 \
  --self-record-seq-num 2 \
  --keystore-path /home/monad/monad-bft/config/id-secp \
  --password "$KEYSTORE_PASSWORD"
  1. Replace [peer_discovery] in the full node's node.toml with the output.
  2. Stop services on the original validator:
systemctl stop monad-bft monad-rpc monad-execution
  1. Stop full node services, restart them as a validator:
systemctl stop monad-bft monad-rpc monad-execution
sleep 1
systemctl start monad-bft monad-rpc monad-execution

On this page