# Installation

## **Node Requirements**

**CPU:**

* 16 cores, **4.5+ GHz base clock**\
  (e.g., AMD Ryzen 9950X / 7950X, EPYC 4584PX)

**RAM:**

* **32 GB+**

**Storage:**

* **2 TB NVMe (PCIe Gen4x4)** — TrieDB
* **500 GB NVMe (PCIe Gen4x4)** — MonadBFT + OS

**Bandwidth:**

* **Validators:** 300 Mbit/s
* **Full Nodes:** 100 Mbit/s

**SSD Reliability Notes:**

* **Recommended:** Samsung 980/990 Pro, Samsung PM9A1
* **Acceptable:** Micron 7450 (occasional slowdowns)
* **Avoid:** Nextorage SSDs — can freeze under load

***

1. ### **Update packages and install useful tools.**

```bash
sudo apt update && sudo apt upgrade --yes && \
sudo apt install git build-essential ufw curl jq snapd screen ncdu nano fuse ufw curl nvme-cli aria2 jq --yes
```

***

2. ### **Install Monad Debian Package.**

```bash
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
```

***

3. ### **Install the `monad` package.**

```bash
apt install -y monad=0.14.2
apt-mark hold monad
```

***

4. ### **Create the `monad` User and Directories.**

```bash
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
```

***

5. ### **Configure TrieDB NVMe Device**

   > Use a **dedicated NVMe** with no filesystem / RAID on it.

```bash
TRIEDB_DRIVE=/dev/nvme1n1   # CHANGE THIS TO YOUR DEVICE
```

Create GPT and a full-disk partition:

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

Create a udev rule and `/dev/triedb` symlink:

```bash
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
```

***

6. ### **Ensure 512-byte LBA.**

Check active LBA format:

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

Expected: `Data Size: 512 bytes ... (in use)`.\
If not, switch to 512-byte LBA:

```bash
nvme format --lbaf=0 $TRIEDB_DRIVE
nvme id-ns -H $TRIEDB_DRIVE | grep 'LBA Format' | grep 'in use'
```

***

7. ### **Initialize TrieDB (monad-mpt).**

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

You should see output showing the MPT database on `/dev/nvme…` and `monad-mpt.service: Deactivated successfully.`

***

8. ### **Firewall Configuration.**

Open SSH and P2P port **8000** (TCP+UDP):

```bash
ufw allow ssh
ufw allow 8000
ufw enable
ufw status
```

Recommended extra protection against spammy small UDP packets:

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

Remember: iptables rules are lost after reboot unless you persist them (e.g. `iptables-persistent`).

To make the mitigation rule permanent under UFW, modify **before.rules**.

1. Open the UFW pre-rules file:

```bash
nano /etc/ufw/before.rules
```

2. Just **below this line**:

```
# End required lines
```

add:

```
# Drop tiny UDP packets to mitigate raptorcast flooding
-A ufw-before-input -p udp --dport 8000 -m length --length 0:1400 -j DROP
```

3. Reload UFW:

```bash
ufw reload
```

4. Verify that the rule is active:

```bash
sudo iptables -S ufw-before-input | grep 8000
```

**Expected output:**

```
-A ufw-before-input -p udp -m udp --dport 8000 -m length --length 0:1400 -j DROP
```

This ensures the anti-spam rule survives reboots and operates consistently.

***

9. ### **Install and Configure OTEL Collector (optional but recommended).**

```bash
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 otelcolcat
```

Monad metrics will be exposed at `http://0.0.0.0:8889/metrics`.

***

10. ### **Fetch Monad Config Files.**

```bash
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
```

***

11. ### **Set Keystore Password.**

Generate a strong random password and store it in `.env`:

```bash
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
```

***

12. ### **Generate Keystores (SECP + BLS).**

```bash
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
```

Back up off-node:

* `/opt/monad/backup/secp-backup`
* `/opt/monad/backup/bls-backup` [docs.monad.xyz](https://docs.monad.xyz/node-ops/full-node-installation)

***

13. ### **Update `node.toml` for a Public Full Node.**

Edit:

```bash
nano /home/monad/monad-bft/config/node.toml
```

Key fields:

```toml
# Block rewards recipient (burn address for public full node)
beneficiary = "0x0000000000000000000000000000000000000000"

# Unique name for your node / provider
node_name = "full_<PROVIDER>-<SUFFIX>"

[fullnode_raptorcast]
enable_client = true

[statesync]
expand_to_group = true

[blocksync_override]
# keep peers empty for public full nodes
```

***

14. ### Node Signature Record (Peer Discovery).

Sign your node’s name record with the SECP key:

```bash
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
```

The command prints `self_address`, `self_record_seq_num` and `self_name_record_sig`.\
Put them into the `peer_discovery` section of `node.toml`, for example:

```toml
self_address = "12.34.56.78:8000"
self_record_seq_num = 0
self_name_record_sig = "<LONG_HEX_SIGNATURE>"
```

15. ### Enable Remote Config Fetching (recommended).

In `/home/monad/.env` you can let the node auto-fetch the latest `validators.toml` and `forkpoint.toml` on startup:

```bash
cat >> /home/monad/.env << 'EOF'
REMOTE_VALIDATORS_URL='https://bucket.monadinfra.com/validators/mainnet/validators.toml'
REMOTE_FORKPOINT_URL='https://bucket.monadinfra.com/forkpoint/mainnet/forkpoint.toml'
EOF
```

***

16. ### **Optional: Enable Call Traces (Archive / RPC Nodes).**

For archive / RPC workloads add `--trace_calls` to the `monad-execution` service:

```bash
systemctl edit monad-execution
```

Drop-in override:

```ini
[Service]
Type=simple
ExecStart=
ExecStart=/usr/local/bin/monad \
    ... \
    --trace_calls \
    ...
```

***

17. ### **Optional: Tune Monad Cruft Retention.**

To control how long artifacts are kept (minutes), append to `/home/monad/.env`:

```bash
cat >> /home/monad/.env << 'EOF'
RETENTION_LEDGER=600      # default 10h
RETENTION_WAL=300         # default 5h
RETENTION_FORKPOINT=300   # default 5h
RETENTION_VALIDATORS=43200 # default 30d
EOF
```

`monad-cruft` runs hourly and will pick up changes automatically.

***

18. ### Start the Full Node.

Fix ownership:

```bash
chown -R monad:monad /home/monad/
```

Enable services at boot:

```bash
systemctl enable monad-bft monad-execution monad-rpc
```

Before first start, follow the [**Hard Reset Guide**](https://services.stakeangle.com/mainnets/monad/hard-reset-guide) to seed the node with a recent database.

Start services:

```bash
systemctl start monad-bft monad-execution monad-rpc
```

Check status and logs:

```bash
systemctl status monad-bft monad-execution monad-rpc
journalctl -u monad-bft -fo cat
journalctl -u monad-execution -fo cat
```


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://services.stakeangle.com/mainnets/monad/installation.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
