How to Run Your Own Monad Full Node
A Monad full node is three processes working together — monad-bft (consensus), monad-execution (the parallel EVM), and monad-rpc(the RPC server) — reflecting Monad’s pipelined architecture. It gives you a private endpoint on chain ID 143 with no rate limits. The headline constraint to understand before you start: Monad nodes must run on bare metal— cloud VMs aren’t officially supported, because the sub-second consensus leaves no room for virtualization overhead. This guide follows the official process on Ubuntu 24.04.
Hardware requirements
Monad favors high clock speed over core count, and needs two separate NVMe drives— one dedicated raw device for the state database (TrieDB), one for consensus data and the OS.
| Resource | Full node | Notes |
|---|---|---|
| CPU | 16 cores, 4.5 GHz+ base | e.g. Ryzen 9950X / 7950X, EPYC 4584PX. Disable HyperThreading/SMT in BIOS. |
| RAM | 32 GB+ | |
| TrieDB disk | 2 TB PCIe Gen4×4 NVMe | Dedicated raw device (no filesystem) for execution state. |
| MonadBFT / OS disk | 500 GB PCIe Gen4×4 NVMe | Consensus data + operating system. |
| Bandwidth | 100 Mbit/s | 300 Mbit/s for validators. |
| Environment | Bare metal only | Cloud/virtualized (AWS/GCP/Azure) not supported — timing-sensitive. |
OS: Ubuntu 24.04+ with Linux kernel ≥ 6.8.0.60. Note the known-bad kernels: avoid v6.8.0.56–59 (cause hangs) and v6.8.0.136–139 (fail to start). This kernel sensitivity is real — pin a known-good version.
Step 1 — Prepare the TrieDB disk
The state DB uses a dedicated raw NVMe device (not a mounted filesystem). Identify the drive, give it a GPT partition, expose it via a stable /dev/triedb symlink, and confirm 512-byte LBA formatting:
sudo apt update && sudo apt install -y nvme-cli
nvme list # find the dedicated TrieDB drive
lsblk -o NAME,SIZE,TYPE,MOUNTPOINT,MODEL # confirm it has NO mountpoint
TRIEDB_DRIVE=/dev/nvme1n1 # <-- set to YOUR device
sudo parted $TRIEDB_DRIVE mklabel gpt
sudo parted $TRIEDB_DRIVE mkpart triedb 0% 100%
# stable symlink -> /dev/triedb
PARTUUID=$(lsblk -o PARTUUID $TRIEDB_DRIVE | tail -n 1)
echo "ENV{ID_PART_ENTRY_UUID}==\"$PARTUUID\", MODE=\"0666\", SYMLINK+=\"triedb\"" \
| sudo tee /etc/udev/rules.d/99-triedb.rules
sudo udevadm trigger && sudo udevadm control --reload && sudo udevadm settle
# must be 512-byte LBA "in use"; if not: sudo nvme format --lbaf=0 $TRIEDB_DRIVE
sudo nvme id-ns -H $TRIEDB_DRIVE | grep 'LBA Format' | grep 'in use'Step 2 — Install the monad package
Monad ships as a Debian package from the Category Labs APT repo. Add the repo + signing key and install the pinned mainnet version (check the docs for the current version number), then hold it so it won’t auto-upgrade:
sudo apt update && sudo apt upgrade -y sudo apt install -y curl nvme-cli aria2 jq cat <<EOF | sudo tee /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 \ | sudo gpg --dearmor --yes -o /etc/apt/keyrings/category-labs.gpg sudo apt update sudo apt install -y monad=0.15.1 # <-- use the current mainnet version sudo apt-mark hold monad
Step 3 — User, directories, and config
Create the monad user and directory tree, then pull the mainnet full-node config templates and generate the node keys (a full node needs an identity for peer discovery). Back up the keystore output somewhere safe:
sudo useradd -m -s /bin/bash monad
sudo mkdir -p /home/monad/monad-bft/config/forkpoint \
/home/monad/monad-bft/config/validators \
/home/monad/monad-bft/ledger /opt/monad/backup
MF=https://bucket.monadinfra.com
sudo curl -o /home/monad/.env $MF/config/mainnet/latest/.env.example
sudo curl -o /home/monad/monad-bft/config/node.toml \
$MF/config/mainnet/latest/full-node-node.toml
# keystore password + keys
sudo sed -i "s|^KEYSTORE_PASSWORD=\$|KEYSTORE_PASSWORD='$(openssl rand -base64 32)'|" /home/monad/.env
source /home/monad/.env
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-backupThen edit /home/monad/monad-bft/config/node.toml: set the beneficiary to the burn address 0x0000000000000000000000000000000000000000 (full nodes don’t earn), give it a unique node_name, set enable_client = true under [fullnode_raptorcast] and expand_to_group = true under [statesync], then sign a name record for peer discovery (monad-sign-name-record with your public IP and ports 8000/8001) and paste the result into [peer_discovery].
Step 4 — Firewall, init, and start
Open the P2P ports, initialize the TrieDB (a one-time format service), then enable the three node services:
sudo ufw allow ssh && sudo ufw allow 8000 && sudo ufw allow 8001 && sudo ufw enable sudo iptables -I INPUT -p udp --dport 8000 -m length --length 0:1400 -j DROP # UDP spam guard sudo chown -R monad:monad /home/monad/ # one-time TrieDB format sudo systemctl start monad-mpt sudo journalctl -u monad-mpt -n 14 -o cat # start consensus + execution + rpc sudo systemctl enable monad-bft monad-execution monad-rpc sudo systemctl start monad-bft monad-execution monad-rpc
Step 5 — Verify
Watch the three services converge, then confirm the RPC is answering on chain ID 143 (0x8f):
journalctl -u monad-bft -f # consensus
journalctl -u monad-execution -f # parallel EVM
journalctl -u monad-rpc -f # RPC server
# once monad-rpc is serving (use the RPC port from your node.toml):
curl http://localhost:8080 -s -X POST -H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":1,"method":"eth_chainId","params":[]}'
# -> {"jsonrpc":"2.0","id":1,"result":"0x8f"} (143 = Monad)Confirm the node is actually at the tip (not just responding) with the checks in Is your RPC node actually at the chain tip?— compare its block height to a reference and watch it advance. Keep the RPC bound to localhost and put a firewall/proxy in front before exposing it.
The honest part: what running it actually costs
Monad is one of the harder nodes to self-host, for reasons specific to its design:
- No cloud option. Bare-metal-only means you can’t spin up a cheap VM — you’re renting or buying a dedicated high-clock machine with two Gen4 NVMe drives. That’s a real monthly floor, not a hobby cost.
- It’s finicky. Specific kernel versions hang or fail to start, SMT must be off, and the state DB wants a raw 512-byte-LBA device. Getting the box right is most of the work.
- It’s a young, fast-moving chain. Expect frequent releases and coordinated upgrades with real node-recovery procedures — you’re on the hook to keep up.
- One node is a single point of failure. Any production use needs redundancy — double the hardware, plus a load balancer and health checks.
- See the full trade-off in Self-Hosted Node vs RPC Provider.
…or skip all of it
SwiftNodes runs load-balanced Monadnodes for you — flat-rate pricing (no per-call compute units), HTTP + WebSocket, no KYC — alongside 75+ other chains under one API key. No bare-metal shopping, no kernel-version roulette.
Grab a key at swiftnodes.io and point your app at https://rpc.swiftnodes.io/rpc/monad?key=YOUR_API_KEY.