Skip to content

Self-host BTCPay Server

This guide documents the self-hosted BTCPay Server path used by the Sprey Processing reference deployment. Its goal is to become a reproducible production runbook: an operator should be able to deploy, verify, maintain, recover, and eventually reproduce the service from a clean VPS without relying on undocumented memory.

The canonical reference deployment is pay.sprey.win on the sprey-btcpay server.

The currently verified host baseline is:

Item Reference value
Provider Hetzner Cloud
Hostname sprey-btcpay
OS Ubuntu 26.04.1 LTS
CPU 4 vCPU
Memory 8 GB RAM; approximately 5.9 GiB available at the verified checkpoint
Disk 80 GB SSD; root filesystem approximately 75 GiB usable and 57% used at the verified checkpoint
Swap 4 GiB /swapfile; vm.swappiness=10
Public firewall Hetzner Cloud Firewall
Allowed inbound 22/tcp and ICMP
Host UFW Inactive
Automatic security updates Enabled with unattended-upgrades
Automatic reboot Not enabled
Public BTCPay endpoint pay.sprey.win
Administrative endpoint adminpay.sprey.win behind Cloudflare Access
Web ingress Cloudflare Tunnel
BTCPay Server v2.4.3
Bitcoin network Mainnet
Bitcoin node mode Pruned and synchronized
Bitcoin prune target 25,000 MiB
Bitcoin Docker volume Approximately 37.73 GB at the verified checkpoint
Lightning None for the initial reference configuration
SMTP Configured
Provider backups Hetzner Backups enabled

This table describes the current Sprey server; it is not yet a claim that every value is a minimum BTCPay requirement.

The runbook is organized around the path used to build and validate a production instance:

clean VPS
|
v
Ubuntu baseline
|
v
SSH + updates + swap
|
v
Docker + BTCPay Server
|
v
Cloudflare Tunnel + DNS
|
v
public endpoint + protected admin endpoint
|
v
external origin firewall
|
v
Bitcoin node + NBXplorer
|
v
pruning + synchronization
|
v
SMTP + backups
|
v
merchant wallet
|
v
invoice
|
v
real payment
|
v
observed invoice state
|
v
backup and recovery test

The Sprey reference server was provisioned on Hetzner Cloud with Ubuntu 26.04 LTS, 4 vCPU, 8 GB RAM, and an 80 GB SSD.

Before using these values as sizing guidance for another deployment, account for Bitcoin pruning policy, enabled payment methods, plugins, expected merchant count, traffic, monitoring, and backup strategy.

Verification: the host boots normally and remote administrative access is available.

The verified reference host has:

  • hostname sprey-btcpay;
  • 4 GiB swap at /swapfile;
  • vm.swappiness=10, persisted in /etc/sysctl.d/99-sprey-memory.conf;
  • IPv4 and IPv6 available;
  • unattended-upgrades enabled and running;
  • APT daily and daily-upgrade timers enabled;
  • automatic reboot not enabled.

At the latest memory checkpoint, free -h reported approximately 7.6 GiB total RAM, 1.7 GiB used, 5.9 GiB available, and about 32 MiB of the 4 GiB swap in use. Small non-zero swap usage is not treated as a fault while ample RAM remains available.

The swap configuration used on the reference host is:

/swapfile none swap sw 0 0

The memory policy is persisted separately:

/etc/sysctl.d/99-sprey-memory.conf
vm.swappiness=10

Useful verification commands are:

Terminal window
sysctl vm.swappiness
swapon --show
free -h

At the latest maintenance checkpoint all available APT updates were installed, no packages remained upgradable, and /var/run/reboot-required was absent.

The Ubuntu baseline above is verified on the reference host. The initial BTCPay deployment itself was then performed on this clean VPS using a script generated by the official BTCPay Server Configurator.

The initial BTCPay Server deployment was performed and verified on the clean reference VPS using the script generated by the official BTCPay Server Configurator and the standard installation entry point:

Terminal window
cd ~/btcpayserver-docker
. ./btcpay-setup.sh -i

The generated installation script cloned or updated the official btcpayserver/btcpayserver-docker repository, exported the selected BTCPay environment, and invoked the standard setup path. Subsequent Sprey-specific configuration is documented below in the order in which it was applied and verified.

BTCPay Server is running on the reference host using the official Docker-based deployment stack. The verified BTCPay version at this checkpoint is v2.4.3.

The active stack includes BTCPay Server, NBXplorer, Bitcoin Core, PostgreSQL, nginx, Tor support containers, and the BTCPay-managed Cloudflare Tunnel container.

The reference environment includes these verified settings:

BTCPAY_HOST=pay.sprey.win
BTCPAY_ADDITIONAL_HOSTS=adminpay.sprey.win
BTCPAYGEN_CRYPTO1=btc
BTCPAYGEN_LIGHTNING=none
BTCPAYGEN_REVERSEPROXY=nginx
BTCPAYGEN_ADDITIONAL_FRAGMENTS=opt-save-storage-xs;opt-add-cloudflared
BTCPAYGEN_EXCLUDE_FRAGMENTS=nginx-https
NBITCOIN_NETWORK=mainnet

