Electrum server
Build Electrs from source, index the blockchain for Electrum-protocol wallets, wrap the raw TCP port in TLS with stunnel so Sparrow, Electrum, and hardware wallets can connect privately.
Your own Bitcoin Core is great at answering questions about blocks. It's lousy at answering "what's the balance of this address?", because doing that means walking the entire chain every time. Electrs bridges the gap: it reads blocks from Bitcoin Core, builds an index keyed by address and script, and serves answers on the Electrum protocol that desktop wallets like Sparrow, the BitBoxApp, Electrum, or Specter already speak.
This is why running Electrs next to your node matters for privacy. Without it, a wallet querying "public" Electrum servers tells strangers exactly which addresses belong to you. With Electrs local, the query never leaves the Pi.
What you'll do
- Install Rust so you can build Electrs, the project doesn't ship prebuilt binaries for ARM.
- Compile and install
electrs0.11.1. - Put it behind stunnel for TLS on the LAN.
- Wire it up as a systemd service so it starts on boot and stays up.
Expect a long initial index
Hands-on setup is about 30 minutes. The slow part comes after: once
electrs starts, it reads the whole chain from bitcoind and builds
its own address index. On a Pi 5 with NVMe that's roughly 8 to 12
hours; on a Pi 4 with USB SSD it can stretch past a day. The Pi
will feel sluggish while it runs, disk I/O is the bottleneck.
Nothing to babysit, check back the next day. Make sure Bitcoin Core
is fully synced (verificationprogress at 1.0) before you start,
otherwise electrs will happily index a moving target and thrash.
stunnel for raw TCP, Caddy for HTTP
Earlier versions of this guide used NGINX's stream module to wrap
Electrum's plain-TCP protocol in TLS. In v4, HTTP services get
Caddy (you'll see that in the next page), but the Electrum protocol
isn't HTTP, it's JSON-RPC over a raw TCP socket. stunnel is the
time-tested, one-config-file tool for exactly that job.
Build dependencies
Electrs is written in Rust and links against clang and cmake for
its native dependencies (mostly RocksDB). Install them once:
sudo apt install cargo clang cmake build-essential libclang-dev pkg-configUsing Debian's packaged cargo avoids the extra step of installing
rustup just for this one crate. The tradeoff is a slightly older
Rust toolchain, still current enough for Electrs 0.11.1.
Build Electrs from source
No binaries, no shortcuts: you build from a signed tag. That's the only way to know the thing running on your node matches what the maintainer, Roman Zeyde, actually released.
-
As
admin, create a workspace for Rust builds:mkdir -p /home/admin/rustMove into it:
cd /home/admin/rustClone the repo at a specific tag:
git clone --branch v0.11.1 https://github.com/romanz/electrs.gitMove into the source tree:
cd electrs -
Verify the release tag. Starting with v0.11.1 Roman signs tags with SSH instead of PGP; he publishes the signing key in a dedicated repo whose
README.mdis itself PGP-signed by his old GPG key, binding the transition.Register his SSH signing key as a trusted signer for
gitand point git's SSH-signature verification at that file.Make sure the git config directory exists:
mkdir -p ~/.config/gitWrite Roman's SSH key into the
allowed_signersfile:echo 'git@romanzey.de ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAZVq/3fgkildjN/MqEnhrP5550sDpFzGxMwevr5q/9w' \ > ~/.config/git/allowed_signersTell git to verify signatures via SSH:
git config --global gpg.format sshPoint git at the trusted-signers file:
git config --global gpg.ssh.allowedSignersFile ~/.config/git/allowed_signersVerify the release tag:
git verify-tag v0.11.1Expected output:
Good "git" signature for git@romanzey.de with ED25519 key SHA256:GifMn7F2swVKyn6MewbQHrYCs4i/bPK7gnwxhuPz/YAThe fingerprint
SHA256:GifMn7F2swVKyn6MewbQHrYCs4i/bPK7gnwxhuPz/YAis what matters; compare it against what you see in your terminal and against the key published in Roman's keys repo. -
Build it. On a Pi 5 this takes somewhere between 30 minutes and an hour; the Pi will work hard, and that's fine:
cargo build --locked --release -
Install the resulting binary to
/usr/local/bin:sudo install -m 0755 -o root -g root -t /usr/local/bin ./target/release/electrsConfirm the right version is on
PATH:electrs --versionExpected output:
v0.11.1
Service user and data directory
Like bitcoind, Electrs runs as its own user with no shell password
and a tidy data directory on the SSD.
Create the service user:
sudo adduser --disabled-password --gecos "" electrsAdd it to the bitcoin group:
sudo adduser electrs bitcoinCreate the data directory on the SSD:
sudo mkdir /data/electrsGive the electrs user ownership:
sudo chown -R electrs:electrs /data/electrsThe bitcoin group membership is what lets Electrs read
/data/bitcoin/.cookie for RPC auth.
Configuration
-
Switch to the
electrsuser:sudo su - electrsOpen a fresh config file:
nano /data/electrs/electrs.conf -
Paste:
# RaspiBolt: electrs configuration # /data/electrs/electrs.conf # Bitcoin Core, uses the .cookie for auth (group-readable) network = "bitcoin" daemon_dir = "/data/bitcoin" daemon_rpc_addr = "127.0.0.1:8332" daemon_p2p_addr = "127.0.0.1:8333" # Electrum protocol endpoint electrum_rpc_addr = "127.0.0.1:50001" db_dir = "/data/electrs/db" # Logging log_filters = "INFO" timestamp = true -
Do a first dry run to make sure the config is sane and Electrs can talk to Bitcoin Core. It'll immediately start indexing, that is expected and will take hours, but you only need to see the first few lines of progress before you cancel:
electrs --conf /data/electrs/electrs.confWithin a few seconds you should see lines like
serving Electrum RPC on 127.0.0.1:50001andindexing 2000 blocks: [1..2000]. Good. HitCtrl-Cand exit back toadmin:exit
Systemd unit
-
As
admin, create the unit file:sudo nano /etc/systemd/system/electrs.service -
Paste:
# RaspiBolt: systemd unit for electrs # /etc/systemd/system/electrs.service [Unit] Description=Electrs daemon Wants=bitcoind.service After=bitcoind.service [Service] ExecStart=/usr/local/bin/electrs --conf /data/electrs/electrs.conf Type=simple Restart=always TimeoutSec=120 RestartSec=30 KillMode=process User=electrs RuntimeDirectory=electrs RuntimeDirectoryMode=0710 # Hardening PrivateTmp=true PrivateDevices=true MemoryDenyWriteExecute=true [Install] WantedBy=multi-user.target -
Enable the service so it starts on boot:
sudo systemctl enable electrsStart it now:
sudo systemctl start electrs -
Watch it index in real time (
Ctrl-Cto exit):sudo journalctl -f -u electrs
Electrs must finish indexing before you connect a wallet
The first index pass walks the entire chain and builds the address index on disk. On a Pi 5 with an SSD plan on eight to twelve hours; on a Pi 4, closer to a day. Don't try to connect Sparrow until the log quiets down and Electrs reports a caught-up tip, pointing a wallet at an Electrum server that's still indexing will just time out.
TLS on the LAN with stunnel
Sparrow and other wallets connect to Electrum servers over TLS on
port 50002. Electrs itself serves plaintext on 50001, which is
fine inside the Pi, but you want the network traffic encrypted
even on your home LAN. stunnel is a tiny, purpose-built proxy that
wraps a plain TCP socket in TLS and has been doing exactly that for
two decades.
Install stunnel
sudo apt install stunnel4Mint a self-signed certificate
stunnel doesn't do automatic CA issuance the way Caddy does for HTTP. You generate a self-signed certificate once and point the config at it. This takes a single command:
Generate a 10-year self-signed certificate and matching key:
sudo openssl req -x509 -newkey rsa:4096 -sha256 -days 3650 -nodes \
-keyout /etc/stunnel/electrs.key \
-out /etc/stunnel/electrs.crt \
-subj "/CN=raspibolt.local"Lock the private key down so only root can read it:
sudo chmod 600 /etc/stunnel/electrs.keyTen-year validity is fine for a LAN certificate. Wallets will warn once that the certificate isn't from a public CA, you accept it, and they remember it from then on.
Configure stunnel
-
Create the service config:
sudo nano /etc/stunnel/electrs.conf -
Paste:
# RaspiBolt: stunnel TLS wrapper for Electrs # /etc/stunnel/electrs.conf pid = /var/run/stunnel4/electrs.pid [electrs] accept = 50002 connect = 127.0.0.1:50001 cert = /etc/stunnel/electrs.crt key = /etc/stunnel/electrs.key -
Enable the service so it starts on boot:
sudo systemctl enable stunnel4Restart it to load the new config:
sudo systemctl restart stunnel4 -
Open the firewall for Electrum over TLS:
sudo ufw allow 50002/tcp comment 'Electrum SSL'
Self-signed is fine on the LAN
Wallets will flag the certificate the first time, that's expected. You accept it manually once (Sparrow does this well) or pin the certificate fingerprint in the wallet config. Getting a public-CA certificate would require a real domain name and DNS setup that most home networks don't have. Internal CA plus first-use trust is the sane default here.
Main takeaway: Electrs is indexing on :50001, stunnel wraps it
in TLS on :50002, and the firewall lets LAN wallets in. The next
page points Sparrow at it.
Remote access over Tor (optional)
You can reach your Electrum server from outside the home network by exposing it as a Tor hidden service. The wallet will need Tor running locally too.
-
Add a stanza to
/etc/tor/torrc:sudo nano /etc/tor/torrc# Hidden service: Electrum HiddenServiceDir /var/lib/tor/hidden_service_electrs/ HiddenServiceVersion 3 HiddenServicePort 50002 127.0.0.1:50002 -
Reload Tor so it picks up the new hidden service:
sudo systemctl reload torRead the generated
.onionaddress:sudo cat /var/lib/tor/hidden_service_electrs/hostnameSave that
abcdefg...xyz.onionin your password manager alongside the one you may already have for SSH. The desktop-wallet page shows how to connect Sparrow to it.
Updating Electrs later
Upgrades follow the same pattern as the first build: check the
release notes,
bump 0.11.1 in lib/versions.ts, then walk the steps below.
Move into the source tree:
cd /home/admin/rust/electrsDiscard any build artefacts and stray files:
git clean -xfdFetch the latest tags from upstream:
git fetchCheck out the new release tag:
git checkout v0.11.1Verify the signature on that tag:
git verify-tag v0.11.1Clear cargo's build cache so the new version compiles cleanly:
cargo cleanBuild the new binary:
cargo build --locked --releaseKeep a copy of the currently installed binary as a rollback option:
sudo cp /usr/local/bin/electrs /usr/local/bin/electrs.oldInstall the freshly built binary on top:
sudo install -m 0755 -o root -g root -t /usr/local/bin ./target/release/electrsRestart the service to pick up the new binary:
sudo systemctl restart electrsKeep the old binary around until the new one has served traffic
for a few hours, rolling back is as simple as copying
electrs.old back on top.
Bitcoin client
Install and run Bitcoin Core (bitcoind), GPG-verify the release, tune bitcoin.conf for a Pi 5 (dbcache, maxmempool), and sync the full blockchain (IBD, initial block download) through Tor.
Blockchain explorer
Run BTC RPC Explorer on Node.js 22 behind Caddy HTTPS. Private lookup for blocks, transactions, mempool, fee estimates, UTXO queries. No leakage to public explorers.

