Run a Lightning node (LND) on a VPS over Tor and pay for it in Bitcoin

· 9 min read

LND on an Ubuntu 24.04 VPS with a pruned Bitcoin Core backend: sizing, signed-release checks, systemd, Tor-only, channel backups off-box, watchtowers.

Why a Lightning node belongs on a VPS, and what it costs you

Lightning punishes downtime in a way Bitcoin does not. A peer that cannot reach your node for long enough will force-close the channel, which costs on-chain fees and locks the funds for the CSV delay. A node that is offline while a counterparty publishes an old state cannot penalise it. Routing fees, if you want them, go to nodes that are always up with a stable address. A VPS in a datacenter gives you both; a laptop or a single-board computer behind home NAT gives you neither.

The honest tradeoff: an LND node is a hot wallet, and a rented virtual machine is hardware you do not control. Anyone with console access to the host could in principle read the disk. Keep on the node only what you would keep in a phone wallet, run everything over Tor so the node’s IP is not its identity, and keep the static channel backup somewhere else so a deleted server is an inconvenience rather than a loss. Paying in Bitcoin without an account means no billing record ties your name to the node’s IP; the Tor section does the rest.

Sizing: why 80 GB is the floor

Three things take disk: the pruned Bitcoin chain, LND’s own databases, and logs. A pruned node alone fits on a 40 GB plan, but once LND and its headroom are added the numbers stop working.

ComponentDiskNotes
Bitcoin Core, prune=20000about 40 GB20 GB of recent blocks plus the 15 GB chainstate and indexes. A bigger window means LND fetches fewer old blocks from peers.
LND channel.db and graph1 to 10 GBGrows with payment and forwarding history. Busy routing nodes reach several GB; compaction on restart keeps it in check.
Logs, Tor state, OS5 to 8 GBUbuntu 24.04 base image plus journald.
Headroom20 GB or moreInitial block download briefly needs more than the steady state, and database compaction needs room for a second copy of channel.db.

That is roughly 60 GB with a sensible margin, so the 80 GB plans are the floor: BVX21 (3 vCPU, 4 GB RAM, 80 GB NVMe, from $18/mo in Europe) or the Arm BVA21 (4 vCPU, 8 GB RAM, 80 GB NVMe, $20/mo, EU only). Both Bitcoin Core and LND publish aarch64 builds, so the Arm plan is a real option and its extra RAM is welcome: bitcoind with dbcache=1000 plus LND holding the channel graph in memory sits around 2.5 GB.

  • Bandwidth: initial block download pulls about 750 GB inbound once (inbound traffic is free). After sync, a Tor-only node moves a few GB a day, well inside the 20 TB included.
  • CPU: 2 to 4 shared vCPUs are enough. Initial sync is the only CPU-heavy phase and takes one to three days; over Tor alone it is slower, so consider syncing over clearnet first and switching to onlynet=onion afterwards.
  • RAM: 4 GB works with dbcache=1000. On 8 GB you can raise dbcache during initial sync and lower it afterwards.

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 enable

Bitcoin Core: pruned, ZMQ, Tor-only

Install Bitcoin Core exactly as in the Bitcoin node guide on this site: download the release from bitcoincore.org, verify SHA256SUMS against the guix.sigs builder keys, install bitcoind and bitcoin-cli, and create the bitcoin user and systemd unit. Only the configuration differs. LND needs ZMQ notifications for blocks and transactions, and since LND 0.13 it works against a pruned backend by fetching any missing old block from bitcoind’s peers. rpccookieperms=group (Bitcoin Core 28 and later) lets a second system user in the bitcoin group read the RPC cookie, which is how LND will authenticate; the 0710 mode on the data directory already lets group members traverse it.

# /etc/bitcoin/bitcoin.conf
server=1
prune=20000
dbcache=1000
maxconnections=40
txindex=0

# RPC on localhost with cookie auth. The lnd user is in the bitcoin group.
rpcbind=127.0.0.1
rpcallowip=127.0.0.1
rpccookieperms=group

# ZMQ notifications for LND
zmqpubrawblock=tcp://127.0.0.1:28332
zmqpubrawtx=tcp://127.0.0.1:28333

# Tor only: outbound through the SOCKS port, inbound via an onion service.
proxy=127.0.0.1:9050
listen=1
listenonion=1
onlynet=onion
torcontrol=127.0.0.1:9051
apt install -y tor
cat >> /etc/tor/torrc <<'EOF'
ControlPort 9051
CookieAuthentication 1
CookieAuthFileGroupReadable 1
EOF
systemctl restart tor

usermod -aG debian-tor bitcoin
systemctl restart bitcoind
ls -l /var/lib/bitcoind/.cookie    # expect -rw-r----- bitcoin bitcoin

