![]()
A complete walkthrough of setting up your own BTCPay Server from scratch — no third-party processors, no KYC, and full custody of every payment from day one.
- Why Self-Host?
- Choosing a VPS
- DNS Configuration with Bunny.net
- Installing the OS
- SSH Key Setup & First Login
- Preparing the Server
- Installing BTCPay Server
- Accessing the Portal & Creating an Account
- Creating a Store
- Setting Up the Wallet with the Right Block Height
- Waiting for Sync
- Creating an Invoice
- API Keys & Webhooks
- Point of Sale
- Maintenance
- Changing the Wallet
Video
01) Why Self-Host BTCPay Server?
Most payment processors sit between you and your customer. They hold your funds, collect transaction data, require identity verification, and can terminate your account without notice. BTCPay Server eliminates every one of those risks.
- No third-party fees — you keep 100% of every payment. No processor cut.
- No KYC — customer payment data never touches a third-party server.
- Full custody — payments go directly to your wallet. No intermediary holds funds.
- No censorship risk — no payment processor can deplatform or freeze you.
- Privacy — all transaction data stays on your own infrastructure.
- Open source — the code is fully auditable. Nothing is hidden.
- Monero-native — BTCPay is one of the few self-hosted options with proper XMR support, making it ideal for merchants who want to accept privacy payments without relying on custodial services.
02) Choosing a VPS
BTCPay Server runs a full Monero node alongside its own services, so you need a reasonably specified server. The specs used in this guide:
| Spec | Value |
|---|---|
| RAM | 4096 MB |
| CPU | 4 Cores |
| Storage | 160 GB |
| Traffic | 1024 GB/month |
| IPv4 | SERVER_IP |
| Hostname | YOURSUBDOMAIN.YOURDOMAIN.com |
160 GB is sufficient for a pruned Monero node. 4 GB RAM handles the wallet RPC and BTCPay services comfortably. 4 CPU cores keeps sync time reasonable during the initial blockchain scan.
Direct link to the Standard plan: https://privateweb.is/cart.php?a=add&pid=570 (affiliate)
03) DNS Configuration with Bunny.net
Before running the BTCPay install script, your domain must already be pointing at the server. The installer uses the domain to provision a Let's Encrypt SSL certificate — if DNS isn't resolving correctly at install time, the cert will fail and setup will error out.
- Log into your Bunny.net DNS panel.
- Create an A record for
btcpay.YOURDOMAIN.com(or whatever you want it to be) pointing to your server's IPv4 address. - Allow a few minutes for propagation before proceeding.
- Verify the domain resolves to your server IP before running the installer.
04) Installing the OS
Select Ubuntu LTS (22.04 or 24.04) when provisioning your server. BTCPay Server's Docker-based install is tested and documented for Ubuntu LTS and it is the most reliable choice.
Provision the server through your hosting panel. Once it shows as online, proceed to set up SSH access before doing anything else.
05) SSH Key Setup & First Login
Logging in with an SSH key is more secure than a password and eliminates brute-force risk. Generate a key locally before touching the server.
On Windows (PowerShell)
ssh-keygen -t ed25519 -C "btcpay"
type $env:USERPROFILE\.ssh\id_ed25519.pub
On Linux
ssh-keygen -t ed25519 -C "btcpay"
cat ~/.ssh/id_ed25519.pub
Copy the output of the pub file. Add it to your server either through your hosting panel's SSH key manager, or by pasting it into ~/.ssh/authorized_keys on the server.
Connect to the server
ssh root@YOUR_SERVER_IP
06) Preparing the Server
Before installing BTCPay, update all packages and configure the firewall.
Update the system
apt update && apt upgrade -y
reboot
Wait for the server to come back online after the reboot, then reconnect via SSH.
Configure the firewall
ufw default deny incoming
ufw default allow outgoing
ufw allow ssh
ufw allow http
ufw allow https
ufw enable
ufw allow ssh is run before ufw enable, or you will lock yourself out of the server.07) Installing BTCPay Server
BTCPay Server is installed via a Docker-based setup script. Clone the repository, set your environment variables, then run the installer.
git clone https://github.com/btcpayserver/btcpayserver-docker
cd btcpayserver-docker
export BTCPAY_HOST="btcpay.yourdomain.com"
export BTCPAYGEN_CRYPTO1="xmr"
export BTCPAYGEN_ADDITIONAL_FRAGMENTS="opt-add-tor"
export BTCPAYGEN_REVERSEPROXY="nginx"
export BTCPAY_ENABLE_SSH=true
. ./btcpay-setup.sh -i
Replace btcpay.yourdomain.com with your actual domain. The installer will pull all required Docker images, configure Nginx as a reverse proxy, and provision your SSL certificate automatically via Let's Encrypt.
08) Accessing the Portal & Creating an Account
Once the installer finishes, navigate to your domain in a browser. You'll be presented with the BTCPay Server registration page. The first account created automatically becomes the server admin — fill in your details and create it.
There is no email verification by default. You are in full control of the instance from this point.
09) Creating a Store
Stores in BTCPay Server are the containers for your payment configuration. Each store has its own wallet, invoice settings, and API keys.
- From the dashboard, click Create a new store.
- Give the store a name and set your preferred display currency (e.g. USD, EUR).
- Configure invoice expiry and payment tolerance as needed.
- Save the store — you'll connect the wallet in the next step.
10) Setting Up the Wallet with the Right Block Height
This is the most critical step of the entire setup. Getting it wrong means the wallet will fail to sync and invoices will not work.
BTCPay Server runs a pruned Monero node. A pruned node does not store the full blockchain from block zero — it only stores data from a certain block height onwards. When you restore a wallet, the wallet RPC needs to know which block to start scanning from. If you tell it to scan from a block that the pruned node does not have data for, the sync will fail.
Check node sync status and blockchain offset
docker exec btcpayserver_monerod monerod status
Look for the pruning seed and blockchain offset in the output. Use the offset value as your restore height — do not use the current sync height, use the oldest block the pruned node holds.
Check wallet logs
docker logs btcpayserver_monero_wallet --tail 30
Verify wallet primary address
docker exec btcpayserver_monero_wallet curl -s http://localhost:18082/json_rpc \
-d '{"jsonrpc":"2.0","id":"0","method":"get_address","params":{"account_index":0,"address_index":[0]}}' \
-H 'Content-Type: application/json'
This confirms the wallet container is running and returns the primary address. Cross-check this against the address shown in BTCPay's store wallet settings.
Reset wallet if restore height was set incorrectly
If you entered the wrong restore height and the wallet is stuck, you need to delete the wallet files and clear the invoice address table before starting fresh:
docker stop btcpayserver_monero_wallet
rm -f /var/lib/docker/volumes/generated_xmr_wallet/_data/wallet
rm -f /var/lib/docker/volumes/generated_xmr_wallet/_data/wallet.keys
rm -f /var/lib/docker/volumes/generated_xmr_wallet/_data/password
docker exec -it $(docker ps -q -f name=postgres) psql -U postgres \
-d btcpayservermainnet \
-c "DELETE FROM \"AddressInvoices\";"
docker start btcpayserver_monero_wallet
11) Waiting for Sync
After the wallet is configured with the correct restore height, the Monero wallet RPC will begin scanning the blockchain from that block forward. This process takes time — in this guide it took approximately 4 days on the hardware listed above.
Monitor progress via the wallet logs:
docker logs btcpayserver_monero_wallet --tail 30
12) Creating an Invoice
Once synced, you can create invoices manually from the BTCPay dashboard or programmatically via the API.
To create one manually: navigate to your store, click Invoices in the sidebar, then Create Invoice. Set the amount and currency, optionally add an order ID or buyer email, and click save. BTCPay generates a Monero address and payment QR code automatically.
Payments are detected on-chain and the invoice status updates in real time once the wallet is fully synced.
13) API Keys & Webhooks
Generate an API key
- Go to Account → Manage Account → API Keys.
- Click Generate Key and select the permissions your application needs (e.g. create invoices, view stores).
- Copy and store the key securely — it is only shown once.
Set up a webhook
- In your store, go to Settings → Webhooks → Create Webhook.
- Enter your endpoint URL — this is the URL BTCPay will POST to when events occur.
- Select the events to listen for — at minimum InvoiceSettled and InvoiceExpired.
- BTCPay signs webhook payloads with HMAC-SHA256 using a secret you define. Validate this signature on your server.
Create an invoice via the API
curl -X POST "https://btcpay.yourdomain.com/api/v1/stores/YOUR_STORE_ID/invoices" \
-H "Authorization: token YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"amount": "10.00",
"currency": "USD",
"metadata": { "orderId": "order_123" }
}'
14) Point of Sale
BTCPay's Point of Sale app creates a public-facing storefront with product listings and a checkout flow — no custom development required.
- In your store, go to Apps → Create App → select Point of Sale.
- Add products with names, prices, and optional images.
- BTCPay generates a public URL for the storefront automatically.
- Customers browse products, add to cart, and checkout — a Monero invoice is created for each order.
- Place a test order to confirm the full payment flow end-to-end.
- Confirmed orders appear in the BTCPay dashboard under Invoices with full payment details.
15) Maintenance
Service management
btcpay-down.sh # Stop all services
btcpay-up.sh # Start all services
btcpay-restart.sh # Restart all services
btcpay-update.sh # Update BTCPay Server
Upgrading BTCPay Server
Always review the release notes before upgrading. Look for any breaking changes, particularly around wallet configuration or Docker image changes that could affect Monero node compatibility.
Run the update script, then verify that the server, node, and wallet are all functioning correctly before considering the upgrade complete.
btcpay-update.sh
Full reset (preserving blockchain data)
If you need to reset BTCPay's application data without re-downloading the Monero blockchain:
btcpay-down.sh
docker volume rm generated_btcpay_datadir
docker volume rm generated_btcpay_pluginsdir
docker volume rm generated_xmr_wallet
btcpay-up.sh
16) Changing the Wallet
If you need to swap the wallet attached to a store — for example to move to a new seed — go to Store Settings → Monero → Modify.
Enter your new wallet seed and, critically, set the correct restore height for the replacement wallet. The same rule applies as during initial setup: use the blockchain offset from the pruned node, not the current sync height.
docker exec btcpayserver_monerod monerod status