How to Run Your Own Bitcoin Node
Running your own Bitcoin node gives you a private, fully-validating source of truth — every block and transaction verified from genesis, no third party trusted. This guide is the exact Bitcoin Core setup we run in production on Ubuntu 24.04, including the address-indexed API layer (Blockbook) that plain bitcoindcan’t give you. Copy-paste ready, with the operational gotchas we actually hit.
Hardware & disk requirements
Bitcoin’s chainstate is random-I/O heavy and the address index is compaction-heavy, so a fast NVMe SSD is mandatory — a SATA SSD will technically work but drags initial sync out for a week or more; an HDD is a non-starter. Note that the full chain with txindex is ~870 GB today and only grows (Ordinals/inscriptions inflated block sizes since 2023).
| Resource | Full node (bitcoind + txindex) | + Address index (Blockbook) |
|---|---|---|
| CPU | 4+ cores | 8+ cores |
| RAM | 8–16 GB | 32–64 GB (index build is memory-hungry) |
| Disk | ~870 GB used → budget 1 TB+ NVMe | +~700 GB → budget 2 TB NVMe |
| Initial sync | 1–3 days (IBD) | + several days to build the index |
Don’t prune if you want to serve historical data — pruning frees disk but discards old blocks and is incompatible with txindex, so you lose getrawtransaction by hash and any historical lookups. An RPC-serving node runs unpruned with txindex=1.
Step 1 — Download and verify Bitcoin Core
Always verify the checksum — Bitcoin Core is a high-value target. Set VER to the current release shown on bitcoincore.org/en/download (we run 31.0.0):
VER=31.0.0 cd /tmp curl -LO https://bitcoincore.org/bin/bitcoin-core-$VER/bitcoin-$VER-x86_64-linux-gnu.tar.gz curl -LO https://bitcoincore.org/bin/bitcoin-core-$VER/SHA256SUMS # Verify the download matches the published checksum: sha256sum --ignore-missing --check SHA256SUMS # -> bitcoin-$VER-x86_64-linux-gnu.tar.gz: OK tar xzf bitcoin-$VER-x86_64-linux-gnu.tar.gz sudo install -m755 bitcoin-$VER/bin/bitcoind bitcoin-$VER/bin/bitcoin-cli /usr/local/bin/ bitcoind --version | head -1
Step 2 — Dedicated user, data dir, and config
Run as a non-root user. Generate a strong RPC password and never commit it:
sudo useradd --system --no-create-home --shell /usr/sbin/nologin bitcoin sudo mkdir -p /var/lib/bitcoin /etc/bitcoin sudo chown bitcoin:bitcoin /var/lib/bitcoin openssl rand -hex 32 # <- use this as your rpcpassword below
Create /etc/bitcoin/bitcoin.conf. RPC is bound to localhost — only widen rpcallowipto specific firewalled IPs if something off-box needs it. The ZMQ feeds are only required if you’ll add the Blockbook index in Step 5.
server=1 disablewallet=1 # RPC-only node, no wallet txindex=1 # full tx index — needed for getrawtransaction by hash prune=0 # keep the whole chain dbcache=4096 # bigger = faster initial sync, more RAM maxmempool=300 listen=1 maxconnections=64 rpcbind=127.0.0.1 rpcallowip=127.0.0.1 rpcport=8332 rpcuser=bitcoin rpcpassword=<PASTE_THE_GENERATED_PASSWORD> rpcthreads=8 rpcworkqueue=64 datadir=/var/lib/bitcoin # ZMQ feeds (only needed for an indexer like Blockbook/electrs): zmqpubhashblock=tcp://127.0.0.1:38330 zmqpubrawblock=tcp://127.0.0.1:38330 zmqpubhashtx=tcp://127.0.0.1:38330 zmqpubrawtx=tcp://127.0.0.1:38330
Permissions gotcha: the bitcoinuser must be able to read the config or the daemon won’t start. Lock it down but keep it group-readable:
sudo chown -R root:bitcoin /etc/bitcoin sudo chmod 750 /etc/bitcoin sudo chmod 640 /etc/bitcoin/bitcoin.conf
Step 3 — systemd service
Create /etc/systemd/system/bitcoind.service. TimeoutStartSec=infinitymatters — the initial block download takes days and you don’t want systemd killing it as “failed to start”. TimeoutStopSec=600 gives bitcoind time to flush its UTXO cache on shutdown (kill it early and you risk a slow startup re-check).
[Unit] Description=Bitcoin Core daemon After=network-online.target Wants=network-online.target [Service] Type=simple User=bitcoin Group=bitcoin ExecStart=/usr/local/bin/bitcoind -conf=/etc/bitcoin/bitcoin.conf Restart=on-failure RestartSec=10 TimeoutStartSec=infinity TimeoutStopSec=600 PrivateTmp=true NoNewPrivileges=true [Install] WantedBy=multi-user.target
Step 4 — Start and watch the sync
sudo systemctl daemon-reload sudo systemctl enable --now bitcoind # Sync status — verificationprogress reaches ~0.9999 when caught up: bitcoin-cli -conf=/etc/bitcoin/bitcoin.conf getblockchaininfo \ | grep -E '"blocks"|"headers"|"verificationprogress"|"size_on_disk"' # A quick RPC smoke test: bitcoin-cli -conf=/etc/bitcoin/bitcoin.conf getblockcount
Initial block download (IBD) validates every signature from genesis — it’s CPU- and disk-bound and takes 1–3 days on NVMe. When blocks equals headers and verificationprogress is 0.9999…, you’re at the tip. Point your apps at http://127.0.0.1:8332 with the RPC user/password.
Step 5 (optional) — Add an address-indexed API with Blockbook
Here’s the catch most guides skip: bitcoind cannot answer “what’s the balance and history of address X?” It has no address index — only per-transaction lookups. To serve wallet-style queries (address balances, full history, xpub/descriptor scanning, a REST + WebSocket API), you need a separate indexer. We run Trezor’s Blockbook (electrs is the lighter alternative if you only need Electrum-protocol queries).
Blockbook reads from your synced bitcoind over RPC + the ZMQ feeds you configured above, and builds its own RocksDB address index (~700 GB for Bitcoin). Build it from the repo’s packaging (it produces a per-coin install under /opt/coins/); it links RocksDB and ZMQ at runtime, so install those libraries first:
sudo apt-get install -y libsnappy1v5 liblz4-1 libzstd1 libbz2-1.0 \ libgflags2.2 zlib1g libgomp1 libzmq5
Run Blockbook under systemd against your bitcoind. A minimal unit (it exposes a public REST/WS API on :9130 and an internal port on :9030):
[Unit] Description=Blockbook daemon (Bitcoin) After=network.target [Service] Type=simple User=blockbook-bitcoin ExecStart=/opt/coins/blockbook/bitcoin/bin/blockbook \ -blockchaincfg=/opt/coins/blockbook/bitcoin/config/blockchaincfg.json \ -datadir=/opt/coins/data/bitcoin/blockbook/db \ -sync -internal=:9030 -public=:9130 \ -dbcache=1073741824 -enablesubnewtx -extendedindex Restart=on-failure TimeoutStopSec=300 LimitNOFILE=500000 [Install] WantedBy=multi-user.target
⚠️ The OOM lesson we learned the hard way: if Blockbook is killed mid-write (most commonly an out-of-memory kill during the initial index build), its RocksDB state is marked inconsistent and -repair / -fixutxowon’t clear it — you reindex from scratch, losing days. Give the box swapand cap Blockbook’s memory via a systemd drop-in (/etc/systemd/system/blockbook-bitcoin.service.d/limits.conf) so it can’t starve bitcoind or the OS:
[Service] MemoryHigh=36G MemoryMax=44G OOMScoreAdjust=-100 RestartSec=60
The honest part: what running it actually costs
The install is an afternoon. The ongoing reality is the expensive part:
- Initial sync is days, not minutes. Full validation of ~885k blocks is CPU-bound, and with the address index on top you’re looking at the better part of a week before you can serve a single query.
- Address queries need a second heavyweight system. bitcoind alone can’t do it. Blockbook is another ~700 GB, its own multi-day build, and — as above — it reindexes from scratch if it ever gets OOM-killed mid-write.
- The disk never stops growing. ~870 GB today and climbing; budget for migrations to bigger volumes (we’ve done exactly that). Pruning isn’t an option if you serve history.
- Redundancy. One node is a single point of failure. Real uptime means two of everything plus a load balancer — double the disk, double the babysitting.
- The real bill is engineer-hours, not the server. We did the full math in Self-Hosted Node vs RPC Provider.
…or skip all of it
SwiftNodes gives you a fully-synced Bitcoin endpoint plus the address-indexed API — balances, full transaction history, and xpub/descriptor scanning — without the ~870 GB chain, the ~700 GB index, or the OOM babysitting. Flat-rate pricing (no per-call metering), Bitcoin alongside dozens of other chains under one key, and a free tier to start.
Grab a key at swiftnodes.io and point your app at https://rpc.swiftnodes.io/rpc/btc?key=YOUR_API_KEY.