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.
Reference server
Section titled “Reference 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.
Deployment path
Section titled “Deployment path”The runbook is organized around the path used to build and validate a production instance:
clean VPS | vUbuntu baseline | vSSH + updates + swap | vDocker + BTCPay Server | vCloudflare Tunnel + DNS | vpublic endpoint + protected admin endpoint | vexternal origin firewall | vBitcoin node + NBXplorer | vpruning + synchronization | vSMTP + backups | vmerchant wallet | vinvoice | vreal payment | vobserved invoice state | vbackup and recovery test1. Provision the VPS
Section titled “1. Provision the VPS”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.
2. Establish the Ubuntu baseline
Section titled “2. Establish the Ubuntu baseline”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-upgradesenabled 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 0The memory policy is persisted separately:
vm.swappiness=10Useful verification commands are:
sysctl vm.swappinessswapon --showfree -hAt 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.
3. Deploy Docker and BTCPay Server
Section titled “3. Deploy Docker and BTCPay Server”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:
cd ~/btcpayserver-docker. ./btcpay-setup.sh -iThe 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.winBTCPAY_ADDITIONAL_HOSTS=adminpay.sprey.winBTCPAYGEN_CRYPTO1=btcBTCPAYGEN_LIGHTNING=noneBTCPAYGEN_REVERSEPROXY=nginxBTCPAYGEN_ADDITIONAL_FRAGMENTS=opt-save-storage-xs;opt-add-cloudflaredBTCPAYGEN_EXCLUDE_FRAGMENTS=nginx-httpsNBITCOIN_NETWORK=mainnetSecrets 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.
4. Configure Cloudflare Tunnel and DNS
Section titled “4. Configure Cloudflare Tunnel and DNS”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://nginxadminpay.sprey.win -> http://nginxThe 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.winopens the BTCPay service over HTTPS;adminpay.sprey.winreaches 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: AllowInclude: 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 LoaderIt matches either BTCPay hostname:
(http.host eq "pay.sprey.win" or http.host eq "adminpay.sprey.win")and applies only:
Rocket Loader: OffGlobal 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.
7. Close direct web access to the origin
Section titled “7. Close direct web access to the origin”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:
- The existing SSH session remains connected.
- A new SSH connection can be established.
pay.sprey.winstill works through the Tunnel.adminpay.sprey.winstill presents Cloudflare Access and reaches BTCPay after authentication.- 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.
8. Bitcoin and NBXplorer
Section titled “8. Bitcoin and NBXplorer”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: mainblocks: 965166headers: 965166verificationprogress: 0.9999911966066127initialblockdownload: falsesize_on_disk: 26083586256pruned: truepruneheight: 951074automatic_pruning: trueprune_target_size: 26214400000warnings: []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.
9. Lightning
Section titled “9. Lightning”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.
10. Configure SMTP
Section titled “10. Configure SMTP”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.
12. Connect a merchant-controlled wallet
Section titled “12. Connect a merchant-controlled wallet”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.
13. Create and pay the first invoice
Section titled “13. Create and pay the first invoice”After wallet configuration:
- Create an invoice in the Sprey Processing store.
- Send the payment independently to the merchant-controlled payment destination.
- Observe the Bitcoin transaction from the payment side.
- Confirm that BTCPay observes the blockchain and determines the invoice state from network data.
- 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.”
14. Verify integrations
Section titled “14. Verify integrations”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.
15. Updates, diagnostics, and recovery
Section titled “15. Updates, diagnostics, and recovery”Ubuntu updates
Section titled “Ubuntu updates”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:
apt list --upgradable
test -f /var/run/reboot-required \ && echo "REBOOT REQUIRED" \ || echo "Reboot not required"
systemctl status unattended-upgrades --no-pagersystemctl list-timers apt-daily.timer apt-daily-upgrade.timerBTCPay updates
Section titled “BTCPay updates”Do not configure unattended replacement of the production BTCPay stack. The reference deployment is updated through the BTCPay Docker repository’s update script:
cd ~/btcpayserver-docker./btcpay-update.shAfter 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.
Routine host checks
Section titled “Routine host checks”A compact manual checkpoint is:
apt list --upgradable
test -f /var/run/reboot-required \ && echo "REBOOT REQUIRED" \ || echo "Reboot not required"
df -h /free -hswapon --showsysctl vm.swappinessdocker ps
cd ~/btcpayserver-dockergit status -sbgit log -1 --onelineApplication-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.
Non-custodial boundary
Section titled “Non-custodial boundary”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.
Documentation method
Section titled “Documentation method”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.