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.
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/.envwithKEYSTORE_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-secpid-bls.envnode.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-secpid-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 = 2self_name_record_sigmust 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"
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.
- Stop Monad services on the new server
- Restore the old active configuration on the old server
- If needed, generate a fresh name record again for the old IP with a higher sequence number
- Start Monad services on the old server
- 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.