sudo -u bitcoin bitcoin-cli -conf=/etc/bitcoin/bitcoin.conf -datadir=/var/lib/bitcoind getblockchaininfo | grep -E '"(blocks|headers|pruned|verificationprogress)"'

You can install LND while the chain is still syncing; it waits for bitcoind to report itself synced before it does anything that matters. If you want the first sync to go faster, leave onlynet=onion commented out until verificationprogress reaches 1.0, then enable it and restart.

Download and verify LND

LND ships release binaries together with a manifest of SHA-256 hashes signed by the release manager. Import the signing key from the lnd repository, verify the manifest signature, then check your tarball against the manifest. The release notes list the key fingerprint (60A1 FA7D A5BF F08B DCBB E790 3BBD 59E9 9B28 0306 at the time of writing); compare against the release page rather than against this text. Set VER to the current stable tag on the LND releases page and skip release candidates.

VER=v0.21.4-beta   # check https://github.com/lightningnetwork/lnd/releases
case "$(uname -m)" in
  x86_64)  ARCH=amd64 ;;
  aarch64) ARCH=arm64 ;;
esac
cd /tmp
BASE=https://github.com/lightningnetwork/lnd/releases/download/$VER
curl -LO $BASE/lnd-linux-$ARCH-$VER.tar.gz
curl -LO $BASE/manifest-$VER.txt
curl -LO $BASE/manifest-roasbeef-$VER.sig

curl -L https://raw.githubusercontent.com/lightningnetwork/lnd/master/scripts/keys/roasbeef.asc | gpg --import
gpg --verify manifest-roasbeef-$VER.sig manifest-$VER.txt     # expect "Good signature from Olaoluwa Osuntokun"
grep lnd-linux-$ARCH-$VER.tar.gz manifest-$VER.txt | sha256sum --check   # expect exactly one OK

tar xzf lnd-linux-$ARCH-$VER.tar.gz
install -m 0755 -o root -g root -t /usr/local/bin lnd-linux-$ARCH-$VER/lnd lnd-linux-$ARCH-$VER/lncli
lnd --version

The manifest covers every platform, so the grep line hands only your tarball’s entry to sha256sum. If either check fails, stop and download again from a different network. The release notes also publish OpenTimestamps proofs for the manifest if you want to confirm when it was signed.

lnd.conf and a systemd unit

Run LND as its own system user with the data directory under /var/lib and the config under /etc. The lnd user joins the bitcoin group to read the RPC cookie and the debian-tor group to read Tor’s control cookie, which LND needs to create its onion service. listen=localhost keeps the clearnet port closed; inbound connections arrive through the onion service only.

useradd --system --home-dir /var/lib/lnd --shell /usr/sbin/nologin lnd
install -d -m 0750 -o lnd -g lnd /var/lib/lnd /etc/lnd
usermod -aG bitcoin lnd
usermod -aG debian-tor lnd
# /etc/lnd/lnd.conf
[Application Options]
alias=YOUR-ALIAS
color=#ff9900
debuglevel=info
maxpendingchannels=2
minchansize=100000
accept-keysend=true

# Auto-unlock after reboot. Ignored on first start while no wallet exists yet.
wallet-unlock-password-file=/etc/lnd/password.txt
wallet-unlock-allow-create=true

# Tor-only: no clearnet listener. The onion service provides inbound.
listen=localhost
rpclisten=localhost:10009
restlisten=localhost:8080

[Bitcoin]
bitcoin.mainnet=true
bitcoin.node=bitcoind

[Bitcoind]
bitcoind.dir=/var/lib/bitcoind
bitcoind.config=/etc/bitcoin/bitcoin.conf
bitcoind.rpccookie=/var/lib/bitcoind/.cookie
bitcoind.rpchost=127.0.0.1
bitcoind.zmqpubrawblock=tcp://127.0.0.1:28332
bitcoind.zmqpubrawtx=tcp://127.0.0.1:28333
bitcoind.pruned-node-max-peers=4

[tor]
tor.active=true
tor.v3=true
tor.streamisolation=true
tor.socks=127.0.0.1:9050
tor.control=127.0.0.1:9051
tor.privatekeypath=/var/lib/lnd/v3_onion_private_key

[wtclient]
wtclient.active=true
# /etc/systemd/system/lnd.service
[Unit]
Description=LND Lightning Network Daemon
Requires=bitcoind.service
After=bitcoind.service tor.service

[Service]
Type=notify
User=lnd
Group=lnd
ExecStart=/usr/local/bin/lnd --configfile=/etc/lnd/lnd.conf --lnddir=/var/lib/lnd
Restart=on-failure
RestartSec=60
TimeoutStartSec=infinity
TimeoutStopSec=1800
LimitNOFILE=128000

