Operator Runbook

Exact Commands, Safe Sequence, Human Logic

Monad server migration, written so you can actually execute it.

This guide is for the real moment when you need to move a Monad node from one server to another. It is written in plain language first, then in commands. If you follow the order exactly, the logic is simple: prepare the new server, prove it is healthy, stop the old identity, move the identity once, re-sign discovery for the new IP, then verify everything before switching public links.

Step by step Real commands Identity-safe Rollback included
Monad Identity Server Migration Production Discipline

Before you start

There are two different things people often confuse:

  • Preparing the new server so it can run Monad
  • Moving the real node identity from the old server to the new one

You can prepare the new server early. You should move the real identity only once, in a short and controlled window.

Do not break this rule

Never run the same id-secp and id-bls on two servers at the same time.

Fill these values first

Replace the placeholders below before you start typing commands.

OLD_HOST=195.3.223.117
OLD_USER=monad-admin

NEW_HOST=38.96.30.106
NEW_USER=admin

NEW_PUBLIC_IP=38.96.30.106
NEW_NODE_NAME=full_proofline-jp-01
NEW_SEQ=2

What files define the live Monad identity

  • /home/monad/monad-bft/config/id-secp
  • /home/monad/monad-bft/config/id-bls
  • /home/monad/.env with KEYSTORE_PASSWORD
  • /home/monad/monad-bft/config/node.toml

Step 1. Check the old server before cutover

First confirm that the current node is healthy and record its identity data. This gives you a clean before/after reference and makes rollback much easier.

1A. Check service health

sudo systemctl status monad-bft monad-execution monad-rpc --no-pager

You want all three services to be active (running).

1B. Check local RPC

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

curl -s -H "Content-Type: application/json" \
  --data '{"jsonrpc":"2.0","method":"eth_syncing","params":[],"id":1}' \
  http://127.0.0.1:8080

The node should answer. If the second command returns that the node is actively syncing or obviously unhealthy, stop and fix that first.

1C. Record identity and discovery values

hostnamectl
ip addr show
ip route

grep -E "node_name|self_address|self_record_seq_num|self_name_record_sig" \
  /home/monad/monad-bft/config/node.toml

1D. Record public keys

sudo -u monad bash -lc '
source /home/monad/.env
monad-keystore recover \
  --password "$KEYSTORE_PASSWORD" \
  --keystore-path /home/monad/monad-bft/config/id-secp \
  --key-type secp | grep "Secp public key"

monad-keystore recover \
  --password "$KEYSTORE_PASSWORD" \
  --keystore-path /home/monad/monad-bft/config/id-bls \
  --key-type bls | grep "BLS public key"
'

Step 2. Check the new server before identity migration

The new server should already be prepared and synced enough to be a real candidate. If it is not ready, this is not a migration window yet.

2A. Confirm basic host state

hostnamectl
uname -r
lsblk -o NAME,SIZE,TYPE,FSTYPE,MOUNTPOINT,MODEL
ip addr show
ip route
df -h

2B. Confirm required Monad paths exist

sudo ls -lah /home/monad
sudo ls -lah /home/monad/monad-bft/config
sudo ls -l /dev/triedb

2C. Confirm listeners and firewall

sudo ss -ltnup | egrep ":8000|:8001|:8080|:8889" || true
sudo ufw status || true

2D. Confirm Monad services can run

sudo systemctl daemon-reload
sudo systemctl list-unit-files | grep monad

Step 3. Save the identity material safely

Before the cutover window, make sure you have secure copies of the identity bundle from the old server.

  • id-secp
  • id-bls
  • .env
  • node.toml
  • backup-secret files if you use the recover/import path

If you copy files through a workstation, your local staging folder should look like this:

migration-staging/
  id-secp
  id-bls
  .env
  node.toml

Step 4. Cutover window: stop old identity first

This is the moment when you stop the old node completely. Do this before placing the real identity on the new server.

sudo systemctl stop monad-rpc
sudo systemctl stop monad-execution
sudo systemctl stop monad-bft

systemctl is-active monad-rpc monad-execution monad-bft

Expected result: all three should return inactive or failed, but not active.

Step 5. Copy the identity files to the new server

Place these files on the new server:

  • id-secp -> /home/monad/monad-bft/config/id-secp
  • id-bls -> /home/monad/monad-bft/config/id-bls
  • .env -> /home/monad/.env
  • edited node.toml -> /home/monad/monad-bft/config/node.toml

Example from a local workstation:

scp id-secp NEW_USER@NEW_HOST:/tmp/id-secp
scp id-bls NEW_USER@NEW_HOST:/tmp/id-bls
scp .env NEW_USER@NEW_HOST:/tmp/.env
scp node.toml NEW_USER@NEW_HOST:/tmp/node.toml
ssh NEW_USER@NEW_HOST

