Build one device

Step-by-step and copy/paste friendly. This builds a local Monero node + P2Pool + XMRig on a Raspberry Pi with external SSD storage.

Safety rule

Do not expose Monero RPC to the internet. This guide binds RPC to 127.0.0.1 only.

Write these down (you’ll need them later)
  • Hostname you chose (example: hachiko)
  • How you connect (mDNS hachiko.local or IP address)
  • External drive filesystem UUID (from blkid)
  • Mountpoint you use (this guide uses /mnt/hachiko)
  • Your Monero wallet address (starts with 4…)

Assumptions

Step 1 — Hardware

Raspberry Pi 5 (8GB) + active cooling recommended. External storage recommended: 1TB+ SSD.

USB cable/adapters matter

If your SSD specs are fast but the Pi is slow, it’s often the cable/adapter. Prefer a known-good USB 3.x cable, and plug into a blue USB 3 port.

Step 2 — Flash OS + enable SSH

  1. Use Raspberry Pi Imager to flash Raspberry Pi OS (64-bit) to microSD.
  2. In Imager settings, set:
    • Hostname (example: hachiko)
    • Enable SSH
    • Set username/password
    • Configure Wi-Fi (optional)
  3. Boot the Pi and SSH in.
SSH example (Mac)
ssh hachiko@hachiko.local

If .local doesn’t resolve, use your router’s device list to find the IP and SSH to that.

Step 3 — OS updates + required packages

3.1 Update the OS

sudo apt update
sudo apt full-upgrade -y
sudo apt autoremove -y
sudo reboot

3.2 Install baseline + build dependencies (required)

sudo apt update
sudo apt install -y \
  curl wget ca-certificates gnupg jq htop tmux ufw \
  lsof psmisc dnsutils iputils-ping net-tools \
  hdparm iotop iperf3 \
  python3 python3-venv \
  git build-essential cmake pkg-config \
  libssl-dev \
  libzmq3-dev libuv1-dev libcurl4-openssl-dev \
  libsodium-dev libpgm-dev

For iperf3 select No if prompted. These extra dev packages prevent P2Pool build failures like missing CURL_LIBRARY, UV_LIBRARY, and ZMQ_INCLUDE_DIR.

Step 4 — Prepare external storage

Critical rule

Drive names can change (example: /dev/sda becomes /dev/sdb). Don’t depend on /dev/sdX. Mount by filesystem UUID in /etc/fstab.

4.1 Identify the drive + check USB speed

lsblk -d -o NAME,MODEL,SIZE,ROTA,TRAN
lsusb -t

USB speed check: you want your storage line to show 5000M (USB 3). If it shows 480M, you’re on USB 2 speed — swap cable/adapter/port.

4.2 Disk throughput sanity check

# Replace sdX with the current disk name shown by lsblk (example: /dev/sda)
sudo hdparm -Tt /dev/sdX

4.3 Unmount anything auto-mounted (if needed)

# Replace sdX with the current disk name shown by lsblk (example: /dev/sda)
sudo umount /dev/sdX1 2>/dev/null || true
sudo umount /dev/sdX2 2>/dev/null || true

4.4 Repartition + format (aligned, repeatable)

This erases the external drive. Double-check MODEL matches your SSD.

# Replace sdX with the current disk name shown by lsblk (example: /dev/sda)

# 1) Fresh GPT (DESTROYS existing partitions)
sudo parted -s /dev/sdX mklabel gpt

# 2) One aligned EXT4 partition
sudo parted -s -a optimal /dev/sdX mkpart primary ext4 0% 100%

# 3) Confirm you now have sdX1
lsblk -o NAME,MODEL,SIZE,MOUNTPOINT,FSTYPE,LABEL /dev/sdX

# 4) Format EXT4 + label
sudo mkfs.ext4 -F -L hachiko /dev/sdX1

4.5 Mount + persist via /etc/fstab (use filesystem UUID)

sudo mkdir -p /mnt/hachiko
sudo mount /dev/sdX1 /mnt/hachiko

# Show filesystem UUID (use this in fstab)
sudo blkid /dev/sdX1
Two UUIDs will appear — use the right one

Use UUID="..." (filesystem UUID). Ignore PARTUUID="...".

sudo nano /etc/fstab

# Add a line like this (replace YOUR-FILESYSTEM-UUID):
UUID=YOUR-FILESYSTEM-UUID  /mnt/hachiko  ext4  defaults,noatime,nofail  0  2

# Test it
sudo mount -a
df -h /mnt/hachiko

Step 5 — Install Monero CLI (monerod + wallet)

Download URL

Current working shortcut for ARM:

