Skip to main content

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, most notably the name of the node, typically "<PROVIDER_NAME>-1" and a list of upstream validators (denoted by secp pubkey and DNS) who have been configured to republish blocks to your full node.
  • /home/monad/monad-bft/config/forkpoint/ - contains consensus quorum checkpoints (written every block) used for bootstrapping state
  • /home/monad/monad-bft/config/validators/ - contains validator sets generated at the boundary block. The newly generated file contains the consensus validator sets for the current epoch and for the upcoming epoch. The most recent validator set is found at validators.toml.
  • /home/monad/monad-bft/ledger/ - contains consensus (BFT) block headers and bodies, including the transactions
  • /dev/triedb - TrieDB database device, contains the state of the blockchain

Prerequisites

Please refer to the Hardware Requirements before continuing.

  • Bare-metal server
  • Ubuntu 24.04+ Operating system
  • Linux Kernel >= 6.8.0.60 (see warning below)
  • Disabled HyperThreading (HT) or Simultaneous MultiThreading (SMT) via BIOS settings
    • these features degrade the node performance
warning

There is a known bug affecting Linux kernel versions v6.8.0.56-generic - v6.8.0.59-generic (inclusive) that causes Monad clients to hang in an uninterruptible sleep state, severely impacting node stability. We recommend v6.8.0.60-generic or higher.

Prepare the Node

info

The following instructions assume the commands are executed as root user.

Update the system

Update the system.

apt update
apt upgrade -y

Reboot the machine if required, for example if the upgrade prints Pending kernel upgrade!.

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.

apt update
apt install -y monad=0.12.2-rpc-hotfix2
apt-mark hold monad

Create monad user

Create a non-privileged user named monad with a home directory and Bash shell.

useradd -m -s /bin/bash monad

Create config directories structure in /home/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

Create the 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. The drive should be on a disk that has no filesystem mounted and no RAID configured.

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

Trigger and reload udev rules, and verify TrieDB is pointing to the NVMe device.

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

Verify the LBA configuration

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

Check if 512 byte LBA is enabled on TRIEDB_DRIVE:

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

This command should return the following expected output:

LBA Format 0 : Metadata Size: 0 bytes - Data Size: 512 bytes - Relative Performance: 0 Best (in use)
info

Data Size should be set to 512 bytes and marked as (in use). If that is not the case, then you will need to set the TRIEDB_DRIVE to use 512 byte LBA with the following command.

nvme format --lbaf=0 $TRIEDB_DRIVE

Verify that the configuration has been corrected.

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

Format the partition

Format the TrieDB partition by executing the monad-mpt one-time service.

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

This should return similar output.

MPT database on storages:
Capacity Used % Path
1.75 Tb 256.03 Mb 0.01% "/dev/nvme1n1p1"
MPT database internal lists:
Fast: 1 chunks with capacity 256.00 Mb used 0.00 bytes
Slow: 1 chunks with capacity 256.00 Mb used 0.00 bytes
Free: 7148 chunks with capacity 1.75 Tb used 0.00 bytes
MPT database has 1 history, earliest is 18446744073709551615 latest is 18446744073709551615.
It has been configured to retain no more than 33554432.
Latest proposed is (18446744073709551615, 0000000000000000000000000000000000000000000000000000000000000000).
Latest voted is (18446744073709551615, 0000000000000000000000000000000000000000000000000000000000000000).
Latest finalized is 18446744073709551615, latest verified is 18446744073709551615, auto expire version is 0
monad-mpt.service: Deactivated successfully.
Finished monad-mpt.service - "Service file for Monad MPT".

Configure Firewall rules

Configure these firewall rules:

  • block all incoming traffic and enable all outgoing (default)
  • allow SSH inbound connections (remote access)
  • allow inbound and outbound to port 8000 for TCP/UDP (Consensus client P2P traffic)

Setup the UFW firewall:

ufw allow ssh
ufw allow 8000
ufw enable
ufw status

Configure the following iptables rule for improved robustness against spam:

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

Please note that this rule will be reset upon reboot, so node operators should use their preferred mechanism, e.g. iptables-persistent for persisting iptables rules.

Hardware firewalls

If using hardware firewalls, you may need to perform additional steps to open up port 8000 to UDP and TCP traffic.

Test outbound TCP

To verify outbound connectivity on TCP port 8000, test a connection to remote host:

