Skip to content

Instantly share code, notes, and snippets.

@oakwhiz
Last active February 12, 2024 02:15
Show Gist options
  • Save oakwhiz/0312f1d6f94b519875773c1eb22ab523 to your computer and use it in GitHub Desktop.
Save oakwhiz/0312f1d6f94b519875773c1eb22ab523 to your computer and use it in GitHub Desktop.
How to set up a bitcoin node on Debian using the official "hardened" systemd unit file, with external storage + testnet

How to set up a bitcoin node on Debian using the official "hardened" systemd unit file, with external storage + testnet

Disclaimer of liability & warnings

It's your responsibility to handle Bitcoin correctly, failure to do so could cause cybersecurity breaches or financial losses, which you are solely responsible for. You should change all passwords, ensure you have a good firewall configuration, keep up-to-date, only use trusted sources for software and configuration, maintain your hardware, ensure working backups and redundancies, etc. You must also double check that these documents are not outdated, malicious or tampered with. The author(s) of these documents cannot be held responsible for losses or damages arising from use or misuse of the information within.

If you are using a bitcoin node in a commercial setting, you need to think about things like redundancy, uptime and recovery. What would happen if you were relying on a single bitcoin node and it went down? How long will it last if you "set it and forget it"? What if you need to make changes to it later or update it? If you don't have a strategy for this you may run into issues eventually. There are things you can do to improve the setup, that are outside the scope of this document.

What is the goal?

We want to use the official unit file for bitcoind, which claims to be "hardened," while making the least amount of changes. We also want to move bulk blockchain storage off of the root volume and onto mounted storage such as a ZFS dataset or a VM virtual disk. Additionally we want to run the same config side-by-side for Bitcoin's testnet.

Many installation guides online omit all of the "hardening" configuration, which admittedly isn't a magic shield, but I think it's a good idea to follow the official instructions as much as possible.

If additional security is desired, a different user can be provided for mainnet/testnet, or a different machine, etc. There are also probably additional things you can do with cgroups, etc. to help.

Systemd and symlinks - the first problem

Can't use symlinks with StateDirectory= etc. so we'll use a bind mount. Since systemd runs the show these days we'll try to use systemd to control the bind mount rather than our good old friend /etc/fstab just to keep things clean. Unfortunately systemd requires special naming rules for the bind mount which includes escaping ordinary things like hyphens which don't normally require escaping.

This is solved with the basic installation instructions in the next section, but here is an example of how that works, in case you want to rename things:

Systemd escaping example (pay attention to shell vs elsewhere):

$ systemd-escape -p --suffix=mount "/var/lib/bitcoind-testnet"
var-lib-bitcoind\x2dtestnet.mount
$ sudo cat /etc/systemd/system/var-lib-bitcoind\\x2dtestnet.mount
...
Where=/var/lib/bitcoind-testnet
...
$ sudo cat /etc/systemd/system/bitcoind-testnet.service
...
After=network-online.target var-lib-bitcoind\x2dtestnet.mount
Wants=network-online.target var-lib-bitcoind\x2dtestnet.mount
...

Basic installation guide

Some assumptions:

  • You should be logged in as a normal user with sudo access.
  • root should be properly locked out, proper firewall in place etc.
  • Path names are "like default" for bitcoind configuration etc. on mainnet, and modified so the directories have -testnet at the end of the name on testnet
  • Mounted bulk storage already set up at /bitcoin, we will create and set permission on subdirectories mainnet and testnet, you need to create and apply correct permissions etc. on /bitcoin
  • Systemd to handle bind mounts
  • User named bitcoinuser already set up (don't let this user log in, or use SSH, lock it down as much as possible)
  • Assuming certain preferences around OOM killer score and nice value, there are more ways to lock down testnet for example
  • Mainnet and testnet will be running the same version of bitcoind

Download, verify and install x86-64 binaries. You should use the latest version, not necessarily what's here. You also need to pay attention to the keys used and the output of the verification, this is your responsibility to ensure security.

sudo apt install git gpg htop
wget https://bitcoincore.org/bin/bitcoin-core-26.0/bitcoin-26.0-x86_64-linux-gnu.tar.gz
wget https://bitcoincore.org/bin/bitcoin-core-26.0/SHA256SUMS
wget https://bitcoincore.org/bin/bitcoin-core-26.0/SHA256SUMS.asc
sha256sum --ignore-missing --check SHA256SUMS
git clone https://github.com/bitcoin-core/guix.sigs
gpg --import guix.sigs/builder-keys/*
gpg --verify SHA256SUMS.asc

If all verification looks good, then proceed to installation of the binaries:

tar -xzvf bitcoin-26.0-x86_64-linux-gnu.tar.gz
sudo install -m 0755 -o root -g root -t /usr/local/bin bitcoin-26.0/bin/*

Set up directories for both mainnet and testnet:

sudo mkdir /bitcoin/mainnet /bitcoin/testnet /etc/bitcoin /run/bitcoind /var/lib/bitcoind /etc/bitcoin-testnet /run/bitcoind-testnet /var/lib/bitcoind-testnet
sudo chown -R bitcoinuser:bitcoinuser /bitcoin/mainnet /bitcoin/testnet /etc/bitcoin /run/bitcoind /var/lib/bitcoind /etc/bitcoin-testnet /run/bitcoind-testnet /var/lib/bitcoind-testnet
sudo chmod 0700 /bitcoin/mainnet /bitcoin/testnet
sudo chmod 0710 /etc/bitcoin /run/bitcoind /var/lib/bitcoind /etc/bitcoin-testnet /run/bitcoind-testnet /var/lib/bitcoind-testnet

Configure mainnet and testnet: (contents are attached alongside this document)

sudo nano /etc/bitcoin/bitcoin.conf
sudo nano /etc/bitcoin-testnet/bitcoin.conf
sudo nano /etc/systemd/system/bitcoind-mainnet.service
sudo nano /etc/systemd/system/bitcoind-testnet.service
sudo nano /etc/systemd/system/var-lib-bitcoind.mount
sudo nano /etc/systemd/system/var-lib-bitcoind\\x2dtestnet.mount

Start up services carefully:

sudo systemctl daemon-reload
sudo systemctl start var-lib-bitcoind.mount var-lib-bitcoind\\x2dtestnet.mount

Check to see if things are OK with the bind mounts before proceeding:

sudo systemctl status var-lib-bitcoind.mount
sudo systemctl status var-lib-bitcoind\\x2dtestnet.mount

If it looks good then proceed to the remaining services and check and enable them:

sudo systemctl start bitcoind-mainnet.service bitcoind-testnet.service
sudo systemctl status bitcoind-mainnet.service
sudo systemctl status bitcoind-testnet.service
sudo systemctl enable bitcoind-mainnet.service bitcoind-testnet.service
sudo journalctl -ef
htop

Do not systemctl enable the var-lib-bitcoind.mount or var-lib-bitcoind\\x2dtestnet.mount units or else it will not survive a reboot.

# /etc/bitcoin-testnet/bitcoin.conf
testnet=1
server=1
rpcuser=testuser
rpcpassword=testpassword
# /etc/bitcoin/bitcoin.conf
server=1
rpcuser=user
rpcpassword=password
# /etc/systemd/system/bitcoind-mainnet.service
# It is not recommended to modify this file in-place, because it will
# be overwritten during package upgrades. If you want to add further
# options or overwrite existing ones then use
# $ systemctl edit bitcoind.service
# See "man systemd.service" for details.
# Note that almost all daemon options could be specified in
# /etc/bitcoin/bitcoin.conf, but keep in mind those explicitly
# specified as arguments in ExecStart= will override those in the
# config file.
[Unit]
Description=Bitcoin daemon (Mainnet)
Documentation=https://github.com/bitcoin/bitcoin/blob/master/doc/init.md
# https://www.freedesktop.org/wiki/Software/systemd/NetworkTarget/
After=network-online.target var-lib-bitcoind.mount
Wants=network-online.target var-lib-bitcoind.mount
[Service]
ExecStart=/usr/local/bin/bitcoind -pid=/run/bitcoind/bitcoind.pid \
-conf=/etc/bitcoin/bitcoin.conf \
-datadir=/var/lib/bitcoind \
-startupnotify='systemd-notify --ready' \
-shutdownnotify='systemd-notify --stopping'
# Make sure the config directory is readable by the service user
PermissionsStartOnly=true
ExecStartPre=/bin/chgrp bitcoinuser /etc/bitcoin
Nice=-10
OOMScoreAdjust=100
# Process management
####################
Type=notify
NotifyAccess=all
PIDFile=/run/bitcoind/bitcoind.pid
Restart=on-failure
TimeoutStartSec=infinity
TimeoutStopSec=600
# Directory creation and permissions
####################################
# Run as bitcoin:bitcoin
User=bitcoinuser
Group=bitcoinuser
# /run/bitcoind
RuntimeDirectory=bitcoind
RuntimeDirectoryMode=0710
# /etc/bitcoin
ConfigurationDirectory=bitcoin
ConfigurationDirectoryMode=0710
# /var/lib/bitcoind
StateDirectory=bitcoind
StateDirectoryMode=0710
# Hardening measures
####################
# Provide a private /tmp and /var/tmp.
PrivateTmp=true
# Mount /usr, /boot/ and /etc read-only for the process.
ProtectSystem=full
# Deny access to /home, /root and /run/user
ProtectHome=true
# Disallow the process and all of its children to gain
# new privileges through execve().
NoNewPrivileges=true
# Use a new /dev namespace only populated with API pseudo devices
# such as /dev/null, /dev/zero and /dev/random.
PrivateDevices=true
# Deny the creation of writable and executable memory mappings.
MemoryDenyWriteExecute=true
[Install]
WantedBy=multi-user.target
# /etc/systemd/system/bitcoind-testnet.service
# It is not recommended to modify this file in-place, because it will
# be overwritten during package upgrades. If you want to add further
# options or overwrite existing ones then use
# $ systemctl edit bitcoind.service
# See "man systemd.service" for details.
# Note that almost all daemon options could be specified in
# /etc/bitcoin/bitcoin.conf, but keep in mind those explicitly
# specified as arguments in ExecStart= will override those in the
# config file.
[Unit]
Description=Bitcoin daemon (Testnet)
Documentation=https://github.com/bitcoin/bitcoin/blob/master/doc/init.md
# https://www.freedesktop.org/wiki/Software/systemd/NetworkTarget/
After=network-online.target var-lib-bitcoind\x2dtestnet.mount
Wants=network-online.target var-lib-bitcoind\x2dtestnet.mount
[Service]
ExecStart=/usr/local/bin/bitcoind -pid=/run/bitcoind-testnet/bitcoind.pid \
-conf=/etc/bitcoin-testnet/bitcoin.conf \
-datadir=/var/lib/bitcoind-testnet \
-startupnotify='systemd-notify --ready' \
-shutdownnotify='systemd-notify --stopping'
# Make sure the config directory is readable by the service user
PermissionsStartOnly=true
ExecStartPre=/bin/chgrp bitcoinuser /etc/bitcoin-testnet
Nice=-10
OOMScoreAdjust=100
# Process management
####################
Type=notify
NotifyAccess=all
PIDFile=/run/bitcoind-testnet/bitcoind.pid
Restart=on-failure
TimeoutStartSec=infinity
TimeoutStopSec=600
# Directory creation and permissions
####################################
# Run as bitcoin:bitcoin
User=bitcoinuser
Group=bitcoinuser
# /run/bitcoind
RuntimeDirectory=bitcoind-testnet
RuntimeDirectoryMode=0710
# /etc/bitcoin
ConfigurationDirectory=bitcoin-testnet
ConfigurationDirectoryMode=0710
# /var/lib/bitcoind
StateDirectory=bitcoind-testnet
StateDirectoryMode=0710
# Hardening measures
####################
# Provide a private /tmp and /var/tmp.
PrivateTmp=true
# Mount /usr, /boot/ and /etc read-only for the process.
ProtectSystem=full
# Deny access to /home, /root and /run/user
ProtectHome=true
# Disallow the process and all of its children to gain
# new privileges through execve().
NoNewPrivileges=true
# Use a new /dev namespace only populated with API pseudo devices
# such as /dev/null, /dev/zero and /dev/random.
PrivateDevices=true
# Deny the creation of writable and executable memory mappings.
MemoryDenyWriteExecute=true
[Install]
WantedBy=multi-user.target
# /etc/systemd/system/var-lib-bitcoind.mount
[Unit]
Description=Bind Mount for Bitcoin (Mainnet)
[Mount]
What=/bitcoin/mainnet
Where=/var/lib/bitcoind
Type=none
Options=bind
[Install]
WantedBy=multi-user.target
# /etc/systemd/system/var-lib-bitcoind\x2dtestnet.mount
[Unit]
Description=Bind Mount for Bitcoin (Testnet)
[Mount]
What=/bitcoin/testnet
Where=/var/lib/bitcoind-testnet
Type=none
Options=bind
[Install]
WantedBy=multi-user.target
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment