All systems operational Amsterdam · Paris · Reykjavík +5 Pay with Cryptocurrency
Payments & privacyIntermediate22 min readUpdated 2026-09-01

Self-host BTCPay Server on a VPS

A payment processor is a company that holds your money on the way through and asks who you are first. BTCPay Server is the same job written as software you run yourself. Here is what it actually costs to operate, and where the keys have to live afterwards.

Self-host BTCPay Server on a VPS
On this page
  1. What a self-hosted processor actually changes
  2. What actually gets deployed, and which part is expensive
  3. Sizing honestly, and which plan fits
  4. What you need before you start
  5. Step-by-step
  6. Where the keys live, and the one mistake that matters
  7. Backups, and the parts that are genuinely irreplaceable
  8. Failure modes the quickstarts skip
  9. Jurisdiction, the domain, and what self-hosting does not hide
  10. Frequently asked questions

Every hosted crypto payment processor asks the same two things before it will move a satoshi for you: who you are, and permission to hold the money in transit. Both are decisions somebody else made about your business. BTCPay Server does the same job — invoices, exchange rates, a checkout page, webhooks, a point of sale — as software you run, with no account, no percentage, and nobody standing between the customer’s wallet and yours.

The trade is that you become the operator. This guide is about what that actually costs: which part of the stack eats the resources (it is not BTCPay), what plan the thing genuinely needs, how to deploy it in about an hour of attention and a day of waiting, and — the part that decides whether any of this was a good idea — where the keys live once it is running. Bitcoin and Lightning are the default; Monero is an optional integration and, as it happens, the one with the cleanest key story of the three. The machine underneath can be rented with an email address and paid for in the same coins you are about to start accepting.

What a self-hosted processor actually changes

The pitch is usually “no fees”, which is the least interesting part of it. Four things change, and only one of them is about money:

  • Nobody holds your funds in transit. A hosted processor receives the customer’s payment into its own wallet and credits you afterwards. That gap is where holds, reviews and freezes happen. BTCPay never receives anything: the customer pays an address derived from your key, and the coins are yours from the first confirmation.
  • Nobody asks who you are. Onboarding to a processor is an identity process with a bank at the end of it. Installing software is not. That is the entire difference, and it is why the answer to “how do I take crypto without KYC” is always “run the processor yourself”.
  • The cost stops scaling with you. A percentage is charged forever and grows with success. A VPS and a domain cost the same in a good month and a bad one.
  • You inherit the operational work. Uptime, updates, backups, and being the person who gets the message when a customer says the invoice never settled. This is the real price, and it is paid in attention rather than in currency.

What you do not get is the one thing processors are genuinely good at: turning coins into bank-account money on your behalf. BTCPay holds what it was paid, in the coin it was paid in. Conversion, invoicing in fiat, accounting exports a tax authority recognises — all separate problems, and most of the ways to solve them reintroduce exactly the identified counterparty you just removed. Work out which half of that trade you actually wanted before you build anything.

What actually gets deployed, and which part is expensive

The name is misleading in a useful way. “BTCPay Server” is not one program; the standard deployment is a small fleet of containers that come up together, and knowing which is which is the difference between sizing this correctly and guessing at it:

  • Bitcoin Core. A real full node, validating from genesis. This is the component that costs you disk, most of the first day, and nearly all of the memory. Everything else is comparatively free.
  • NBXplorer. The indexer between Core and BTCPay. You give it extended public keys; it tracks the derived addresses and tells BTCPay when money lands. It is also the piece that cares about pruning, for reasons the failure-modes section comes back to.
  • BTCPay Server itself. The application: stores, invoices, the checkout page, the point of sale, webhooks, the Greenfield API. Modest by comparison — a few hundred megabytes of memory.
  • PostgreSQL. Invoices, stores, settings, users, API keys. Small, and the thing your backups are actually for.
  • nginx with automatic certificates. A reverse proxy plus a companion that issues and renews a certificate for your hostname. This is why ports 80 and 443 have to be genuinely reachable from the internet.
  • Optionally a Lightning node (Core Lightning or LND) and a Monero daemon with a wallet RPC beside it — each bringing its own chain and its own appetite for disk.

So the sizing question is never “how big does BTCPay need to be”. It is “how much chain am I keeping, and how many chains”. Answer that and the plan chooses itself.

Sizing honestly, and which plan fits

The project documents 2 GB of RAM with swap as a minimum and 4 GB as the recommendation, and the gap between those two numbers is the gap between an install that technically completes and one you are willing to point customers at. An application runtime, PostgreSQL and a syncing bitcoind sharing 2 GB will finish — slowly, with the swap file doing work it should not have to.