sudo mv /tmp/id-secp /home/monad/monad-bft/config/id-secp
sudo mv /tmp/id-bls /home/monad/monad-bft/config/id-bls
sudo mv /tmp/.env /home/monad/.env
sudo mv /tmp/node.toml /home/monad/monad-bft/config/node.toml

Step 6. Fix ownership and permissions

sudo chown monad:monad /home/monad/.env
sudo chown monad:monad /home/monad/monad-bft/config/id-secp
sudo chown monad:monad /home/monad/monad-bft/config/id-bls
sudo chown monad:monad /home/monad/monad-bft/config/node.toml

sudo chmod 600 /home/monad/.env
sudo chmod 600 /home/monad/monad-bft/config/id-secp
sudo chmod 600 /home/monad/monad-bft/config/id-bls
sudo chmod 600 /home/monad/monad-bft/config/node.toml

Do not skip this. Bad ownership and permissions are one of the easiest ways to break the cutover.

Step 7. Edit node.toml for the new server

Before starting Monad, confirm these values in the new server config:

  • node_name = "full_proofline-jp-01"
  • self_address = "38.96.30.106:8000"
  • self_record_seq_num = 2
  • self_name_record_sig must be replaced with a fresh signature for the new IP

Step 8. Generate a fresh signed name record for the new IP

sudo -u monad bash -lc '
source /home/monad/.env
monad-sign-name-record \
  --address 38.96.30.106:8000 \
  --authenticated-udp-port 8001 \
  --keystore-path /home/monad/monad-bft/config/id-secp \
  --password "$KEYSTORE_PASSWORD" \
  --self-record-seq-num 2
'

Copy the generated signature into the peer_discovery section of /home/monad/monad-bft/config/node.toml.

Then confirm the final values:

grep -E "self_address|self_record_seq_num|self_name_record_sig" \
  /home/monad/monad-bft/config/node.toml

Step 9. Start Monad on the new server

sudo systemctl start monad-bft
sudo systemctl start monad-execution
sudo systemctl start monad-rpc

sudo systemctl status monad-bft monad-execution monad-rpc --no-pager

Step 10. Verify immediately

Do not rush to update public links yet. First verify the node itself.

10A. Check local RPC again

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

curl -s -H "Content-Type: application/json" \
  --data '{"jsonrpc":"2.0","method":"eth_syncing","params":[],"id":1}' \
  http://127.0.0.1:8080

10B. Check listeners

ss -ltnup | egrep ":8000|:8001|:8080|:8889"

10C. Check recent logs

journalctl -u monad-bft -n 80 --no-pager
journalctl -u monad-execution -n 80 --no-pager
journalctl -u monad-rpc -n 80 --no-pager

10D. Confirm the public keys are still the same

sudo -u monad bash -lc '
source /home/monad/.env
monad-keystore recover \
  --password "$KEYSTORE_PASSWORD" \
  --keystore-path /home/monad/monad-bft/config/id-secp \
  --key-type secp | grep "Secp public key"

monad-keystore recover \
  --password "$KEYSTORE_PASSWORD" \
  --keystore-path /home/monad/monad-bft/config/id-bls \
  --key-type bls | grep "BLS public key"
'

10E. Watch for 30 minutes

systemctl show monad-bft monad-execution monad-rpc -p NRestarts

watch -n 5 "curl -s -H 'Content-Type: application/json' \
  --data '{\"jsonrpc\":\"2.0\",\"method\":\"eth_blockNumber\",\"params\":[],\"id\":1}' \
  http://127.0.0.1:8080"
About blockDifference

A small positive blockDifference like 1 or 2 can still be normal in real operation. Do not interpret it alone. Always look at service state, peer count, RPC health, and whether the local block height keeps moving.

Step 11. Only then switch public links

After the new node is clearly healthy, switch:

  • public Sentinel backend
  • public dashboard links
  • website status cards
  • public RPC reverse proxy or domain, if you use one

Rollback plan

If the new node cannot stay healthy, rollback should be immediate and boring.

  1. Stop Monad services on the new server
  2. Restore the old active configuration on the old server
  3. If needed, generate a fresh name record again for the old IP with a higher sequence number
  4. Start Monad services on the old server
  5. Point public links and public monitoring back to the old server

Rollback commands

# on the new server
sudo systemctl stop monad-rpc
sudo systemctl stop monad-execution
sudo systemctl stop monad-bft

# on the old server
sudo systemctl start monad-bft
sudo systemctl start monad-execution
sudo systemctl start monad-rpc

Success criteria

  • The old host is no longer running the identity
  • The new host is using the same SECP and BLS keys
  • The new name record matches the new IP
  • RPC is healthy
  • Peers are healthy
  • No obvious restart loop exists
  • Monitoring shows live data from the new host
  • Public-facing service continuity is preserved

Final Proofline principle

A good migration is boring. The new host is prepared first, the identity is moved once, the checks are explicit, the public cutover happens only after health is proven, and rollback remains possible until the new primary has already survived real observation time.