https://downloads.getmonero.org/cli/linuxarm8
cd /tmp
wget -O monero.tar.bz2 "https://downloads.getmonero.org/cli/linuxarm8"
tar -xjf monero.tar.bz2

sudo mkdir -p /opt/monero
sudo mv monero-*/ /opt/monero/

cd /opt/monero/monero-*/
sudo install -m 0755 -t /usr/local/bin monerod monero-wallet-cli
which monerod
monerod --version
which monero-wallet-cli
monero-wallet-cli --version
Common footgun

The binaries live inside /opt/monero/monero-*/. Make sure you cd into that folder before running install.

Step 6 — Configure monerod

6.1 Create a dedicated service user

sudo useradd --system --no-create-home --shell /usr/sbin/nologin monero || true

6.2 Create directories (data on SSD, logs on rootfs)

sudo mkdir -p /etc/monero
sudo mkdir -p /mnt/hachiko/monero
sudo mkdir -p /var/log/monero

sudo chown -R monero:monero /mnt/hachiko/monero /var/log/monero
sudo chmod 0755 /var/log/monero

6.3 Create config file

sudo nano /etc/monero/monerod.conf
# /etc/monero/monerod.conf

# Data on external SSD
data-dir=/mnt/hachiko/monero

# Logging
log-file=/var/log/monero/monerod.log
max-log-file-size=104857600
log-level=0

# During initial sync you may use fast
# After initial sync completes, switch back to safe
db-sync-mode=fast

# Optional pruning
prune-blockchain=1

# Local-only RPC (do not expose)
rpc-bind-ip=127.0.0.1
rpc-bind-port=18081

# ZMQ pub (required for P2Pool)
zmq-pub=tcp://127.0.0.1:18083

Step 7 — systemd service for monerod

Why PermissionsStartOnly matters

If the service runs as User=monero, pre-start commands also run as that user and may fail creating /var/log/monero. PermissionsStartOnly=true runs pre-start as root.

sudo nano /etc/systemd/system/monerod.service
[Unit]
Description=Monero Daemon (monerod)
After=network-online.target
Wants=network-online.target

[Service]
Type=simple
User=monero
Group=monero

PermissionsStartOnly=true
ExecStartPre=/usr/bin/mkdir -p /mnt/hachiko/monero /var/log/monero
ExecStartPre=/usr/bin/chown -R monero:monero /mnt/hachiko/monero /var/log/monero
ExecStartPre=/usr/bin/chmod 0755 /var/log/monero

ExecStart=/usr/local/bin/monerod --config-file=/etc/monero/monerod.conf --non-interactive

Restart=on-failure
RestartSec=5
TimeoutStopSec=120
KillSignal=SIGINT

NoNewPrivileges=true
PrivateTmp=true

[Install]
WantedBy=multi-user.target
sudo systemctl daemon-reload
sudo systemctl enable --now monerod
systemctl status monerod --no-pager -l
If monerod won’t start

Check logs and confirm RPC + ZMQ are listening:

sudo journalctl -u monerod -n 120 --no-pager
sudo ss -ltnp | egrep ':(18081|18083)\b' || echo "not listening yet"

Step 8 — Sync the blockchain

Initial sync is the long part. Let it run. Avoid power cycling. Expect hours to days depending on SSD + network.

8.1 Watch logs

sudo journalctl -u monerod -f

8.2 Quick health checks

# Ports (monerod RPC + ZMQ)
sudo ss -ltnp | egrep ':(18081|18083)\b' || echo "not listening yet"

# Height should respond even while syncing
curl -s http://127.0.0.1:18081/get_height | jq .
After initial sync completes

Switch db-sync-mode back to safe (Step 8.3). This matters for long-term stability.

Step 8a — Install the web status server (server.py)

About requirements.txt

pip install -r requirements.txt is only needed if your server.py imports third-party packages. If your script uses only the Python standard library, there is nothing to install.

8a.1 Create a location + put server.py on the Pi

sudo mkdir -p /opt/hachiko
sudo chown -R "$USER":"$USER" /opt/hachiko
cd /opt/hachiko
Option A: nano + copy/paste
nano /opt/hachiko/server.py
  • Paste the full server.py contents
  • Save: Ctrl + O → Enter
  • Exit: Ctrl + X
chmod +x /opt/hachiko/server.py
ls -la /opt/hachiko/server.py
head -n 3 /opt/hachiko/server.py
Option B: copy from another machine
scp ./server.py hachiko@hachiko.local:/opt/hachiko/server.py
chmod +x /opt/hachiko/server.py

8a.2 Create a virtualenv

cd /opt/hachiko
python3 -m venv .venv
source .venv/bin/activate
python -V
pip -V

8a.3 Run it once (quick test)