Disk is set by how hard you prune, and the deployment exposes that as a fragment you pass to the installer. The family runs roughly opt-save-storage at about 100 GB of block files, -s at about 50 GB, -xs at about 25 GB and -xxs at about 5 GB. That last one is a trap for anything meant to survive: it keeps so little history that ordinary maintenance starts failing. For a store, -s or -xs is the sensible band.

Concrete numbers from the grid, because “it depends” is not an answer:

  • Bitcoin only, pruned, one storeScout, 2 vCPU / 4 GB / 70 GB NVMe, $9.00/mo. The honest floor, and the right answer for most readers.
  • Bitcoin and LightningRunner, 3 vCPU / 6 GB / 100 GB, $14.00/mo. A Lightning daemon is light on disk and insistent about staying online; the headroom is for the node underneath it.
  • Bitcoin, Lightning and MoneroAlpha, 6 vCPU / 12 GB / 200 GB, $28.00/mo. A pruned Monero chain is around 85 GB by itself, so two chains and two indexers is where 8 GB gets thin. Hunter (4 vCPU / 8 GB / 140 GB, $19.00/mo) fits only if Bitcoin stays pruned hard and stays that way.
  • An unpruned node behind the store — a different project with a different bill. The full-node guide has those numbers, and they start at Fenrir.

CPU matters exactly once. Signature verification during the initial block download parallelises, so two to four vCPU turns the first sync from days into about a day, then goes back to idling. Bandwidth is a one-time cost of the whole chain, on a 1 Gbps port with unlimited traffic and no overage invoice at the end of the month. Latency is irrelevant to a payment processor, so choose the location for jurisdiction rather than for milliseconds — Amsterdam, Paris, Bucharest and Sofia all sit at base price.

What you need before you start

A short list, and one item on it is not technical.

  • A domain, with an A record already pointing at the server. The certificate is issued over HTTP against that hostname, so DNS has to resolve before the installer runs, not during. A subdomain such as pay.example.com is the normal shape.
  • Ports 80 and 443 open to the world. Port 80 is not optional even though nothing useful serves on it afterwards; the certificate challenge needs it.
  • Debian 13 from the template library, on a machine that has already had the ten-minute hardening pass. Do it first — it is much harder to do politely once a stack is running.
  • A wallet you already control, and its extended public key. Sparrow, Electrum or a hardware wallet. Have it in hand before you install, so you are never tempted to let the server generate one for you.
  • An email address for the certificate authority. It receives expiry warnings and nothing else.
  • A decision about pruning, made now. Changing your mind later means resyncing the chain from scratch, which is a day you will not enjoy spending twice.

Step-by-step

  1. Deploy the server, point the domain at it, and harden it first

    Order the plan the sizing section pointed at, choose Debian 13, and pick the location on jurisdiction rather than latency. Create the DNS record immediately, because it has to have propagated by the time the installer asks a certificate authority to prove you own the name:

    pay.example.com.   300   IN   A   198.51.100.10

    Then the pass from the Debian hardening guide: a non-root user with a key, password authentication off, unattended security upgrades on, and a default-deny firewall. Everything below assumes that has happened and that 22, 80 and 443 are the only things open.

    apt update && apt full-upgrade -y
    apt install -y git curl

    Do not install Docker by hand. The setup script installs and configures the version it expects, and a hand-rolled installation is the most common reason a first run fails.

  2. Clone the deployment repository and choose your fragments

    The whole deployment is one repository of shell scripts and compose fragments. Clone it as root, somewhere you will remember:

    git clone https://github.com/btcpayserver/btcpayserver-docker
    cd btcpayserver-docker

    Configuration is environment variables, read once by the installer and then persisted, so the exports below are the entire configuration file you will ever write:

    export BTCPAY_HOST="pay.example.com"
    export NBITCOIN_NETWORK="mainnet"
    export BTCPAYGEN_CRYPTO1="btc"
    export BTCPAYGEN_REVERSEPROXY="nginx"
    export BTCPAYGEN_LIGHTNING="clightning"
    export BTCPAYGEN_ADDITIONAL_FRAGMENTS="opt-save-storage-s"
    export LETSENCRYPT_EMAIL="you@example.com"

    BTCPAYGEN_LIGHTNING takes clightning, lnd, phoenixd or nothing at all — leave it empty if you are not ready to hold hot funds, since adding it later is one more run of the same script. Fragment names are the part of this that moves between releases, so read the project’s current deployment page before you paste.

  3. Run the installer, then let the chain sync

    One command builds the compose file, pulls the images and brings everything up:

    . ./btcpay-setup.sh -i

    It finishes in a few minutes and leaves a working web interface behind. It does not leave a working store behind, because Bitcoin Core is now downloading and validating the chain and nothing can be paid until that completes. Watch it rather than guessing:

    bitcoin-cli.sh -getinfo
    btcpay-down.sh     # stop everything
    btcpay-up.sh       # start everything

    The figure to trust is verificationprogress, and it is deceptive early: the first ninety per cent is quick and the last ten takes most of the time, because recent blocks are full ones. On NVMe with two to four vCPU, expect roughly a day. Leave it alone while it works — restarting mid-sync only costs you the cache.

  4. Create the admin account and close the door behind you

    Open https://pay.example.com and register. The first account created becomes the administrator, which makes the window between the installer finishing and you registering the only genuinely dangerous moment in this process. Do it immediately, from a browser tab you already have open.

    Then, in the server settings, turn off open registration so the second visitor cannot create an account, and enable two-factor authentication on your own. Both are two clicks, and together they are the difference between a payment processor and somebody else’s payment processor. Invite any further administrators explicitly.

  5. Connect a wallet the server cannot spend from

    Create a store, then attach a Bitcoin wallet. BTCPay offers to generate a new one for you; take the other path — connect an existing wallet — and paste the account-level extended public key exported from your own:

    zpub6r...
        # account-level extended PUBLIC key (xpub / ypub / zpub), never a prv
        # Sparrow: wallet settings.  Electrum: Wallet -> Information -> Master Public Key

    That string is public by design. It lets BTCPay derive an unlimited supply of receive addresses and recognise payments to them; it does not let BTCPay, or anyone who compromises it, move a satoshi. The private key stays where it was, ideally on a hardware wallet that has never met this machine.

    Two consequences worth internalising. Use a fresh account or a dedicated derivation path for the store rather than the key of a wallet you have used for years — the failure-modes section explains why history and pruning do not get along. And remember that whoever holds that extended key can see every payment you ever receive: not a key, but not nothing either. Treat it like a ledger left open on a desk.

  6. Add Lightning, and decide how much of your money lives there

    If you set BTCPAYGEN_LIGHTNING at install time the node is already running and connected internally, and the store settings only need it switched on. If you left it empty, export the variable and run the setup script again; it is idempotent and will not resync the chain.

    export BTCPAYGEN_LIGHTNING="clightning"
    . ./btcpay-setup.sh -i

    Now the part that is a decision rather than a configuration. Channel balances are held in a wallet the server can spend from, because that is what a payment channel is; there is no watch-only Lightning. A compromise of this box costs you exactly the channel balance and nothing else, which is why the right amount to keep there is the amount you would be annoyed rather than ruined to lose. Sweep to cold storage on a schedule you will actually keep.

    Expect the first month to be about liquidity rather than about payments. A merchant node needs inbound capacity, which is the opposite of what opening a channel gives you — opening one funds your own side. Buying inbound liquidity from a service, running a submarine swap to push your funds to the far side, or waiting for peers to open toward you once you have volume are the three honest options. Core Lightning and LND both work; Core Lightning is the easier one to reason about beside a pruned node, for reasons the full-node guide covers.

  7. Add Monero with a view-only wallet

    Monero attaches the same way, as a second crypto slot, which brings up a Monero daemon and a wallet RPC beside everything else:

    export BTCPAYGEN_CRYPTO2="xmr"
    . ./btcpay-setup.sh -i

    Budget for a second chain: around 85 GB pruned, and a first sync measured in a day or two on NVMe. The Monero node guide has the detail on both.

    The key handling here is the best part of the whole setup. Monero separates the ability to see incoming payments from the ability to spend them, so you generate a view-only wallet from your primary address and private view key, and that is all the server ever holds:

    monero-wallet-cli --generate-from-view-key store-view

    It asks for the standard address, the private view key and a password. Copy the resulting wallet files into the Monero wallet directory the deployment exposes, point the store at it, and BTCPay generates a subaddress per invoice and watches for payments to it. The spend key never leaves the machine you generated it on.

    One thing to design the checkout around: received outputs are locked for ten blocks, roughly twenty minutes, so BTCPay sees a payment arrive well before it is spendable. For digital goods that is a policy choice. For anything handed over in person, it is a queue.

  8. Test it like a customer, then like an operator

    “The page loads” is not a test. Create an invoice for a trivial amount, pay it from a real wallet, then work through the settings that decide what happens when a payment is not perfect:

    • Confirmation policy. How many blocks before an invoice counts as settled. Zero confirmations is reasonable for a coffee and wrong for a laptop; it is a per-store setting and the most consequential number in the interface.
    • Invoice expiry. How long a customer has before the quoted rate stops applying. Fifteen minutes is the default and it is short for anyone paying out of an exchange withdrawal.
    • Payment tolerance. The percentage of underpayment you will accept rather than leave a customer with a half-paid invoice and a support ticket. Small and non-zero is the pragmatic setting.
    • Underpayment and overpayment. Deliberately pay an invoice short, once, and watch what your store does about it. Much better to learn that on your own money.

    Then the operator’s half: enable a webhook and confirm your shop actually receives it, generate a Greenfield API key if anything will create invoices programmatically, and take one full backup before you go live — so that the first restore you ever perform is a rehearsal rather than an emergency.

Where the keys live, and the one mistake that matters

Almost every bad outcome with a self-hosted processor traces back to a single decision made early and casually: letting the server hold something that can spend. It is worth being explicit about the three cases, because they are genuinely different.

  • On-chain Bitcoin: watch-only, always. BTCPay holds an extended public key and nothing else. If the box is compromised, the attacker learns what you were paid and can change where future invoices point — a real attack, and the reason to re-check your store’s wallet after any incident — but cannot touch a coin already received.
  • Lightning: hot by definition. Channels are funded with coins the server can spend, because that is what a channel is. This is the deliberate exception, and the section above sizes it.
  • Monero: view-only, structurally. The private view key reveals every incoming payment and authorises none of them. There is no equivalent on the Bitcoin side.

The one feature that breaks this model on purpose is Payjoin. It lets your server contribute an input to the customer’s transaction, which meaningfully degrades the common-input heuristic chain analysis leans on and is a genuine privacy gain for both parties — but a receiver has to be able to sign, so it needs a hot wallet inside BTCPay. That is a real trade rather than a free feature. Take it knowingly, and fund it the way you fund the Lightning wallet: with an amount, not with everything.

And if you let the setup wizard generate the store wallet because it was the fastest path through: write down the seed it showed you, verify it in an offline wallet, then plan the migration to a watch-only setup. A seed that has ever been on an internet-facing server is a seed with a countdown running on it.

Backups, and the parts that are genuinely irreplaceable

Sort the state by what it would take to recreate, because the answers are very different:

  • The chain. Not a backup problem. It is public data and it resyncs, slowly and for free. Never back it up.
  • The database. Invoices, stores, settings, users, API keys. This is the real backup, it is small, and losing it costs you the accounting record of every sale — not the money, but the paperwork.
  • Your on-chain wallet. Already safe, because the key was never here. That is the payoff for step five.
  • Lightning channel state. The one that bites. A static channel backup lets you recover the funds in your channels after a total loss by forcing them closed; it does not restore the channels, and it is worthless if it is stale. It changes every time a channel opens or closes, so it belongs in an automated off-site copy rather than a folder you remember quarterly.
  • The Monero view-only wallet. Rebuildable from the address and view key you still hold offline. Back up those and treat the wallet files as a cache.

The deployment ships helpers that do this correctly:

btcpay-backup.sh    # stops the stack, dumps Postgres, tars the config, restarts
btcpay-restore.sh   # puts one of those archives back

That pause is the point — a hot copy of a running database can restore into something interesting. Encrypt whatever leaves the machine, and remember that the weekly snapshot included with every plan is a convenience rather than a backup: a snapshot lives with the thing it is protecting.

Failure modes the quickstarts skip

In rough order of how often they cost somebody an evening:

  • The certificate never issues. Nine times in ten the A record was created after the installer ran, or port 80 is filtered. Fix DNS, confirm the name resolves from somewhere that is not your own laptop, then re-run the setup script. Retrying blindly against the certificate authority earns a rate limit and a week of waiting, so change something between attempts.
  • An imported extended key with history shows no balance. This is the pruning trap. The indexer finds new payments by watching new blocks, but reconstructing an existing wallet’s past means reading blocks a pruned node has deleted. Use a fresh account for the store and the problem does not exist; if you must import history, you need an unpruned node and a rescan.
  • “The customer paid and the invoice is still open.” Usually underpayment by the sending wallet’s fee, an invoice that expired while the payment sat unconfirmed, or a confirmation policy stricter than you remember choosing. All three are settings, and all three are why you test with your own money first.
  • The node quietly falls behind. A stalled bitcoind hands out addresses and misses payments without complaining. Compare your block height against any public source on a timer and alert on the gap; this is not something to learn from a customer.
  • An update at the wrong moment. btcpay-update.sh is well behaved, but it restarts everything. Run it deliberately, never automatically, and never during a sale.
  • The address acquires a reputation. A checkout page on a blocklisted IPv4 is a checkout page some corporate networks and mail filters quietly refuse. Every plan here ships a dedicated address with no history, and it is worth checking it before you print the domain on anything.

Jurisdiction, the domain, and what self-hosting does not hide

The software removes a counterparty. It does not remove the rest of the surface, and being honest about that is more useful than another list of features.

The domain is the weak point. It is registered somewhere, it resolves publicly, and it is the first thing anyone looks at. A processor self-hosted on a hostname registered in your own name at a registrar in your own country has moved the money out of a third party’s hands and left the identity precisely where it was. If that matters for what you sell, the registrar deserves at least as much thought as the host.

The jurisdiction is a real variable. Where the machine sits decides whose court order reaches it and how much process stands in the way. That is friction and distance, not immunity — a point the offshore hosting entry makes at length, and worth reading before choosing a flag over a network.

The account is the last link. Renting the server with an email address and paying in Monero means no card statement ties the checkout page to a bank — a strange thing to overlook while building a system whose entire point is not needing one. Every plan here is no-KYC by default, and every location ships a dedicated clean IPv4 with custom reverse DNS.

And the boundary is stated rather than implied. Nodes, processors and stores are ordinary infrastructure and welcome here; the acceptable-use policy is short, public, and has a floor under it. Whether you need a licence to take payments is a question about what you sell and where you are, not about the software — and it is a question for someone qualified, not for a hosting guide.

Frequently asked questions

Which VPS plan do I need for BTCPay Server?

Scout (2 vCPU / 4 GB / 70 GB NVMe, $9.00/mo) running a pruned Bitcoin node is the honest floor for a working store, and it is what most readers should buy. Add Lightning and Runner (3 vCPU / 6 GB / 100 GB, $14.00/mo) is more comfortable; add Monero as a second chain and Alpha (6 vCPU / 12 GB / 200 GB, $28.00/mo) is the size that stops you thinking about it. The project documents 2 GB as its minimum, which is true and not pleasant.

Can I run BTCPay Server without a full Bitcoin node?

Yes, but you are choosing which trust to keep. BTCPay can be pointed at an external node you already run — the sane version of this, and a good reason to have followed the full-node guide first — or at somebody else’s node, which reintroduces a third party that sees every address your store generates. The bundled node exists because it is the only configuration in which nobody is watching. A pruned node is still a full node: prune it and most of the resource objection disappears.

Does BTCPay Server support Monero?

Yes, as an optional integration you enable at deploy time. It brings up a Monero daemon and a wallet RPC, and you connect it with a view-only wallet generated from your primary address and private view key — so the server can see payments and cannot spend them, which is a better arrangement than anything available on the Bitcoin side. The costs are a second chain to sync and store, and the ten-block lock on received outputs, roughly twenty minutes before funds are spendable.

Is it legal to run my own payment processor?

Running the software is ordinary software operation. The regulated activity in most places is holding or moving money on behalf of other people, and a non-custodial processor taking payment for your own goods is specifically not doing that, because nothing is ever held on anyone’s behalf. What you sell and where you live still determine your obligations, tax included, and self-hosting changes none of them. This is a hosting guide and not legal advice: if you intend to process for third parties, assume you are in a different category and ask someone qualified.

If the server dies, do I lose the money?

Not the on-chain money, provided you followed step five — those coins sit in a wallet whose key was never on the server, and a fresh install fed the same extended public key sees them again. What you lose is the record: invoices, store settings, API keys, unless you kept the database backup. Lightning is the exception, because channel funds are on the server; recovering them after a total loss needs a current static channel backup, and it force-closes your channels rather than restoring them.

Do I need a domain name for BTCPay Server?

In practice yes. The deployment issues a certificate for a hostname, browsers and wallets expect HTTPS on a checkout page, and customers are being asked to send money to whatever is in the address bar. A subdomain of something you already own is plenty. If a public domain is itself the problem, BTCPay can be reached over an onion service instead — a legitimate configuration that also changes how many customers can reach the page.

What does self-hosting actually cost per month?

The software is free and AGPL-licensed, so the bill is the VPS, the domain and your time. At $9.00 a month for Scout and a few dollars a year for a name, the arithmetic against any percentage-based processor stops being close at a very small volume — and unlike a percentage it does not grow when you succeed. The honest cost is the third item: you are now the person who notices when the node stops syncing.

Deploy an offshore VPS in about a minute

No-KYC, crypto-paid, all-NVMe. Pick a tier, pay in Monero or any major coin, and get root in roughly 60 seconds.

Fenrir on guard