Table of Contents
Who This Guide Is For
This guide is mainly for open‑source software developers who want to accept donations or sell paid services for bitcoins. Ideally, you should have basic Linux knowledge and some self‑hosting experience.
You can also share your node with friends and other open‑source projects. Collectively owned nodes reduce costs, simplify maintenance, and let people without Linux ops skills integrate Bitcoin payments into their work, accepting payments from anywhere in the world with no exceptions and no red tape.
Why Bitcoin
A lot of developers I know have to work around sanctions or simply don’t want to form legal entities or kneel to financial institutions. Open‑source software needs open‑source money. As you’ll see, self‑hosting your own “bank account” is pretty easy. You stay in full control, no paperwork or permission needed to send or receive money.
Expected Functionality
After finishing this guide, you’ll have a fully functional Bitcoin and Lightning node that can create and process invoices via a straightforward REST API. You can also use this server to make Bitcoin payments or withdraw funds to your personal wallet.
Fixed on‑chain addresses are great for donations, while programmable Lightning invoices let you integrate Bitcoin payments into your backend. With this setup, you can sell premium features or offer a hosted version of your FOSS software for a monthly fee, for example.
High‑Level Technical Overview
My distro of choice for this setup is Arch Linux, but you should be able to reproduce it on any popular Linux distro. They’re all Systemd‑based, and that’s the only hard dependency.
The two moving parts (bitcoind and lnd) are self‑contained binaries, each wrapped in a Systemd service for easy daemon management and better observability.
The core priority here is simplicity and easy maintenance, so I won’t cover security hardening. For small amounts of money, that’s often overkill. It’s usually easier to sweep the funds out periodically than to be paranoid. There’s no point in hardening a system that might only bring in $100 in donations and other payments, a harsh reality for many open‑source projects, especially early on.
Part 1: Preparing the Server
You’ll need a server with a public IP address. Aim for at least 2 GB of RAM and ideally 2+ (v)CPUs. I’ve run this setup on old, slow hardware before, and it worked fine. If you don’t have enough RAM, adding swap will help you avoid the OOM killer.
Provision a fresh server and log in via SSH as root. This guide prioritizes simplicity, so we won’t create extra users or set up complicated permissions.
Because we’ll use Let’s Encrypt, make sure you have a domain name that you can point to this server so HTTPS works from the start. This guide uses lnd.example.com, and you’re supposed to change it to your actual domain.
Part 1: bitcoind
Getting the Binary
You need to download a binary called bitcoind and place it in the /usr/local/bin/ directory. You can also put the bitcoin-cli binary there, it’s a handy admin interface for your node.
For a detailed walkthrough on downloading Bitcoin binaries, check out one of my other posts: Bitcoin 29 Unpacked.
Config
Create /root/.bitcoin/bitcoin.conf:
# Enable RPC but only allow local connections
server=1
rpcbind=127.0.0.1
rpcallowip=127.0.0.1
# Measured in MB
# Can be adjusted or disabled depending on available disk space
prune=25000
# Improved Systemd integration
startupnotify=systemd-notify --ready
shutdownnotify=systemd-notify --stopping
Systemd Service
Create /etc/systemd/system/bitcoind.service:
[Unit]
After=network-online.target
Wants=network-online.target
[Service]
Type=notify
ExecStart=/usr/local/bin/bitcoind -datadir=/root/.bitcoin
[Install]
WantedBy=multi-user.target
Sync
systemctl enable --now bitcoind.service
Your Bitcoin node will start syncing. It stores all its data in /root/.bitcoin/, so expect that directory to grow steadily.
Wait for your node to finish syncing. This can take anywhere from hours to days. Here’s how you can check the progress:
bitcoin-cli getblockchaininfo
{
"chain": "main",
"blocks": 934319,
"headers": 934319,
"bestblockhash": "000000000000000000018b7caca393ae6b0e009828b03d91fbdc415d70d6abd2",
"bits": "1701fca1",
"target": "00000000000000000001fca10000000000000000000000000000000000000000",
"difficulty": 141668107417558.2,
"time": 1769759954,
"mediantime": 1769757145,
"verificationprogress": 0.9999960291979498,
"initialblockdownload": false,
"chainwork": "00000000000000000000000000000000000000010b06672e15fc4dd7fd029150",
"size_on_disk": 5399547383,
"pruned": true,
"pruneheight": 931376,
"automatic_pruning": true,
"prune_target_size": 26214400000,
"warnings": [
]
}
You should check the verificationprogress field. If it’s close to 1.0, your node is fully synced and you can move on to installing a Lightning daemon.
Part 2: lnd
Lightning implementations run on top of bitcoind, which is why we installed it first. Now it’s time to add the Lightning layer, this enables cheap, instant payments and provides a straightforward REST API for integrating this borderless, permissionless system into your projects.
Getting the Binary
Grab the latest release from the official GitHub repo:
https://github.com/lightningnetwork/lnd/releases
Once you have the lnd binary, put it in /usr/local/bin/, the same directory where you placed bitcoind and bitcoin-cli in part 1. You should also put lncli there. It’s like bitcoin-cli, but for Lightning.
You’ll need to run lnd manually (and without a config file) the first time to set a wallet password.
Config
Create /root/.lnd/lnd.conf:
[Application Options]
listen=:9735
rpclisten=127.0.0.1:10009
restlisten=0.0.0.0:443
restlisten=[::]:443
letsencryptdomain=lnd.example.com
wallet-unlock-password-file=/root/.lnd/password.txt
[Bitcoin]
bitcoin.mainnet=true
bitcoin.node=bitcoind
[Bitcoind]
bitcoind.rpccookie=/root/.bitcoin/.cookie
bitcoind.rpcpolling=true
Note that you need to create a file /root/.lnd/password.txt manually and put your wallet unlock password there.
Change lnd.example.com to your actual domain or subdomain.
Systemd Service
Create /etc/systemd/system/lnd.service:
[Unit]
Requires=bitcoind.service
After=bitcoind.service
[Service]
Type=notify
ExecStart=/usr/local/bin/lnd
TimeoutStartSec=1200
ExecStop=/usr/local/bin/lncli stop
TimeoutStopSec=3600
Restart=on-failure
RestartSec=60
[Install]
WantedBy=multi-user.target
Sync
systemctl enable --now lnd.service
I’m not sure if you should wait, but I usually let lnd run for a few minutes and check the logs to make sure it’s healthy and there aren’t any errors:
journalctl --unit lnd.service --since '5m ago'
Part 3: Inbound Liquidity
Lightning nodes need inbound liquidity to accept payments. You can ask a friend to open a channel to you, or just buy liquidity from one of the well‑known providers:
Last time I checked, $50k worth of inbound liquidity costs about $10.
Part 4: First Invoice
See this page with examples in Javascript and Python:
https://lightning.engineering/api-docs/api/lnd/lightning/add-invoice/
Here’s a curl example:
curl -X POST \
-H "Grpc-Metadata-macaroon: 030303" \
-H "Content-Type: application/json" \
https://lnd.example.com/v1/invoices \
-d '{ "value": 25, "memo": "test", "expiry": 3600 }'
On the first REST API call, lnd will try to get a TLS certificate from Let’s Encrypt. Double‑check that your domain is set up and points to your server’s IP address.
Macaroons are essentially bearer tokens. Your lnd instance generated a set of default macaroons in /root/.lnd/data/chain/bitcoin/mainnet/.
Different macaroons have different permissions. For invoices, I recommend using this one to follow the principle of least privilege:
xxd -ps -u -c 1000 /root/.lnd/data/chain/bitcoin/mainnet/invoices.macaroon
You’ll get a HEX string that you should put into the Grpc-Metadata-macaroon header when making REST API calls.
Part 5: Checking Invoice Status
When you create an invoice, the JSON response returns an object with the following structure:
# {
"r_hash": "<bytes>",
"payment_request": "<string>",
"add_index": "<uint64>",
"payment_addr": "<bytes>",
}
You should pay attention to these two fields:
r_hash- This is the invoice ID, which you’ll use to check the invoice status.payment_request- This is the invoice itself, show it to your users as a string or QR code.
The r_hash field is base64‑encoded, but the invoice lookup endpoint expects it in HEX. Convert it like this:
echo -n "r_hash" | base64 -d | xxd -ps -c 64
API reference:
https://lightning.engineering/api-docs/api/lnd/lightning/lookup-invoice/
curl -H "Grpc-Metadata-macaroon: A012345" https://lnd.example.com/v1/invoice/fa045443 | jq
If the state field is SETTLED, the invoice has been paid and you can take whatever action you need.
Part 6: Further Reading
If you want to get comfortable with bitcoin-cli, here’s a great course:
https://github.com/BlockchainCommons/Learning-Bitcoin-from-the-Command-Line
To build your LND skills, check out these links:
https://docs.lightning.engineering/
https://lightning.engineering/api-docs/category/api-reference/
If you’re interested in hosting Lightning wallets for friends and other projects, take a look at this nice web‑wallet UI that runs on top of LND:
Conclusion
While setting up a Bitcoin node isn’t the easiest thing in the world, it’s often simpler than dealing with traditional payment providers. It’s also an indispensable tool for anyone serious about self‑sovereignty.
The truth is, there are no real alternatives if you want to enable truly borderless payments with no exceptions. Open‑source software has no borders, and neither should open‑source money.