When you’re done testing, stop it with Ctrl + C and continue to 8a.4.

cd /opt/hachiko
source .venv/bin/activate
python server.py
Open it

From a browser on the same network: http://hachiko.local:8080 (or the Pi’s IP).

8a.4 Enable the status page at boot (required)

sudo nano /etc/systemd/system/hachiko-server.service
[Unit]
Description=Hachiko local status server
After=network-online.target monerod.service
Wants=network-online.target

[Service]
Type=simple
User=hachiko
WorkingDirectory=/opt/hachiko
Environment=PYTHONUNBUFFERED=1
ExecStart=/opt/hachiko/.venv/bin/python /opt/hachiko/server.py
Restart=always
RestartSec=3

NoNewPrivileges=true
PrivateTmp=true

[Install]
WantedBy=multi-user.target
sudo systemctl daemon-reload
sudo systemctl enable --now hachiko-server
systemctl status hachiko-server --no-pager -l
One-time check

Reboot once. After it comes back up, open http://hachiko.local:8080.

Step 8.3 — After sync (required)

Switch db-sync-mode back to safe
sudo nano /etc/monero/monerod.conf
# change:
# db-sync-mode=fast
# to:
# db-sync-mode=safe

sudo systemctl restart monerod
systemctl status monerod --no-pager

Step 9 — Validate network quality (Wi-Fi / Ethernet)

If you’re on Wi-Fi, validate link state and throughput to avoid sync stalls/timeouts.

9.1 Wi-Fi link (if applicable)

iw dev wlan0 link || true

9.2 Throughput test (Mac ↔ Pi)

On the Pi:

iperf3 -s

On the Mac (replace with your Pi’s IP):

iperf3 -c 192.168.1.123 -t 15

Step 10 — Create a wallet (required for mining payouts)

Back up the seed

The 25-word seed is the wallet. Write it down offline and store it safely. If you lose it, funds are not recoverable.

10.1 Create the wallets folder (SSD) + lock permissions

sudo mkdir -p /mnt/hachiko/wallets
sudo chown -R "$USER":"$USER" /mnt/hachiko/wallets
sudo chmod 700 /mnt/hachiko/wallets

10.2 Create a new wallet file (interactive)

cd /mnt/hachiko/wallets
monero-wallet-cli --generate-new-wallet hachiko-wallet

Follow the prompts. When asked about background mining, choose No (this build uses P2Pool + XMRig). When it shows the 25-word seed, write it down.

10.3 Get your wallet address (for P2Pool)

Important: once you launch monero-wallet-cli, you are in an interactive prompt. Do not paste shell commands there. To exit, type exit.

monero-wallet-cli --wallet-file /mnt/hachiko/wallets/hachiko-wallet --daemon-address 127.0.0.1:18081

Inside the wallet, run:

address
exit

Step 11 — Install + run P2Pool (required)

Important build rules

1) Clone with submodules. 2) Build as your normal user (not root). 3) P2Pool needs monerod RPC + ZMQ listening (18081/18083) or it won’t start stratum.

11.1 Confirm monerod RPC + ZMQ are listening

sudo ss -ltnp | egrep ':(18081|18083)\b' || echo "monerod not listening yet"

11.2 Clone with submodules (required)

sudo mkdir -p /opt/p2pool
sudo chown -R "$USER":"$USER" /opt/p2pool

cd /opt/p2pool
git clone --recurse-submodules https://github.com/SChernykh/p2pool.git .
git submodule update --init --recursive

11.3 Build (no sudo)

cd /opt/p2pool
rm -rf build
mkdir -p build
cd build

cmake ..
make -j"$(nproc)"
# Install the binary
sudo install -m 0755 /opt/p2pool/build/p2pool /usr/local/bin/p2pool
p2pool --version || true
If CMake fails with missing CURL/UV/ZMQ

Install these and run cmake .. again:

sudo apt update
sudo apt install -y libcurl4-openssl-dev libuv1-dev libzmq3-dev

11.4 Create runtime directories (required)

Keep this on the SSD.

sudo mkdir -p /mnt/hachiko/p2pool /mnt/hachiko/p2pool-api
sudo chown -R "$USER":"$USER" /mnt/hachiko/p2pool /mnt/hachiko/p2pool-api

11.5 Start P2Pool at boot (required)

sudo nano /etc/systemd/system/p2pool.service
[Unit]
Description=P2Pool (Monero decentralized pool)
After=network-online.target monerod.service
Wants=network-online.target

[Service]
Type=simple
User=hachiko
WorkingDirectory=/mnt/hachiko/p2pool

