Skip to content

Rippled Setup Runbook — Hetzner CPX32

Author: Vesper Date: 2026-04-22 Updated: 2026-04-22 — CPX32 (US-only SKU) replaced with CPX32 Nuremberg per Atlas ruling Atlas directive: "Treat rippled as infrastructure, not as an experiment. Boring setup, documented, health checks, clear separation from engine runtime, no improvising once the install starts." Atlas ruling: CPX32 Nuremberg approved — operational sufficiency + cost sanity + architectural alignment beats exact SKU symmetry. Status: Ready to execute


Overview

This runbook rescales the existing Hetzner CPX22 (neo-engine, 178.104.245.3, Nuremberg) to CPX32 and installs a stock rippled XRPL node for use as the NEO engine's local RPC endpoint. It replaces the external managed RPC dependency (QuikNode → public bridge → self-hosted).

What this is: A stock rippled node — keeps recent ledger history only (default 256 ledgers). Sufficient for all engine operations: account_offers, account_info, submit, account_tx.

What this is not: A full-history archive node. We do not need that.


Phase 0 — Pre-Setup Checklist

Complete all before touching the server.

  • Hetzner account access confirmed
  • SSH key pair ready (use existing key from CPX22 if available)
  • Public RPC bridge (s1.ripple.com) confirmed working (fallback during setup)
  • Engine is NOT running during server setup
  • neo_live_stage1.db is backed up locally before any migration steps

Note on CPX22: The existing CPX22 server (178.104.245.3) will be rescaled to CPX32 in-place. Same IP is retained. Nothing is deployed on CPX22, so there is no data to migrate.


Phase 1 — Rescale CPX22 → CPX32 on Hetzner

1.1 Rescale the server

In Hetzner Cloud console (console.hetzner.com):

  1. Navigate to Servers → neo-engine (CPX22, 178.104.245.3)
  2. Click the Rescale tab
  3. Select Regular Performance → x86 (AMD)
  4. Select CPX32 (4 vCPU, 8 GB RAM, 80 GB SSD)
  5. Leave "CPU and RAM only" unchecked — no disk expansion needed (disk stays at 80 GB, which is the CPX32 default)
  6. Click Rescale and confirm

Cost: ~$16.49/month + $0.60 IPv4 = ~$17.09/month (Hetzner Nuremberg) Downtime: Brief reboot — server powers off, rescales, powers back on. Typically 2–5 minutes. IP retained: 178.104.245.3 — same IP after rescale.

1.2 Wait for rescale to complete

Monitor the server status in the Hetzner console. Status will go: Running → Off (rescaling) → Running.

Note the server's IP address (178.104.245.3 — unchanged).

1.2 First login

ssh root@<NEW_SERVER_IP>

Confirm you are on the right server:

hostname && lsb_release -a && free -h && df -h

Expected: Ubuntu 22.04, ~7.7 GB RAM free, ~160 GB disk.


Phase 2 — Basic Server Hardening

Run once, takes ~5 minutes.

2.1 Update packages

apt-get update && apt-get upgrade -y

2.2 Set timezone to UTC

timedatectl set-timezone UTC
timedatectl status

2.3 Configure firewall (UFW)

rippled needs ports 51235 (peer protocol — other XRPL nodes connect here). RPC port 5005 stays localhost-only (not exposed).

ufw default deny incoming
ufw default allow outgoing
ufw allow ssh
ufw allow 51235/tcp    # XRPL peer protocol
ufw enable
ufw status

Do NOT expose port 5005 to the internet. Engine connects to localhost.

adduser neo
usermod -aG sudo neo
# Copy SSH key
mkdir -p /home/neo/.ssh
cp /root/.ssh/authorized_keys /home/neo/.ssh/
chown -R neo:neo /home/neo/.ssh
chmod 700 /home/neo/.ssh
chmod 600 /home/neo/.ssh/authorized_keys

Phase 3 — Install rippled

Official Ripple packages for Ubuntu 22.04. Do not install from source or unofficial repos.

3.1 Add Ripple apt repository

apt-get install -y apt-transport-https ca-certificates curl gnupg
curl -fsSL https://repos.ripple.com/repos/api/gpg/key/public | \
    gpg --dearmor > /usr/share/keyrings/ripple-archive-keyring.gpg

echo "deb [signed-by=/usr/share/keyrings/ripple-archive-keyring.gpg] \
    https://repos.ripple.com/repos/rippled-deb jammy stable" | \
    tee /etc/apt/sources.list.d/ripple.list

apt-get update

3.2 Install rippled

apt-get install -y rippled

Verify installation:

rippled --version

rippled is installed as a systemd service but NOT started yet. Do not start until configured.


Phase 4 — Configure rippled (Stock Node)

The default config file is at /etc/opt/ripple/rippled.cfg. We replace it with a clean stock config.

4.1 Back up the default config

cp /etc/opt/ripple/rippled.cfg /etc/opt/ripple/rippled.cfg.default

4.2 Write the stock node config

cat > /etc/opt/ripple/rippled.cfg << 'EOF'
# NEO Engine — stock rippled node config
# Author: Vesper | Date: 2026-04-22
# Stock node: recent ledger history only (no full archive)

[server]
port_rpc_admin_local
port_peer

[port_rpc_admin_local]
port = 5005
ip = 127.0.0.1
admin = 127.0.0.1
protocol = http

[port_peer]
port = 51235
ip = 0.0.0.0
protocol = peer

[node_size]
medium

[node_db]
type=NuDB
path=/var/lib/rippled/db/nudb
advisory_delete=0

[database_path]
/var/lib/rippled/db

[debug_logfile]
/var/log/rippled/debug.log

[sntp_servers]
time.windows.com
time.apple.com
time.nist.gov
pool.ntp.org

[validators_file]
/etc/opt/ripple/validators.txt

[rpc_startup]
{ "command": "log_level", "severity": "warning" }

[ssl_verify]
0
EOF

4.3 Verify validators file exists

ls /etc/opt/ripple/validators.txt

If missing (rare), reinstall the package: apt-get install --reinstall rippled

4.4 Set correct permissions

chown rippled:rippled /etc/opt/ripple/rippled.cfg
chmod 640 /etc/opt/ripple/rippled.cfg

Phase 5 — Start rippled and Monitor Sync

5.1 Enable and start the service

systemctl enable rippled
systemctl start rippled
systemctl status rippled

Status should show active (running).

5.2 Watch the log for sync progress

journalctl -u rippled -f

What you will see: - Initial: LedgerConsensus: Syncing... — normal, syncing from network - Progress: NetworkOPs: STATE->Tracking — connected to peers, downloading ledger - Healthy: NetworkOPs: STATE->Fullfully synced, ready to serve RPC

Expected sync time: 2–6 hours on CPX32 with NVMe storage and Hetzner's network. Do not interrupt.

5.3 Check peer connections during sync

rippled peers | python3 -m json.tool | grep -c '"ip"'

Should show 5–20 peers within a few minutes of starting. If 0 peers after 10 minutes, check firewall (port 51235).

5.4 Check sync state

rippled server_info | python3 -m json.tool | grep -A5 '"server_state"'

States in order: disconnectedconnectedsyncingtrackingfull

Only proceed to Phase 6 when state = full.


Phase 6 — Validate RPC Endpoint

Run these from the server itself (localhost only).

6.1 Basic connectivity check

curl -s -X POST http://127.0.0.1:5005 \
  -H "Content-Type: application/json" \
  -d '{"method":"server_info","params":[{}]}' | \
  python3 -m json.tool | grep server_state

Expected: "server_state": "full"

6.2 Account info check (use the NEO wallet address)

curl -s -X POST http://127.0.0.1:5005 \
  -H "Content-Type: application/json" \
  -d '{
    "method": "account_info",
    "params": [{
      "account": "rPzFU5zgphszGzC86GfTbZ8nkVGe3Trmn4",
      "ledger_index": "current"
    }]
  }' | python3 -m json.tool | grep -E "Balance|account"

Expected: shows current XRP balance (in drops, divide by 1,000,000 for XRP).

6.3 Fee check

curl -s -X POST http://127.0.0.1:5005 \
  -H "Content-Type: application/json" \
  -d '{"method":"fee","params":[{}]}' | \
  python3 -m json.tool | grep median_fee

Expected: a reasonable fee value (not null, not error).

All three checks must pass before the engine is pointed at this node.


Phase 7 — Health Check Setup

Add a simple health check alias for ongoing monitoring.

cat >> /root/.bashrc << 'EOF'

# rippled health check
alias rip-status='rippled server_info | python3 -m json.tool | grep -E "server_state|complete_ledgers|peers|uptime"'
alias rip-log='journalctl -u rippled -n 50 --no-pager'
EOF
source /root/.bashrc

Usage: - rip-status — quick state check - rip-log — last 50 log lines

7.1 Confirm systemd auto-restart is enabled

systemctl is-enabled rippled

Expected: enabled — rippled restarts automatically on reboot or crash.


Phase 8 — Update Engine Config

Once Phase 6 validation passes, update the engine to use the local node.

8.1 Update config_live_stage1.yaml

Change the RPC URLs in config/config_live_stage1.yaml on Katja's local machine (and later on the VPS copy):

rpc_url: "http://127.0.0.1:5005"
websocket_url: "wss://127.0.0.1:6006"   # if websocket needed; use ws:// for localhost

Note: The local rippled endpoint is HTTP (not HTTPS). This is correct and intentional for localhost — TLS adds no security value for loopback traffic.

8.2 Verify engine can reach the node

Run the realignment dry-run from the repo (once engine is on the VPS or while still local with SSH tunnel):

python tools/realign_inventory_to_onchain.py \
  --config config/config_live_stage1.yaml \
  --db neo_live_stage1.db \
  --dry-run

Expected: connects cleanly, shows current on-chain balances. No connection errors.


Phase 9 — Decommission CPX22

Once CPX32 is running and the engine is migrated:

  1. Confirm all data is copied off CPX22
  2. Take a final snapshot in Hetzner console (optional, cheap insurance)
  3. Delete CPX22 server in Hetzner console

Do not delete CPX22 until at least one full clean session has run on CPX32.


Troubleshooting

Symptom Likely cause Fix
rippled not starting Config syntax error rippled --conf /etc/opt/ripple/rippled.cfg to see error
0 peers after 10 min Firewall blocking 51235 ufw allow 51235/tcp && ufw reload
Stuck in syncing > 6 hours Network issue or bad peers Restart service: systemctl restart rippled
RPC returns error Node not yet full Wait for sync; check rip-status
Engine can't connect Wrong URL or port Confirm http:// (not https://) and port 5005

Reference

  • Ripple official install docs: https://xrpl.org/install-rippled-on-ubuntu.html
  • rippled config reference: https://xrpl.org/rippled-server-config.html
  • Server states: https://xrpl.org/rippled-server-states.html

Vesper — 2026-04-22 Atlas-approved: Option C, CPX32, stock node