Why a VPS, and why pay for it in Bitcoin
A wallet that does not talk to your own node is trusting somebody else’s view of the chain and leaking your addresses to them. Running Bitcoin Core on a small VPS fixes both: it validates every block itself and your wallets query it directly. A VPS also gives you what a laptop does not: a static IP, a symmetric link in a datacenter, and a process that stays up when you close the lid.
Paying for that server in Bitcoin without creating an account means the node’s IP is not sitting next to your name in a billing database. That does not make the node anonymous on its own; the Tor section below handles the network side.
Disk, RAM and bandwidth sizing
The chain is the sizing problem. A pruned node validates every block exactly like a full node but discards old block files once they are spent, keeping only the UTXO set (the chainstate) plus a configurable window of recent blocks.
- Pruned, prune=10000: about 10 GB of blocks, 12 to 15 GB of chainstate, a few hundred MB of indexes. Around 30 GB total. This fits the 40 GB plan (from about $10/mo) with the OS and some headroom.
- Pruned with a bigger window: on a plan with 160 GB or more you can set prune=100000 and keep roughly 100 GB of recent blocks, enough to serve history to peers and rescan wallets going back years.
- Unpruned archival node: well over 700 GB and growing about 60 GB a year. That belongs on the largest storage tier or on a dedicated box, not a 40 GB VPS.
- RAM: 4 GB is comfortable with dbcache=1000. On a 2 GB plan keep dbcache at its default (450) and add 2 GB of swap; it works, just slower.
- Bandwidth: initial block download pulls the entire chain regardless of pruning, so budget roughly 750 GB of inbound traffic once. After sync, a listening node averages 5 to 20 GB per day depending on peers.
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 enableDownload and verify Bitcoin Core
Do not skip verification. Bitcoin Core releases are reproducibly built and signed by a dozen or so maintainers; the public keys live in the guix.sigs repository. Set VER to the current release shown at bitcoincore.org/en/download.
VER=30.0 # check https://bitcoincore.org/en/download/ for the current release
ARCH=$(uname -m) # x86_64 on the BVX plans, aarch64 on the Arm BVA plans
cd /tmp
curl -LO https://bitcoincore.org/bin/bitcoin-core-$VER/bitcoin-$VER-$ARCH-linux-gnu.tar.gz
curl -LO https://bitcoincore.org/bin/bitcoin-core-$VER/SHA256SUMS
curl -LO https://bitcoincore.org/bin/bitcoin-core-$VER/SHA256SUMS.asc
# Import the builder keys and check the signatures over SHA256SUMS.
apt install -y git
git clone --depth 1 https://github.com/bitcoin-core/guix.sigs
gpg --import guix.sigs/builder-keys/*.gpg
gpg --verify SHA256SUMS.asc SHA256SUMS
# Then check the tarball against the signed hash list.
sha256sum --ignore-missing --check SHA256SUMSYou want several lines of "Good signature from ..." (warnings about keys not being certified are expected; you have not signed them yourself) and exactly one "bitcoin-...-linux-gnu.tar.gz: OK". If either check fails, stop and download again from a different network.
tar xzf bitcoin-$VER-$ARCH-linux-gnu.tar.gz
install -m 0755 -o root -g root -t /usr/local/bin bitcoin-$VER/bin/bitcoind bitcoin-$VER/bin/bitcoin-cli
bitcoind --version | head -1System user, bitcoin.conf and a systemd unit
Run the daemon as its own unprivileged user with the data directory under /var/lib and the config under /etc. This mirrors the unit shipped in Bitcoin Core’s contrib/init directory, trimmed down.
useradd --system --home-dir /var/lib/bitcoind --shell /usr/sbin/nologin bitcoin
install -d -m 0710 -o bitcoin -g bitcoin /var/lib/bitcoind
install -d -m 0750 -o bitcoin -g bitcoin /etc/bitcoin
cat > /etc/bitcoin/bitcoin.conf <<'EOF'
server=1
txindex=0
prune=10000
dbcache=1000
maxconnections=40
# RPC only on localhost, cookie auth (the default) for local clients.
rpcbind=127.0.0.1
rpcallowip=127.0.0.1
# Accept inbound connections.
listen=1
EOF
chown bitcoin:bitcoin /etc/bitcoin/bitcoin.conf
chmod 0640 /etc/bitcoin/bitcoin.conf# /etc/systemd/system/bitcoind.service
[Unit]
Description=Bitcoin Core daemon
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
User=bitcoin
Group=bitcoin
ExecStart=/usr/local/bin/bitcoind -conf=/etc/bitcoin/bitcoin.conf -datadir=/var/lib/bitcoind
ExecStop=/usr/local/bin/bitcoin-cli -conf=/etc/bitcoin/bitcoin.conf -datadir=/var/lib/bitcoind stop
TimeoutStopSec=600
Restart=on-failure
RestartSec=30
# Hardening
PrivateTmp=true
ProtectSystem=full
ProtectHome=true
NoNewPrivileges=true
PrivateDevices=true
MemoryDenyWriteExecute=true
[Install]
WantedBy=multi-user.targetsystemctl daemon-reload
systemctl enable --now bitcoind
journalctl -u bitcoind -f # watch it find peers and start downloading headersRoute the node over Tor
Bitcoin Core can make all outbound connections through Tor and, if it can reach Tor’s control port, create and advertise its own onion service for inbound connections. Tor from the Ubuntu repository is fine for this.
apt install -y tor
cat >> /etc/tor/torrc <<'EOF'
ControlPort 9051
CookieAuthentication 1
CookieAuthFileGroupReadable 1
EOF
systemctl restart tor
# Let the bitcoin user read Tor's auth cookie.
usermod -aG debian-tor bitcoin
cat >> /etc/bitcoin/bitcoin.conf <<'EOF'
# Tor
proxy=127.0.0.1:9050
listenonion=1
torcontrol=127.0.0.1:9051
# Uncomment to refuse clearnet entirely:
# onlynet=onion
EOF
systemctl restart bitcoindWith proxy= set, every outbound connection goes through Tor’s SOCKS port, including DNS seed lookups. listenonion=1 asks Tor for an ephemeral v3 onion service forwarding to port 8333; the address is logged at startup and is visible under "localaddresses" in getnetworkinfo. If you keep clearnet inbound as well, open 8333/tcp in ufw; if you go onion-only, leave the firewall as it is.
# Only if you want clearnet peers to reach you too:
ufw allow 8333/tcp
# Confirm the onion address was created:
sudo -u bitcoin bitcoin-cli -conf=/etc/bitcoin/bitcoin.conf -datadir=/var/lib/bitcoind getnetworkinfo | grep -A3 localaddressesMonitor and maintain
getblockchaininfo is the one command to know. "blocks" versus "headers" shows sync progress, "verificationprogress" approaches 1.0, "pruned" and "pruneheight" confirm pruning is active, and "size_on_disk" tells you whether your sizing guess was right.
alias btc='sudo -u bitcoin bitcoin-cli -conf=/etc/bitcoin/bitcoin.conf -datadir=/var/lib/bitcoind'
btc getblockchaininfo
btc getnetworkinfo | grep -E '"connections|"version'
btc getpeerinfo | grep -c '"inbound": true'
df -h /var/lib/bitcoind
journalctl -u bitcoind --since "1 hour ago" | grep -i -E 'error|warn'Upgrading is the download-verify-install sequence again with a new VER, followed by systemctl restart bitcoind. Keep an eye on the pruned disk: if "size_on_disk" creeps toward the volume size, lower prune= or move up a plan. Back up only /var/lib/bitcoind/wallets if you created wallets on the node; the chain itself is replaceable.
Which plan to pick
For a pruned node, the 40 GB, 2 vCPU, 4 GB RAM plan (from about $10/mo) is the right size: it clears prune=10000 with room to spare and the extra RAM keeps dbcache healthy during initial sync. For a bigger pruning window or an archival node, step up to a plan with 160 GB or more and pick the region closest to the wallets that will use it. Configure a server, pay in Bitcoin, and the magic link with your root credentials arrives by email.