# Replace WALLET_ADDRESS with your wallet address from Step 10 (starts with 4...)
# --mini is recommended for small miners (Pi)
ExecStart=/usr/local/bin/p2pool \
  --host 127.0.0.1 --rpc-port 18081 --zmq-port 18083 \
  --wallet WALLET_ADDRESS \
  --data-api /mnt/hachiko/p2pool-api \
  --stratum 127.0.0.1:3333 \
  --mini

Restart=always
RestartSec=5

NoNewPrivileges=true
PrivateTmp=true

[Install]
WantedBy=multi-user.target
sudo systemctl daemon-reload
sudo systemctl enable --now p2pool
systemctl status p2pool --no-pager -l

11.6 Confirm stratum is listening

sudo ss -ltnp | egrep ':(3333)\b' || echo "p2pool stratum not listening yet"
sudo journalctl -u p2pool -n 120 --no-pager

Step 12 — Install + run XMRig (required)

What this does

XMRig connects to your local P2Pool stratum at 127.0.0.1:3333. P2Pool pays your wallet address from Step 10.

12.1 Build XMRig from source (recommended)

sudo mkdir -p /opt/xmrig
sudo chown -R "$USER":"$USER" /opt/xmrig

cd /opt/xmrig
git clone https://github.com/xmrig/xmrig.git src

cd /opt/xmrig/src
mkdir -p build
cd build
cmake ..
make -j"$(nproc)"

sudo install -m 0755 xmrig /usr/local/bin/xmrig
xmrig --version || true

12.2 Create XMRig config for local P2Pool

sudo mkdir -p /etc/xmrig
sudo nano /etc/xmrig/config.json
{
  "autosave": true,
  "donate-level": 0,
  "cpu": true,
  "opencl": false,
  "cuda": false,
  "pools": [
    {
      "url": "127.0.0.1:3333",
      "user": "x",
      "pass": "x",
      "keepalive": true,
      "tls": false
    }
  ]
}

12.3 Start XMRig at boot (required)

sudo nano /etc/systemd/system/xmrig.service
[Unit]
Description=XMRig (CPU miner) -> local P2Pool
After=network-online.target p2pool.service
Wants=network-online.target

[Service]
Type=simple
User=hachiko
ExecStart=/usr/local/bin/xmrig --config=/etc/xmrig/config.json
Restart=always
RestartSec=5

NoNewPrivileges=true
PrivateTmp=true

[Install]
WantedBy=multi-user.target
sudo systemctl daemon-reload
sudo systemctl enable --now xmrig
systemctl status xmrig --no-pager -l

12.4 Sanity checks

# XMRig should say it's connected to 127.0.0.1:3333
sudo journalctl -u xmrig -n 160 --no-pager

# P2Pool should show miners connected / shares
sudo journalctl -u p2pool -n 160 --no-pager

Step 13 — UFW (LAN-only firewall)

Goal

Allow SSH + status page from your LAN. Do not expose RPC (18081). Keep P2Pool stratum local-only (3333 bound to 127.0.0.1).

13.1 Check if UFW is active

sudo ufw status verbose

13.2 Find your LAN subnet (quick)

This prints the Pi’s IPv4 + CIDR. Most home LANs are 192.168.x.x/24 or 10.x.x.x/24.

ip -4 addr show | egrep 'inet ' | awk '{print $2}'

13.3 Enable UFW (recommended)

Safe path: add SSH allow first, then enable.

# Replace 192.168.0.0/16 if your LAN uses something else
LAN_CIDR="192.168.0.0/16"

# Allow SSH from LAN
sudo ufw allow from ${LAN_CIDR} to any port 22 proto tcp

# Allow status page from LAN (8080)
sudo ufw allow from ${LAN_CIDR} to any port 8080 proto tcp

# Defaults
sudo ufw default deny incoming
sudo ufw default allow outgoing

# Enable
sudo ufw enable

# Review
sudo ufw status numbered
Optional: help the Monero network (P2P)

If you want to accept inbound Monero P2P connections, allow TCP 18080 from anywhere. (RPC is still local-only, and should not be opened.)

sudo ufw allow 18080/tcp
sudo ufw status numbered

Step 14 — Final “plug and play” verification

14.1 Confirm services are enabled + running

systemctl is-enabled monerod p2pool xmrig hachiko-server 2>/dev/null || true
systemctl status monerod p2pool xmrig hachiko-server --no-pager -l

14.2 Confirm ports (local-only)

# monerod RPC + ZMQ should listen on 127.0.0.1
sudo ss -ltnp | egrep ':(18081|18083)\b' || true

# p2pool stratum should listen on 127.0.0.1:3333
sudo ss -ltnp | egrep ':(3333)\b' || true

14.3 Reboot test (the appliance test)

sudo reboot

After reboot, confirm: