BTCPay Server is a free, open-source, and self-hosted Bitcoin payment gateway, which means developers and security auditors can always inspect the code for quality. It enables individuals and businesses to accept Bitcoin payments online or in person without any fees, offering self-sovereignty in the process.
Difficulty: Hard
BTCPay Server is a self-hosted and automated invoicing system. At checkout, a customer is presented with an invoice that they pay from their wallet. BTCPay Server follows the status of the invoice through the blockchain and informs you when the payment has been settled so that you can fulfill the order. It also takes care of payment refunding and bitcoin management alongside plenty of other features.
More information can be found in its documentation, and stay tuned for news on its blog
To run the BTCPay Server you will need to install .NET Core SDK, PostgreSQL, and NBXplorer
Configure Bitcoin Core
We need to set up settings in the Bitcoin Core configuration file - add new lines if they are not present
With user admin, edit bitcoin.conf
$sudonano/data/bitcoin/bitcoin.conf
Add the following line to the "# Connections" section. Save and exit
# NBXplorer requeriment
whitelist=127.0.0.1
Restart Bitcoin Core to apply changes
$sudosystemctlrestartbitcoind
Firewall
Configure the firewall to allow incoming HTTPS requests
$sudoufwallow23000/tcpcomment'allow BTCPay Server from anywhere'
Expected output
Rule added
Rule added (v6)
Create the btcpay user & group
We do not want to run BTCPay Server and other related services alongside other services due to security reasons. Therefore, we will create a separate user and run the code under the new user's account.
With user admin, create a new user called btcpay
$sudoadduser--disabled-password--gecos""btcpay
Add btcpay user to the bitcoin and lnd groups
$sudousermod-a-Gbitcoin,lndbtcpay
Install .NET Core SDK
With user admin, change to the btcpay user
$sudosu-btcpay
We will use the scripted install mode. Download the script
Before running this script, you'll need to grant permission for this script to run as an executable
$chmod+x./dotnet-install.sh
Set environment variable version
$VERSION=8.0
Install .NET Core SDK
$./dotnet-install.sh--channel $VERSION
Example of expected output ⬇️
dotnet-install: Attempting to download using aka.ms link https://dotnetcli.azureedge.net/dotnet/Sdk/6.0.417/dotnet-sdk-6.0.417-linux-x64.tar.gz
dotnet-install: Remote file https://dotnetcli.azureedge.net/dotnet/Sdk/6.0.417/dotnet-sdk-6.0.417-linux-x64.tar.gz size is 186250370 bytes.
dotnet-install: Extracting zip from https://dotnetcli.azureedge.net/dotnet/Sdk/6.0.417/dotnet-sdk-6.0.417-linux-x64.tar.gz
dotnet-install: Downloaded file size is 186250370 bytes.
dotnet-install: The remote and local file sizes are equal.
dotnet-install: Installed version is 6.0.417
dotnet-install: Adding to current process PATH: `/home/btcpay/.dotnet`. Note: This change will be visible only when sourcing script.
dotnet-install: Note that the script does not resolve dependencies during installation.
dotnet-install: To check the list of dependencies, go to https://learn.microsoft.com/dotnet/core/install, select your operating system and check the "Dependencies" section.
dotnet-install: Installation finished successfully.
Cloning into 'btcpayserver'...
remote: Enumerating objects: 75078, done.
remote: Counting objects: 100% (2765/2765), done.
remote: Compressing objects: 100% (1249/1249), done.
remote: Total 75078 (delta 1834), reused 2203 (delta 1485), pack-reused 72313
Receiving objects: 100% (75078/75078), 51.55 MiB | 4.86 MiB/s, done.
Resolving deltas: 100% (58704/58704), done.
Note: switching to 'a921504bcf619c5e845813b8f994b39147694a97'.
You are in 'detached HEAD' state. You can look around, make experimental
changes and commit them, and you can discard any commits you make in this
state without impacting any branches by switching back to a branch.
If you want to create a new branch to retain commits you create, you may
do so (now or later) by using -c with the switch command. Example:
git switch -c <new-branch-name>
Or undo this operation with:
git switch -
Turn off this advice by setting config variable advice.detachedHead to false
$cdNBXplorer
Modify NBXplorer run script
$nanorun.sh
Comment existing line
#dotnet run --no-launch-profile --no-build -c Release --project "NBXplorer/NBXplorer.csproj" -- $@
Add the next line below. Save and exit
/home/btcpay/.dotnet/dotnet run --no-launch-profile --no-build -c Release --project "NBXplorer/NBXplorer.csproj" -- $@
Welcome to .NET 8.0!
---------------------
SDK Version: 8.0.100
----------------
Installed an ASP.NET Core HTTPS development certificate.
To trust the certificate, view the instructions: https://aka.ms/dotnet-https-linux
----------------
Write your first app: https://aka.ms/dotnet-hello-world
Find out what's new: https://aka.ms/dotnet-whats-new
Explore documentation: https://aka.ms/dotnet-docs
Report issues and find source on GitHub: https://github.com/dotnet/core
Use 'dotnet --help' to see available commands or visit: https://aka.ms/dotnet-cli
--------------------------------------------------------------------------------------
MSBuild version 17.8.3+195e7f5a3 for .NET
Determining projects to restore...
Restored /home/btcpay/src/NBXplorer/NBXplorer.Client/NBXplorer.Client.csproj (in 30.33 sec).
Restored /home/btcpay/src/NBXplorer/NBXplorer/NBXplorer.csproj (in 30.35 sec).
NBXplorer.Client -> /home/btcpay/src/NBXplorer/NBXplorer.Client/bin/Release/netstandard2.1/NBXplorer.Client.dll
NBXplorer -> /home/btcpay/src/NBXplorer/NBXplorer/bin/Release/net8.0/NBXplorer.dll
Build succeeded.
0 Warning(s)
0 Error(s)
Time Elapsed 00:00:41.43
Check the correct installation
$ head -n 6 /home/btcpay/src/NBXplorer/NBXplorer/NBXplorer.csproj | grep Version
Prepare “nbxplorer” monitoring by the systemd journal and checking the logging output. You can exit monitoring at any time with Ctrl-C
$ journalctl -f -u nbxplorer
Keep this terminal open, you'll need to come back here on the next step to monitor the logs
Running NBXplorer
To keep an eye on the software movements, start your SSH program (eg. PuTTY) a second time, connect to the MiniBolt node, and log in as "admin". Commands for the second session start with the prompt $2 (which must not be entered)
With user admin, start the nbxplorer service
$ sudo systemctl start nbxplorer
Example of expected output on the first terminal with $ journalctl -f -u nbxplorer ⬇️
Cloning into 'btcpayserver'...
remote: Enumerating objects: 75078, done.
remote: Counting objects: 100% (2765/2765), done.
remote: Compressing objects: 100% (1249/1249), done.
remote: Total 75078 (delta 1834), reused 2203 (delta 1485), pack-reused 72313
Receiving objects: 100% (75078/75078), 51.55 MiB | 4.86 MiB/s, done.
Resolving deltas: 100% (58704/58704), done.
Note: switching to 'a921504bcf619c5e845813b8f994b39147694a97'.
You are in 'detached HEAD' state. You can look around, make experimental
changes and commit them, and you can discard any commits you make in this
state without impacting any branches by switching back to a branch.
If you want to create a new branch to retain commits you create, you may
do so (now or later) by using -c with the switch command. Example:
git switch -c <new-branch-name>
Or undo this operation with:
git switch -
Turn off this advice by setting config variable advice.detachedHead to false
MSBuild version 17.3.2+561848881 for .NET
Determining projects to restore...
Restored /home/btcpay/src/btcpayserver/BTCPayServer.Rating/BTCPayServer.Rating.csproj (in 32.66 sec).
Restored /home/btcpay/src/btcpayserver/BTCPayServer.Data/BTCPayServer.Data.csproj (in 1.41 sec).
Restored /home/btcpay/src/btcpayserver/BTCPayServer.Common/BTCPayServer.Common.csproj (in 392 ms).
Restored /home/btcpay/src/btcpayserver/BTCPayServer.Client/BTCPayServer.Client.csproj (in 1.1 sec).
Restored /home/btcpay/src/btcpayserver/BTCPayServer.Abstractions/BTCPayServer.Abstractions.csproj (in 8 ms).
Restored /home/btcpay/src/btcpayserver/BTCPayServer/BTCPayServer.csproj (in 36.6 sec).
BTCPayServer.Common -> /home/btcpay/src/btcpayserver/BTCPayServer.Common/bin/Release/net6.0/BTCPayServer.Common.dll
BTCPayServer.Client -> /home/btcpay/src/btcpayserver/BTCPayServer.Client/bin/Release/netstandard2.1/BTCPayServer.Client.dll
BTCPayServer.Rating -> /home/btcpay/src/btcpayserver/BTCPayServer.Rating/bin/Release/net6.0/BTCPayServer.Rating.dll
BTCPayServer.Abstractions -> /home/btcpay/src/btcpayserver/BTCPayServer.Abstractions/bin/Release/net6.0/BTCPayServer.Abstractions.dll
BTCPayServer.Data -> /home/btcpay/src/btcpayserver/BTCPayServer.Data/bin/Release/net6.0/BTCPayServer.Data.dll
/home/btcpay/src/btcpayserver/BTCPayServer/Services/Cheater.cs(37,35): warning CS1998: This async method lacks 'await' operators and will run synchronously. Consider using the 'await' operator to await non-blocking API calls, or 'await Task.Run(...)' to do CPU-bound work on a background thread. [/home/btcpay/src/btcpayserver/BTCPayServer/BTCPayServer.csproj]
BTCPayServer -> /home/btcpay/src/btcpayserver/BTCPayServer/bin/Release/net6.0/BTCPayServer.dll
BTCPayServer -> /home/btcpay/src/btcpayserver/BTCPayServer/bin/Release/publish/
Check the correct installation
$ head -n 3 /home/btcpay/src/btcpayserver/Build/Version.csproj | grep Version
# MiniBolt: systemd unit for BTCpay server
# /etc/systemd/system/btcpay.service
[Unit]
Description=BTCPay Server
Wants=nbxplorer.service
After=nbxplorer.service
[Service]
WorkingDirectory=/home/btcpay/src/btcpayserver
ExecStart=/home/btcpay/src/btcpayserver/run.sh
User=btcpay
Group=btcpay
# Process management
####################
Type=simple
TimeoutSec=120
[Install]
WantedBy=multi-user.target
Enable autoboot (optional)
$ sudo systemctl enable btcpay
Prepare btcpay monitoring by the systemd journal and checking the logging output. You can exit monitoring at any time with Ctrl-C
$ journalctl -f -u btcpay
Keep this terminal open, you'll need to come back here on the next step to monitor the logs
Running BTCPay Server
To keep an eye on the software movements, start your SSH program (eg. PuTTY) a second time, connect to the MiniBolt node, and log in as admin. Commands for the second session start with the prompt $2 (which must not be entered)
$ sudo systemctl start btcpay
Example of expected output on the first terminal with $ journalctl -f -u btcpay ⬇️
Jul 05 18:01:08 bbonode run.sh[2810276]: info: Configuration: Data Directory: /home/btcpay/.btcpayserver/Main
Jul 05 18:01:08 bbonode run.sh[2810276]: info: Configuration: Configuration File: /home/btcpay/.btcpayserver/Main/settings.config
Jul 05 18:01:09 bbonode run.sh[2810276]: info: BTCPayServer.Plugins.PluginManager: Loading plugins from /home/btcpay/.btcpayserver/Plugins
Jul 05 18:01:09 bbonode run.sh[2810276]: info: BTCPayServer.Plugins.PluginManager: Adding and executing plugin BTCPayServer - 1.10.3
Jul 05 18:01:09 bbonode run.sh[2810276]: info: BTCPayServer.Plugins.PluginManager: Adding and executing plugin BTCPayServer.Plugins.Shopify - 1.10.3
Jul 05 18:01:09 bbonode run.sh[2810276]: info: BTCPayServer.Plugins.PluginManager: Adding and executing plugin BTCPayServer.Plugins.PointOfSale - 1.10.3
Jul 05 18:01:09 bbonode run.sh[2810276]: info: BTCPayServer.Plugins.PluginManager: Adding and executing plugin BTCPayServer.Plugins.PayButton - 1.10.3
Jul 05 18:01:09 bbonode run.sh[2810276]: info: BTCPayServer.Plugins.PluginManager: Adding and executing plugin BTCPayServer.Plugins.NFC - 1.10.3
Jul 05 18:01:09 bbonode run.sh[2810276]: info: BTCPayServer.Plugins.PluginManager: Adding and executing plugin BTCPayServer.Plugins.Crowdfund - 1.10.3
Jul 05 18:01:09 bbonode run.sh[2810276]: info: Configuration: Supported chains: BTC
Jul 05 18:01:09 bbonode run.sh[2810276]: info: Configuration: BTC: Explorer url is http://127.0.0.1:24444/
Jul 05 18:01:09 bbonode run.sh[2810276]: info: Configuration: BTC: Cookie file is /home/btcpay/.nbxplorer/Main/.cookie
Jul 05 18:01:09 bbonode run.sh[2810276]: info: Configuration: Network: Mainnet
Jul 05 18:01:13 bbonode run.sh[2810276]: info: Configuration: Root Path: /
Jul 05 18:01:14 bbonode run.sh[2810276]: info: PayServer: Checking if any payment arrived on lightning while the server was offline...
Jul 05 18:01:14 bbonode run.sh[2810276]: info: PayServer: Processing lightning payments...
Jul 05 18:01:14 bbonode run.sh[2810276]: info: PayServer: Starting listening NBXplorer (BTC)
Jul 05 18:01:14 bbonode run.sh[2810276]: info: PayServer: Start watching invoices
Jul 05 18:01:14 bbonode run.sh[2810276]: info: PayServer: Starting payment request expiration watcher
Jul 05 18:01:14 bbonode run.sh[2810276]: info: PayServer: 0 pending payment requests being checked since last run
Jul 05 18:01:14 bbonode run.sh[2810276]: info: Configuration: Now listening on: http://127.0.0.1:23000
Jul 05 18:01:14 bbonode run.sh[2810276]: info: PayServer: BTC: Checking if any pending invoice got paid while offline...
Jul 05 18:01:14 bbonode run.sh[2810276]: info: PayServer: BTC: 0 payments happened while offline
Jul 05 18:01:14 bbonode run.sh[2810276]: info: PayServer: Connected to WebSocket of NBXplorer (BTC)
Ensure the BTCPay Server is running and listening on the default port 23000
Now point your browser, "http://minibolt.local:23000" (or your node IP address) like "http://192.168.0.20:23000".
You can now create the first account to access the dashboard using a real (recommended) or a dummy email, and password
Congratulations! You now have the amazing BTCPay Server payment processor running
Extras (optional)
Remote access over Tor
You can easily do so by adding a Tor hidden service on the MiniBolt and accessing the BTCPay Server with the Tor browser from any device.
Ensure that you are logged in with the user admin and add the following lines to the location hidden services section, below ## This section is just for location-hidden services ## in the torrc file. Save and exit
$ sudo nano /etc/tor/torrc
# Hidden Service BTCPay Server
HiddenServiceDir /var/lib/tor/hidden_service_btcpay/
HiddenServiceVersion 3
HiddenServicePoWDefensesEnabled 1
HiddenServicePort 80 127.0.0.1:23000
Before running this script, you'll need to grant permission for this script to run as an executable
$ chmod +x ./dotnet-install.sh
Set the new VERSION environment variable, for example, 6.0 -> 8.0
$ VERSION=8.0
Install .NET Core SDK
$ ./dotnet-install.sh --channel $VERSION
Example of expected output ⬇️
dotnet-install: Attempting to download using aka.ms link https://dotnetcli.azureedge.net/dotnet/Sdk/6.0.417/dotnet-sdk-6.0.417-linux-x64.tar.gz
dotnet-install: Remote file https://dotnetcli.azureedge.net/dotnet/Sdk/6.0.417/dotnet-sdk-6.0.417-linux-x64.tar.gz size is 186250370 bytes.
dotnet-install: Extracting zip from https://dotnetcli.azureedge.net/dotnet/Sdk/6.0.417/dotnet-sdk-6.0.417-linux-x64.tar.gz
dotnet-install: Downloaded file size is 186250370 bytes.
dotnet-install: The remote and local file sizes are equal.
dotnet-install: Installed version is 6.0.417
dotnet-install: Adding to current process PATH: `/home/btcpay/.dotnet`. Note: This change will be visible only when sourcing script.
dotnet-install: Note that the script does not resolve dependencies during installation.
dotnet-install: To check the list of dependencies, go to https://learn.microsoft.com/dotnet/core/install, select your operating system and check the "Dependencies" section.
dotnet-install: Installation finished successfully.
(Optional) If you haven't done this before, to improve your privacy, disable the .NET Core SDK telemetry
hint: You have divergent branches and need to specify how to reconcile them.
hint: You can do so by running one of the following commands sometime before
hint: your next pull:
hint:
hint: git config pull.rebase false # merge (the default strategy)
hint: git config pull.rebase true # rebase
hint: git config pull.ff only # fast-forward only
hint:
hint: You can replace "git config" with "git config --global" to set a default
hint: preference for all repositories. You can also pass --rebase, --no-rebase,
hint: or --ff-only on the command line to override the configured default per
hint: invocation.
You need to do and exec the before git pull command again:
$ git config pull.rebase false
Press Ctrl+X when the nano automatically opens the MERGE_MSG to no apply modifications
Build it
$ ./build.sh
Check the correct installation update
$ head -n 6 /home/btcpay/src/NBXplorer/NBXplorer/NBXplorer.csproj | grep Version
Example of expected output:
> <Version>2.4.3</Version>
Go back to the admin user
$ exit
Start the NBXplorer & BTCpay server again. Monitor logs with $ journalctl -f -u nbxplorer & $ journalctl -f -u btcpay to ensure that all is running well
If the prompt shows you: fatal: Need to specify how to reconcile divergent branches.⬇️
$ git config pull.rebase false
Press Ctrl+X when the nano automatically opens the MERGE_MSG to no apply modifications
Build it
$ ./build.sh
Example of expected output ⬇️
Determining projects to restore...
Restored /home/btcpay/src/btcpayserver/BTCPayServer.Abstractions/BTCPayServer.Abstractions.csproj (in 965 ms).
Restored /home/btcpay/src/btcpayserver/BTCPayServer.Client/BTCPayServer.Client.csproj (in 965 ms).
Restored /home/btcpay/src/btcpayserver/BTCPayServer.Common/BTCPayServer.Common.csproj (in 978 ms).
Restored /home/btcpay/src/btcpayserver/BTCPayServer.Data/BTCPayServer.Data.csproj (in 113 ms).
Restored /home/btcpay/src/btcpayserver/BTCPayServer.Rating/BTCPayServer.Rating.csproj (in 178 ms).
Restored /home/btcpay/src/btcpayserver/BTCPayServer/BTCPayServer.csproj (in 1.9 sec).
BTCPayServer.Client -> /home/btcpay/src/btcpayserver/BTCPayServer.Client/bin/Release/netstandard2.1/BTCPayServer.Client.dll
BTCPayServer.Common -> /home/btcpay/src/btcpayserver/BTCPayServer.Common/bin/Release/net8.0/BTCPayServer.Common.dll
BTCPayServer.Rating -> /home/btcpay/src/btcpayserver/BTCPayServer.Rating/bin/Release/net8.0/BTCPayServer.Rating.dll
BTCPayServer.Abstractions -> /home/btcpay/src/btcpayserver/BTCPayServer.Abstractions/bin/Release/net8.0/BTCPayServer.Abstractions.dll
BTCPayServer.Data -> /home/btcpay/src/btcpayserver/BTCPayServer.Data/bin/Release/net8.0/BTCPayServer.Data.dll
BTCPayServer -> /home/btcpay/src/btcpayserver/BTCPayServer/bin/Release/net8.0/BTCPayServer.dll
BTCPayServer -> /home/btcpay/src/btcpayserver/BTCPayServer/bin/Release/publish/
Check the correct installation update
$ head -n 3 /home/btcpay/src/btcpayserver/Build/Version.csproj | grep Version
Example of expected output:
> <Version>1.12.0</Version>
Go back to the admin user
$ exit
Start the BTCpay server again. Monitor logs with $ journalctl -f -u btcpay to ensure that all is running well
$ sudo systemctl start btcpay
Uninstall
Uninstall service & user
Ensure you are logged in with the user admin, stop btcpay and nbxplorer services
$ sudo systemctl stop btcpay
$ sudo systemctl stop nbxplorer
Delete btcpay and nbxplorer services
$ sudo rm /etc/systemd/system/btcpay.service
$ sudo rm /etc/systemd/system/nbxplorer.service
Ensure you are logged in with the user admin. Delete the btcpay user.
Don't worry about userdel: btcpay mail spool (/var/mail/btcpay) not found output, the uninstall has been successfull
$ sudo userdel -rf btcpay
Uninstall Firewall configuration & Reverse proxy
Ensure you are logged in with the user admin, display the UFW firewall rules, and note the numbers of the rules for BTCPay Server (e.g. X and Y below)
$ sudo ufw status numbered
Expected output:
> [Y] 23000 ALLOW IN Anywhere # allow BTCPay Server from anywhere
Delete the rule with the correct number and confirm with yes
$ sudo ufw delete X
Uninstall Tor hidden service
Ensure you are logged in with the user admin
$ sudo nano /etc/tor/torrc
Comment or remove the btcpay hidden service in the torrc. Save and exit
# Hidden Service BTCPay Server
#HiddenServiceDir /var/lib/tor/hidden_service_btcpay/
#HiddenServiceVersion 3
#HiddenServicePoWDefensesEnabled 1
#HiddenServicePort 80 127.0.0.1:23000