$ nc -vz 64.31.29.190 8000
Connection to 64.31.29.190 8000 port [tcp/*] succeeded!

See the node.toml file to test with a remote bootstrap peer.

Configure OTEL Collector

The monad package supports OTEL collector. Through this, you will be able to see all the relevant Monad-specific metrics, available at http://0.0.0.0:8889/metrics.

OTEL_VERSION="0.139.0"
OTEL_PACKAGE="https://github.com/open-telemetry/opentelemetry-collector-releases/releases/download/v${OTEL_VERSION}/otelcol_${OTEL_VERSION}_linux_amd64.deb"
curl -fsSL "$OTEL_PACKAGE" -o /tmp/otelcol_linux_amd64.deb
dpkg -i /tmp/otelcol_linux_amd64.deb
cp /opt/monad/scripts/otel-config.yaml /etc/otelcol/config.yaml
systemctl restart otelcol

Configure the Node

Get configuration files

Configuration files for full nodes:

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

Configuration files for validators:

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

Define Keystore password

Set your own unique and strong password for KEYSTORE_PASSWORD. This password is used to encrypt and decrypt your keystores. It must be wrapped in single quotes (e.g. 'password').

Assuming KEYSTORE_PASSWORD in /home/monad/.env file is not already set, generate a secure random 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

Generate encrypted BLS and SECP keys using monad-keystore binary.

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

Public keys are exported to /home/monad/pubkey-secp-bls for convenience.

External Backup

Please ensure that these backup files are properly stored in an external location, outside of the node. They are required to restore your full node or validator identity in the event of hardware failure or system loss:

  • /opt/monad/backup/secp-backup
  • /opt/monad/backup/bls-backup

Update node.toml

Public Full node configuration

Full nodes can receive block proposals either directly from a validator that whitelists it, or via a raptorcast group. These different configurations are described here.

The bellow instructions are intended for the public full node configuration, where a full node joins the network by connecting to available upstream validators participating in secondary raptorcast. Joining in this configuration is permissionless.

Update the configuration that was downloaded previously (for reference, you can check the testnet config and the mainnet config):

  1. Edit /home/monad/monad-bft/config/node.toml.

  2. The beneficiary field to include the address that should receive block rewards.
    For full nodes, this field can be set to the burn address.

    beneficiary = "0x0000000000000000000000000000000000000000"

    For validator, use the beneficiary wallet address. This address should be prefixed by 0x.

    beneficiary = "0x<VALIDATOR_REWARDS_ADDRESS>"
  3. Update the node_name field to include your provider name. Note that if you are operating multiple nodes, the node name should be unique.

    node_name = "full_<PROVIDER>-<OPTIONAL_SUFFIX>"
  4. Ensure enable_client = true under [fullnode_raptorcast].

  5. Ensure expand_to_group = true under [statesync].

  6. [blocksync_override] peers should remain empty for public full nodes.

Node signature record

The node Name record is a cryptographically signed record that contains a node's network address information and is used for peer discovery and network topology management in the monad-bft system.

Using the keypairs are created, a fullnode will need to sign its name record using the SECP key in order to participate in peer discovery.

The sequence number (seq) in the record allows nodes to determine which version of a peer's address information is most recent, supporting scenarios where nodes change their network location.

source /home/monad/.env
monad-sign-name-record \
--address $(curl -s4 ifconfig.me):8000 \
--keystore-path /home/monad/monad-bft/config/id-secp \
--password "${KEYSTORE_PASSWORD}" \
--self-record-seq-num 0

Update the peer_discovery section in node.toml with the IP addresses, sequence number and name record signature generated from the previous command, for example:

self_address = "12.34.56.78:8000"
self_record_seq_num = 0
self_name_record_sig = "5995f8dc5af4ca70e3b49ce793e7fe353d72b261c14037272958a9f0cc105fdd4890e56cb99765750ca48bab113cccbb378fc61dff8b23da4a03c07bba60034300"

Remote Configuration Fetching (v0.12.1+)

Nodes can automatically fetch forkpoint.toml and validators.toml from remote locations on startup. This feature is configured using the following environment variables in /home/monad/.env:

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

These URLs point to the latest configuration files. When defined, the node will automatically attempt to download fresh configuration files on startup, simplifying node operations and reducing manual intervention during network updates. Monad Foundation is not the exclusive provider of these configuration files. Feel free to replace the above remote links with those from other providers.

Call traces (optional)

For full nodes intended for archiving or RPC workflows, enabling --trace_calls is recommended. This preserves the detailed error information necessary for call traces, e.g. debug_traceTransaction. To make this override, please run systemctl edit monad-execution and add the --trace_calls CLI param to the ExecStart definition (may need a line continuation character \):

systemctl edit monad-execution
[Service]
Type=simple
ExecStart=
ExecStart=/usr/local/bin/monad \
...
--trace_calls
...

Monad Cruft service

Installation of the monad Debian package enables the monad-cruft timer, which runs hourly to clear old artifacts (/opt/monad/scripts/clear-old-artifacts.sh). This is necessary to prevent inode exhaustion as artifacts like forkpoint.toml and ledger files accumulate.

Starting with v0.12.2, you can configure artifact retention times by setting environment variables in /home/monad/.env. The following variables control how long artifacts are retained before deletion (all values in minutes):

  • RETENTION_LEDGER - Ledger files (headers and bodies, default: 600 = 10 hours)
  • RETENTION_WAL - WAL files (default: 300 = 5 hours)
  • RETENTION_FORKPOINT - Forkpoint files (default: 300 = 5 hours)
  • RETENTION_VALIDATORS - Validators files (default: 43200 = 30 days)
# Example: Add retention configuration to /home/monad/.env
RETENTION_LEDGER=600
RETENTION_WAL=300
RETENTION_FORKPOINT=300
RETENTION_VALIDATORS=43200

To customize retention, add or modify these variables in /home/monad/.env. For example, to retain ledger files for 20 hours:

echo "RETENTION_LEDGER=1200" >> /home/monad/.env

These settings will be automatically picked up by the monad-cruft timer on its next hourly run.

Start the Node

Set filesystem permissions:

chown -R monad:monad /home/monad/

Enable the monad services to allow them to automatically start whenever the server is rebooted:

systemctl enable monad-bft monad-execution monad-rpc

Next, run the Hard Reset Instructions to import a recent database snapshot into the node.

Start the monad services:

systemctl start monad-bft monad-execution monad-rpc

This completes the process of starting up a full node!

Refer to the General Operations to monitor the state of the node.