Who this is for
This guide covers two jobs on one small server. The first is a remote node for your own wallets that nobody else can see: monerod with its restricted RPC bound to localhost and published only as a Tor onion service, so your phone and desktop wallets stop handing their IP address and refresh pattern to a stranger’s public node, and the node itself is never advertised. The second is monero-wallet-rpc, the JSON-RPC daemon a shop, donation page or invoicing script talks to in order to generate addresses and notice incoming payments, running on a view-only wallet so the server never holds a spend key.
The Monero node guide on this site builds a public node that serves other people. This one deliberately does not: no public-node flag, no clearnet RPC port, and a wallet daemon that can see money arrive but cannot move it. If an attacker gets into this VPS they learn your payment history, which is bad, and nothing else.
Sizing: 160 GB for a pruned chain
A pruned Monero chain is around 100 GB in October 2026 and grows a few GB a month, which rules out the 40 and 80 GB plans. monero-wallet-rpc adds very little: a view-only wallet cache of a few hundred MB and a few hundred MB of RAM. The initial scan of the chain for the wallet is CPU-bound and happens once.
- Disk: 160 GB is the sensible minimum; 240 GB if you want to leave it alone for a couple of years.
- RAM: monerod uses 1.5 to 3 GB during sync; 8 GB total is comfortable for both daemons.
- Bandwidth: about 100 GB inbound for the initial sync with sync-pruned-blocks (inbound is free). Afterwards a private node with capped peers moves a few GB a day.
- Plans: BVX31 (4 vCPU, 8 GB RAM, 160 GB NVMe, from $32/mo in Europe) or the Arm BVA31 (8 vCPU, 16 GB, 160 GB, $40/mo, EU only). Monero publishes linuxarm8 builds, so either works.
Prepare the server
Everything below assumes a fresh Ubuntu 24.04 image and a root shell (prefix sudo if you created a user). Patch first, then lock down SSH and the firewall before anything else is listening.
apt update && apt full-upgrade -y
apt install -y ufw unattended-upgrades curl gnupg
# SSH: keys only. Put your public key in /root/.ssh/authorized_keys first and
# confirm you can log in with it before disabling passwords.
cat > /etc/ssh/sshd_config.d/00-keys-only.conf <<'EOF'
PasswordAuthentication no
KbdInteractiveAuthentication no
PermitRootLogin prohibit-password
EOF
sshd -t && systemctl restart ssh
# Firewall: deny everything inbound except SSH for now.
ufw default deny incoming
ufw default allow outgoing
ufw allow OpenSSH
ufw --force enablemonerod: pruned, restricted RPC on localhost, broadcasts over Tor
Download and verify the Monero CLI exactly as described in the Monero node guide on this site: fetch the tarball from getmonero.org, import binaryFate’s key, verify the signed hashes.txt, confirm your tarball’s hash appears in it, and install monerod, monero-wallet-cli and monero-wallet-rpc into /usr/local/bin. Then create the monero user and this configuration. The only ports bound to a public address are the P2P port, which is harmless, and nothing else.
useradd --system --home-dir /var/lib/monero --shell /usr/sbin/nologin monero
install -d -m 0750 -o monero -g monero /var/lib/monero /var/log/monero /etc/monero
apt install -y tor
cat > /etc/monero/monerod.conf <<'EOF'
data-dir=/var/lib/monero
log-file=/var/log/monero/monerod.log
log-level=0
max-log-files=5
prune-blockchain=1
sync-pruned-blocks=1
p2p-bind-ip=0.0.0.0
p2p-bind-port=18080
out-peers=16
in-peers=32
limit-rate-up=2048
limit-rate-down=8192
# Unrestricted RPC for monero-wallet-rpc on this machine only.
rpc-bind-ip=127.0.0.1
rpc-bind-port=18081
# Restricted RPC for your wallets. Localhost only; Tor publishes it as an onion.
rpc-restricted-bind-ip=127.0.0.1
rpc-restricted-bind-port=18089
# Your own transaction broadcasts go out through Tor.
tx-proxy=tor,127.0.0.1:9050,16
no-zmq=1
EOF
chown monero:monero /etc/monero/monerod.conf
cat > /etc/systemd/system/monerod.service <<'EOF'
[Unit]
Description=Monero daemon
After=network-online.target tor.service
Wants=network-online.target
[Service]
Type=simple
User=monero
Group=monero
ExecStart=/usr/local/bin/monerod --config-file /etc/monero/monerod.conf --non-interactive
Restart=always
RestartSec=10
TimeoutStopSec=120
PrivateTmp=true
ProtectSystem=full
ProtectHome=true
NoNewPrivileges=true
PrivateDevices=true
[Install]
WantedBy=multi-user.target
EOF
systemctl daemon-reload
systemctl enable --now monerod
ufw allow 18080/tcp # P2P only. 18081 and 18089 stay closed.
tail -f /var/log/monero/monerod.logNote what is missing compared to a public node: no public-node=1, and the restricted RPC listens on 127.0.0.1 instead of 0.0.0.0. Peers see an ordinary pruned node on 18080; nobody can query it for wallet data over clearnet. Let the sync finish before going further; height and target_height in curl -s http://127.0.0.1:18081/get_info tell you how far along it is.
Publish the restricted RPC as an onion and connect your wallets
A v3 onion service pointing at port 18089 gives every wallet you own a stable address for the node that works from any network, hides the server’s IP from the wallet’s network, and hides the wallet’s IP from the server. Nothing needs to be opened in the firewall.
cat >> /etc/tor/torrc <<'EOF'
HiddenServiceDir /var/lib/tor/monero-rpc/
HiddenServiceVersion 3
HiddenServicePort 18089 127.0.0.1:18089
EOF
systemctl restart tor
cat /var/lib/tor/monero-rpc/hostname # YOURNODE.onion
# From your laptop, with Tor running locally on 9050:
monero-wallet-cli --proxy 127.0.0.1:9050 --daemon-address YOURNODE.onion:18089 --trusted-daemon --wallet-file mywallet
# Or skip Tor and tunnel the port over SSH instead:
ssh -N -L 18089:127.0.0.1:18089 [email protected]
monero-wallet-cli --daemon-address 127.0.0.1:18089 --trusted-daemon --wallet-file mywalletIn the Monero GUI or Feather, add a custom node with the onion hostname and port 18089 and enable the Tor proxy in the network settings; on Android, Monerujo and Cake Wallet accept onion node addresses when Orbot or their built-in Tor is on. --trusted-daemon is correct here because the node is yours. Tor also supports client authorization for onion services, where only wallets holding a matching key can even reach the address; the Tor Project’s onion service documentation covers it if you want that extra layer.
monero-wallet-rpc on a view-only wallet for a shop
A Monero wallet has two private keys. The view key reveals incoming payments and amounts; the spend key signs transactions. A view-only wallet is built from the primary address plus the private view key and can detect every payment to every subaddress, but cannot construct a spend. That is exactly what a payment server should hold. On the machine where your real shop wallet lives, open it in monero-wallet-cli and run the address and viewkey commands to get the two values; the seed and spend key never leave that machine.
useradd --system --home-dir /var/lib/monero-wallet --shell /usr/sbin/nologin walletrpc
install -d -m 0750 -o walletrpc -g walletrpc /var/lib/monero-wallet /var/log/monero-wallet /etc/monero-wallet
# Create the view-only wallet on the server. Prompts: primary address, private view key,
# a wallet password, and a restore height (the block height when the shop wallet was created).
sudo -u walletrpc monero-wallet-cli --generate-from-view-key /var/lib/monero-wallet/shop-view \
--daemon-address 127.0.0.1:18081 --trusted-daemon
# Type "exit" once it has created the wallet and started refreshing.
printf '%s' 'THE-WALLET-PASSWORD' > /etc/monero-wallet/password
openssl rand -hex 24 > /etc/monero-wallet/rpc-password
cat > /etc/monero-wallet/wallet-rpc.conf <<EOF
wallet-file=/var/lib/monero-wallet/shop-view
password-file=/etc/monero-wallet/password
daemon-address=127.0.0.1:18081
trusted-daemon=1
rpc-bind-ip=127.0.0.1
rpc-bind-port=18083
rpc-login=shop:$(cat /etc/monero-wallet/rpc-password)
log-file=/var/log/monero-wallet/wallet-rpc.log
log-level=0
EOF
chown -R walletrpc:walletrpc /etc/monero-wallet && chmod 0600 /etc/monero-wallet/*
cat > /etc/systemd/system/monero-wallet-rpc.service <<'EOF'
[Unit]
Description=Monero wallet RPC (view-only shop wallet)
Requires=monerod.service
After=monerod.service
[Service]
Type=simple
User=walletrpc
Group=walletrpc
ExecStart=/usr/local/bin/monero-wallet-rpc --config-file /etc/monero-wallet/wallet-rpc.conf
Restart=always
RestartSec=10
PrivateTmp=true
ProtectSystem=full
ProtectHome=true
NoNewPrivileges=true
[Install]
WantedBy=multi-user.target
EOF
systemctl daemon-reload
systemctl enable --now monero-wallet-rpcCredentials live in the config file rather than on the command line so they do not show up in the process list. The RPC uses HTTP digest authentication, so curl needs --digest. The shop pattern is one subaddress per order: create it with a label, show it with the amount due, then poll for transfers to that subaddress index and treat the order as paid at ten confirmations. Amounts are in piconero; 1 XMR is 1,000,000,000,000.
rpc() {
curl -s --digest -u "shop:$(cat /etc/monero-wallet/rpc-password)" \
-H 'Content-Type: application/json' http://127.0.0.1:18083/json_rpc -d "$1"
echo
}
# Is the wallet caught up with the daemon?
rpc '{"jsonrpc":"2.0","id":"0","method":"get_height"}'
# New subaddress for order 1042 (account 0). Returns "address" and "address_index".
rpc '{"jsonrpc":"2.0","id":"0","method":"create_address","params":{"account_index":0,"label":"order-1042"}}'
# Incoming transfers (confirmed and still in the mempool) to that subaddress index.
rpc '{"jsonrpc":"2.0","id":"0","method":"get_transfers","params":{"in":true,"pool":true,"account_index":0,"subaddr_indices":[7]}}'Operate: sync, upgrades, backups, firewall
curl -s http://127.0.0.1:18081/get_info | tr ',' '\n' | grep -E '"(height|target_height|synchronized|outgoing_connections_count)"'
systemctl status monerod monero-wallet-rpc --no-pager
tail -n 20 /var/log/monero-wallet/wallet-rpc.log
df -h /var/lib/monero
ufw status # expect only OpenSSH and 18080/tcpTwo heights matter. The daemon’s height against target_height tells you whether the node is synced; the wallet daemon’s get_height against the daemon’s height tells you whether the shop wallet has scanned every block. A payment is invisible to your application until the wallet has scanned the block containing it, so if the wallet-rpc log shows it lagging, look at the daemon first. The initial scan from the restore height is CPU-bound and can take an hour or more for a wallet created years ago; set the restore height accurately when you generate the view-only wallet and that cost is paid once.
Upgrade monerod and monero-wallet-rpc together: repeat the download and verification, stop both services, install, start both. Monero hard forks are announced well in advance on getmonero.org and a stale node stops syncing at the fork height, so read release announcements. There is nothing irreplaceable on this server: the chain re-syncs, and the view-only wallet is regenerated from the address and view key you keep offline. Back up /etc/monero-wallet so the RPC password and settings survive a rebuild, and extend the prepaid term before it runs out or the shop stops noticing payments.
Which plan to pick
BVX31 (4 vCPU, 8 GB RAM, 160 GB NVMe, from $32/mo) is the right size for a pruned private node plus the wallet daemon; BVA31 (8 vCPU, 16 GB, 160 GB, $40/mo) if you want faster initial scans and more headroom. Choose a European location unless your shop’s web server is in the US or Singapore. Configure a server, pay in Monero, and the root credentials arrive by magic link once the payment confirms.