Secrets such as the Cloudflare Tunnel token are intentionally omitted.

Existing installations should use the official BTCPay update mechanism rather than manually replacing generated containers or individual images.

The Sprey reference deployment uses a Cloudflare Tunnel named sprey-btcpay. The tunnel is established outbound from the server by the BTCPay-managed cloudflared container.

Two published application routes are configured:

pay.sprey.win -> http://nginx
adminpay.sprey.win -> http://nginx

The public hostname is therefore not routed to the server IP with a conventional public web-origin A record. Cloudflare provides the public HTTPS edge and sends traffic through the Tunnel to nginx.

The generated BTCPay deployment excludes the nginx-https fragment. At the verified checkpoint the host publishes nginx on port 80 for the internal Tunnel target, while port 443 is not publicly bound by Docker.

Verification:

  • the Cloudflare Tunnel reports healthy;
  • pay.sprey.win opens the BTCPay service over HTTPS;
  • adminpay.sprey.win reaches the same BTCPay deployment after Access authentication;
  • the BTCPay login methods continue to work after the edge configuration is applied.

5. Protect the administrative hostname with Cloudflare Access

Section titled “5. Protect the administrative hostname with Cloudflare Access”

pay.sprey.win remains public. Do not place the public Processing hostname behind an interactive Access login: invoices, merchant integrations, APIs, webhooks, and customer traffic need to reach BTCPay normally.

adminpay.sprey.win is the protected administrative hostname. It is configured as an additional BTCPay host and as a Cloudflare Access self-hosted application.

The verified Access policy uses:

Action: Allow
Include: Emails -> <specific administrative email>

Use the Emails selector for a specific address. Do not confuse it with Emails ending in, which matches an email domain. The actual administrative email is private and is not recorded in this public runbook.

Verification: open adminpay.sprey.win in a private/incognito browser session. Cloudflare Access must appear before BTCPay, the authorized address must be able to complete authentication, and pay.sprey.win must remain publicly reachable without the Access challenge.

6. Disable Rocket Loader for BTCPay hostnames

Section titled “6. Disable Rocket Loader for BTCPay hostnames”

On the reference deployment Cloudflare Rocket Loader broke BTCPay login JavaScript under BTCPay’s Content Security Policy. Browser diagnostics showed Rocket Loader involvement, and disabling Rocket Loader restored the affected BTCPay login-code flow.

The verified solution is a Cloudflare Configuration Rule named:

BTCPay - Disable Rocket Loader

It matches either BTCPay hostname:

(http.host eq "pay.sprey.win" or http.host eq "adminpay.sprey.win")

and applies only:

Rocket Loader: Off

Global Rocket Loader remains enabled for the rest of the sprey.win zone.

Verification: after deploying the Configuration Rule and re-enabling global Rocket Loader, the BTCPay login-code flow on pay.sprey.win and the Cloudflare Access plus BTCPay login flow on adminpay.sprey.win were both tested successfully.

Do not disable unrelated Cloudflare features in this rule. Its purpose is deliberately narrow: one compatibility exception for the two BTCPay hostnames.

The reference host publishes Docker nginx on 0.0.0.0:80 and [::]:80. UFW is inactive, the host INPUT policy was observed as ACCEPT, and the DOCKER-USER chain contained no custom filtering rules.

Because Docker-published ports require special care with host firewall rules, the Sprey reference deployment uses an external Hetzner Cloud Firewall as the public perimeter.

Verified inbound rules are:

Protocol Port Source
TCP 22 Any IPv4 and Any IPv6
ICMP Any IPv4 and Any IPv6

No inbound rule allows ports 80 or 443. Hetzner therefore drops unsolicited inbound web traffic before it reaches the server, while the outbound Cloudflare Tunnel remains operational. No outbound firewall rules are configured at this checkpoint.

After applying the firewall, verify all of the following before closing the existing SSH session:

  1. The existing SSH session remains connected.
  2. A new SSH connection can be established.
  3. pay.sprey.win still works through the Tunnel.
  4. adminpay.sprey.win still presents Cloudflare Access and reaches BTCPay after authentication.
  5. Direct access to the server IP on port 80 no longer succeeds externally.

This is the verified origin-protection boundary for the current reference deployment.

The reference deployment uses Bitcoin mainnet with a pruned Bitcoin node. Bitcoin Core synchronization and pruning were verified directly through the BTCPay Docker bitcoin-cli.sh helper using getblockchaininfo.

At the verified checkpoint Bitcoin Core reported:

chain: main
blocks: 965166
headers: 965166
verificationprogress: 0.9999911966066127
initialblockdownload: false
size_on_disk: 26083586256
pruned: true
pruneheight: 951074
automatic_pruning: true
prune_target_size: 26214400000
warnings: []

This verifies that the reference node is on mainnet, block and header heights are equal at the checkpoint, initial block download is complete, automatic pruning is active with a 25 GiB target, and Bitcoin Core reports no warnings.

