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.

Operator RunbookExact Commands, Safe Sequence, Human LogicMonad IdentityServer MigrationProduction DisciplineStep by stepReal commandsIdentity-safeRollback included
Before you startVariablesOld server checksNew server checksIdentity cutoverVerificationRollback
Build the new house first. Move the passport second. Never live in two houses with the same passport at the same time.

In Monad terms, the “passport” is your live node identity: the SECP keystore, the BLS keystore, the matching password, and a fresh signed discovery record for the new public IP.

Before you start

There are two different things people often confuse:

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

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.

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:

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:

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:

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

Final Prooflines 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.