PrivateTmp=true
ProtectSystem=full
ProtectHome=true
NoNewPrivileges=true
PrivateDevices=true

[Install]
WantedBy=multi-user.target
chown -R lnd:lnd /etc/lnd
systemctl daemon-reload
systemctl enable --now lnd
journalctl -u lnd -f        # wait for "Waiting for wallet encryption password"

# In a second shell: create the wallet. Write the 24 words down, offline.
sudo -u lnd lncli --lnddir=/var/lib/lnd create

# Then store the wallet password so lnd unlocks itself after a reboot.
printf '%s' 'YOUR-WALLET-PASSWORD' > /etc/lnd/password.txt
chown lnd:lnd /etc/lnd/password.txt && chmod 0400 /etc/lnd/password.txt

lncli create asks for a wallet password, whether you have an existing cipher seed (no), and an optional seed passphrase, then prints a 24-word aezeed. That seed recovers on-chain funds and, together with the channel backup file, lets you recover funds locked in channels. It does not recover channels themselves. Once the wallet is created, lncli getinfo should show your onion address under uris after a minute or two.

Channel backups off-box, and a watchtower

LND rewrites channel.backup every time a channel opens or closes. It is encrypted with a key derived from your seed and it is the only thing, besides the seed, that you need to get funds out of channels if this server disappears. Recovery means running lncli restorechanbackup on a new node, which asks each peer to force-close and sweeps the funds to your wallet. The file has to live somewhere that is not this VPS. The simplest arrangement is a pull from a machine you already trust, on a timer.

# On your laptop or home server, every hour from cron:
scp -q root@NODE_IP:/var/lib/lnd/data/chain/bitcoin/mainnet/channel.backup \
    ~/lnd-backups/channel-$(date +%F-%H).backup

# Or, on the node, export a fresh copy on demand:
sudo -u lnd lncli --lnddir=/var/lib/lnd exportchanbackup --all --output_file /var/lib/lnd/channel-export.backup

If you prefer push, install inotify-tools on the node and run a small loop that scp’s the file to another host whenever it changes; the lnd user then needs its own SSH key. Either way, test the backup once by reading it back with lncli verifychanbackup.

A watchtower covers the other failure mode: your node is offline while a peer broadcasts an old channel state. wtclient.active=true above enables the client; it does nothing until you add a tower. Towers are run by other node operators and reachable over Tor, which is why the onion-only setup is not a problem here.

# Add a tower (ask a friend who runs one, or use one published by a node you trust):
sudo -u lnd lncli --lnddir=/var/lib/lnd wtclient add TOWER_PUBKEY@TOWER_ONION_ADDRESS.onion:9911
sudo -u lnd lncli --lnddir=/var/lib/lnd wtclient towers

Operate: upgrades, monitoring, firewall, and the expiry date

alias lncli='sudo -u lnd lncli --lnddir=/var/lib/lnd'
lncli getinfo | grep -E '"(synced_to_chain|synced_to_graph|num_active_channels|num_peers|block_height)"'
lncli state
lncli walletbalance && lncli channelbalance
df -h /var/lib/lnd /var/lib/bitcoind
journalctl -u lnd --since "1 hour ago" | grep -iE 'error|warn'

Upgrading LND is the download-and-verify sequence again: systemctl stop lnd, install the new binaries, systemctl start lnd. Read the release notes first; database migrations run automatically and are one-way, so downgrading afterwards is not supported. Bitcoin Core upgrades follow the Bitcoin node guide. Ubuntu’s unattended-upgrades handles the OS.

The firewall from the preparation step stays as it is. A Tor-only node needs nothing inbound except SSH; ports 9735, 10009 and 8080 are bound to localhost and never exposed. If you later want clearnet peers as well, change listen to 0.0.0.0:9735, set externalip, and ufw allow 9735/tcp. Watch the prepaid term from the magic link page, extend before the reminder email turns into a deletion notice, and keep channel.backup copies current.

Which plan to pick

For a personal node with a handful of channels, BVX21 (3 vCPU, 4 GB RAM, 80 GB NVMe, from $18/mo) is the floor and BVA21 (4 vCPU, 8 GB RAM, 80 GB NVMe, $20/mo, EU only) is the better buy because of the RAM. If you intend to route seriously and expect channel.db to grow, step up to 160 GB. Pick the location nearest your main peers. Configure a server, pay in Bitcoin for as many days as you want it to live, and the root credentials arrive by magic link once the payment confirms.

Ready to run this yourself?

Linux KVM servers with NVMe storage in the EU, US and Singapore. Root access arrives by magic link; no account, no KYC.

Configure a server — pay with Bitcoin, no account needed