The complete Bitcoin Docker volume can be larger than the pruning target because the target does not represent every file in the Bitcoin data directory.

NBXplorer remains part of the BTCPay Bitcoin backend, but a separate NBXplorer health check is not a merchant-facing product milestone. Its behavior is exercised through the real Store → Wallet → Invoice → Payment flow; component-level inspection remains available for troubleshooting when required.

For routine Bitcoin diagnostics, prefer the BTCPay Docker installation’s bitcoin-cli.sh helper over manually extracting RPC credentials.

Lightning is intentionally not part of the initial reference payment path.

Sprey Processing is designed to support merchant-controlled external Lightning infrastructure rather than making a Sprey-operated Lightning node a prerequisite for the core service. Customer-specific node deployment may be treated separately from the base Processing service.

The first production verification therefore uses Bitcoin on-chain.

SMTP is configured on the current BTCPay/Processing deployment.

The exact provider settings, sender identities, ports, TLS mode, and secrets must not be copied into public documentation. A future verified procedure should document the required fields, safe secret handling, and a test-email verification step without exposing credentials.

11. Enable infrastructure and application backups

Section titled “11. Enable infrastructure and application backups”

Hetzner Backups are enabled for sprey-btcpay, providing a provider-level recovery layer for the VPS.

An application-level legacy BTCPay backup checkpoint has also been completed. The existing backup.sh script performed a PostgreSQL dump, created the local backup archive at /var/lib/docker/volumes/backup_datadir/_data/backup.tar.gz, stopped and restarted the BTCPay containers as part of its procedure, and completed successfully. After the restart, the public production health endpoint returned:

{"synchronized":true}

This confirms that the service returned healthy after the backup/restart cycle. It does not verify restore capability.

The legacy backup.sh is not the canonical backup strategy for future automation. The current BTCPay backup/restore workflow using btcpay-backup.sh and btcpay-restore.sh still needs to be adopted and documented for the reference deployment.

Pending verification:

  • define retention expectations;
  • adopt and document the current BTCPay backup/restore procedure;
  • document restore order;
  • perform and record an actual recovery test.

This is the next product milestone for the reference store.

The merchant payment destination must remain under merchant control. Sprey does not receive, hold, or forward merchant funds.

The exact wallet setup procedure will be documented while it is performed on the reference store.

After wallet configuration:

  1. Create an invoice in the Sprey Processing store.
  2. Send the payment independently to the merchant-controlled payment destination.
  3. Observe the Bitcoin transaction from the payment side.
  4. Confirm that BTCPay observes the blockchain and determines the invoice state from network data.
  5. Confirm that the BTCPay invoice reaches the expected state.

This test is the boundary between “the server is online” and “the Bitcoin payment path has been verified.”

Storefront and API integrations are verified only after the underlying payment path works independently.

For WooCommerce, products and orders stay in WooCommerce. The BTCPay integration creates/tracks the invoice, BTCPay observes the relevant payment network, and the resulting invoice state is reported back to WooCommerce. See BTCPay + WooCommerce.

Additional networks, tokens, plugins, or conversion integrations should be enabled and documented one at a time.

The reference server uses unattended-upgrades for routine OS updates. The service is enabled and running, and the APT daily timers are active. Automatic reboot is not enabled; a required reboot should be treated as a controlled maintenance action.

Useful checks are:

Terminal window
apt list --upgradable
test -f /var/run/reboot-required \
&& echo "REBOOT REQUIRED" \
|| echo "Reboot not required"
systemctl status unattended-upgrades --no-pager
systemctl list-timers apt-daily.timer apt-daily-upgrade.timer

Do not configure unattended replacement of the production BTCPay stack. The reference deployment is updated through the BTCPay Docker repository’s update script:

Terminal window
cd ~/btcpayserver-docker
./btcpay-update.sh

After any BTCPay update, verify container health, Bitcoin backend state, the public endpoint, the Access-protected administrative endpoint, and the payment path before considering the maintenance complete.

A compact manual checkpoint is:

Terminal window
apt list --upgradable
test -f /var/run/reboot-required \
&& echo "REBOOT REQUIRED" \
|| echo "Reboot not required"
df -h /
free -h
swapon --show
sysctl vm.swappiness
docker ps
cd ~/btcpayserver-docker
git status -sb
git log -1 --oneline

Application-level backup has a completed legacy checkpoint, but adoption of the current BTCPay backup/restore workflow, actual restore testing, and automated monitoring/alerting remain separate pending operational tasks.

Self-hosting BTCPay does not change the intended Sprey payment model. In the Sprey reference architecture, customer payment happens independently of Sprey and settles to the merchant-controlled wallet or configured payment destination. BTCPay observes the Bitcoin blockchain or another configured payment network and determines invoice state from network data.

Sprey does not initiate, route, receive, hold, or forward merchant funds.

This runbook follows the Sprey rule:

Build it. Verify it. Document it.

Record commands, assumptions, verification steps, and rollback implications while the real system is being built. Do not reconstruct critical production procedures later from memory.