PiAxe – systemd Service pyminer Not Starting on Boot
Warning — Should be addressed soon
Symptoms
- Pool dashboard hashrate stays at 0 GH/s after a Pi reboot that previously hashed cleanly
- `sudo systemctl status piaxe` reports `Active: failed (Result: exit-code)` or `Active: activating (auto-restart)` with `code=exited, status=1`
- `journalctl -u piaxe -n 100` shows `python3: can't open file '/home/pi/piaxe-miner/pyminer.py': [Errno 2] No such file or directory`
- `journalctl -u piaxe -n 100` shows `ModuleNotFoundError: No module named 'pyserial'` (or `requests`, `pyyaml`, `bitstring`, `lgpio`) despite `pip3 list` confirming installed
- `journalctl -u piaxe -n 100` shows `PermissionError: [Errno 13] Permission denied: '/dev/serial0'` even though the script works in an interactive shell as the same user
- `journalctl -u piaxe -n 100` shows `RuntimeError: No access to /dev/gpiomem; try running as root` or `Cannot determine SOC peripheral base address`
- `journalctl -u piaxe -n 100` shows `python3: command not found` or `/bin/sh: 1: python3: not found`
- `systemctl is-enabled piaxe` returns `disabled` even though the service was previously enabled
- Service launches, exits with code 0 within 2 seconds, then re-launches forever - classic Type=simple race when pyminer wrongly daemonizes
- Hand-crafted unit file in `/etc/systemd/system/` is missing the `[Install] WantedBy=multi-user.target` block
- After a kernel update, the USB-TTL serial path the unit hardcoded (`/dev/ttyUSB0`) shifted to `/dev/ttyUSB1` and the unit cannot find its serial device
- Service works on `systemctl start` after the Pi has fully booted, but fails on cold reboot - boot-order race against udev / network-online.target
Step-by-Step Fix
Read the actual error before changing anything. Run `sudo journalctl -u piaxe -n 200 --no-pager` (substitute `pyminer` if your unit has that name). Scroll back to the FIRST error line, not the most recent. The first error is the cause; everything below it is downstream noise from the auto-restart loop. Categorize: ModuleNotFoundError, FileNotFoundError, PermissionError, RuntimeError, command not found, daemonized too quickly, or unrecognized. Write the category down before touching anything.
Confirm the unit is actually enabled. Run `systemctl is-enabled piaxe`. If it returns `disabled` or `static`, re-enable: `sudo systemctl enable piaxe.service`. This creates the symlink under `/etc/systemd/system/multi-user.target.wants/`. Reboot and verify with `systemctl status piaxe`. If the unit was simply not enabled, this is the entire fix.
Print the unit file: `systemctl cat piaxe`. Verify these directives are present and correct: `WorkingDirectory=`, `ExecStart=`, `User=`, `Group=`, `Restart=`, `Type=`, and the `[Install] WantedBy=multi-user.target` line. Note any path that does not actually exist on disk and any user that does not exist in `/etc/passwd`. These are your fix targets for Tier 2.
Reproduce manually under the same user as the unit. Run `sudo -u <unit-User> -H bash -c 'cd <unit-WorkingDirectory> && <unit-ExecStart>'`, substituting the literal values from the unit file. This runs pyminer the same way systemd would, only with stdout/stderr on your terminal. If it works manually, the problem is systemd environment isolation. If it fails manually, the problem is the script or its dependencies.
Restart the service after any change: `sudo systemctl daemon-reload && sudo systemctl restart piaxe`. Then watch live: `sudo journalctl -u piaxe -f`. Within 10-20 seconds you should see `BM1366 init OK`, then `stratum connecting`, then `got new work`. If it fails again, the journal will show a different error than before - the fix moved the failure mode but did not resolve it. Repeat the diagnosis cycle.
Fix WorkingDirectory and ExecStart paths. Edit the unit with `sudo nano /etc/systemd/system/piaxe.service`. Set `WorkingDirectory=/home/pi/piaxe-miner` (or wherever your config.yaml lives). Set `ExecStart=/home/pi/piaxe-venv/bin/python /home/pi/piaxe-miner/pyminer.py` to use the virtualenv's Python explicitly - never rely on `/usr/bin/python3` finding modules installed into a venv. Save, `daemon-reload`, restart.
Build a virtualenv if you do not already have one. On Bookworm, `pip install -r requirements.txt` outside a venv hits PEP 668 (`error: externally-managed-environment`). Run: `python3 -m venv ~/piaxe-venv && source ~/piaxe-venv/bin/activate && pip install --upgrade pip && pip install -r ~/piaxe-miner/requirements.txt`. Then update the unit's `ExecStart=` to use `~/piaxe-venv/bin/python`. Never use `--break-system-packages` - it contaminates system Python and bites you six months later.
Add SupplementaryGroups for hardware access. In the `[Service]` section: `User=pi`, `Group=pi`, `SupplementaryGroups=dialout gpio`. The `dialout` group is required to open `/dev/serial0` (UART); the `gpio` group is required for `/dev/gpiomem` access used by RPi.GPIO / lgpio. If you created a custom user (e.g. `miner`), also run `sudo usermod -aG dialout,gpio miner` so the user is actually a member of those groups, not just listed in the unit.
Set environment variables explicitly. systemd does NOT inherit your shell PATH or HOME. Add: `Environment="PATH=/home/pi/piaxe-venv/bin:/usr/local/bin:/usr/bin:/bin"`, `Environment="HOME=/home/pi"`, `Environment="PYTHONUNBUFFERED=1"`. The PYTHONUNBUFFERED flag makes pyminer's output appear in journalctl in real time instead of after Python's stdout buffer flushes - critical for live debugging.
Fix boot-order races. In the `[Unit]` section: `After=network-online.target systemd-udev-settle.service` and `Wants=network-online.target`. This makes systemd wait for udev to finish creating `/dev/serial0` and the network to be reachable before launching pyminer. Add `Restart=on-failure` and `RestartSec=10s` so a transient race recovers without spinning up a 30-rapid-restart fountain. Also add `StartLimitIntervalSec=300` and `StartLimitBurst=10` to give up cleanly after 10 failures in 5 minutes instead of thrashing the SD card forever.
Stabilize USB-TTL serial paths if your PiAxe carrier uses USB instead of GPIO UART. USB enumeration order is unstable across reboots and kernel updates - `/dev/ttyUSB0` can become `/dev/ttyUSB1`. Write a udev rule: create `/etc/udev/rules.d/99-piaxe.rules` containing `SUBSYSTEM=="tty", ATTRS{idVendor}=="10c4", ATTRS{idProduct}=="ea60", SYMLINK+="piaxe-uart"` (substitute your adapter's vendor/product IDs from `lsusb`). Reload udev: `sudo udevadm control --reload-rules && sudo udevadm trigger`. Update the unit to reference `/dev/piaxe-uart`.
Configure structured logging to journald. In `[Service]`: `StandardOutput=journal`, `StandardError=journal`, `SyslogIdentifier=piaxe`. This routes all pyminer output into journalctl with proper rotation. Do NOT log to a flat file in `~/piaxe-miner/log.txt` - flat-file logs eventually fill the SD card and brick the Pi for unrelated reasons. Query with `journalctl -t piaxe -n 100` once SyslogIdentifier is set.
Reboot test. Every unit-file change demands a real reboot to verify boot-time behavior. `sudo reboot`, wait 90 seconds, SSH back in, `systemctl status piaxe`. Should show `Active: active (running)` within 30-60 seconds of boot completion. If the service comes up clean on `systemctl restart` but fails after `reboot`, you still have a boot-order race - tighten `After=` ordering or add a longer `RestartSec=`.
Build a hardened multi-PiAxe fleet config if you run more than one PiAxe per Pi or operate a fleet. Use systemd template units: `/etc/systemd/system/piaxe@.service` with `WorkingDirectory=/home/pi/piaxe-miner-%i` and `ExecStart=/home/pi/piaxe-venv/bin/python pyminer.py --config config-%i.yaml`. Enable per-instance: `sudo systemctl enable piaxe@1 piaxe@2`. Pair with the udev rules above for stable USB paths. Treat the unit file as version-controlled config - commit to a private git repo alongside config.yaml.
Add a watchdog with sd_notify if you want belt-and-suspenders reliability. Modify pyminer.py (or run a wrapper) to call `systemd.daemon.notify('WATCHDOG=1')` every 30 seconds while hashing. In the unit: `WatchdogSec=120s` and `NotifyAccess=main`. systemd will kill and restart pyminer if it stops sending the heartbeat - catches hung-but-not-crashed states (frozen mainloop, deadlocked thread) that `Restart=on-failure` does not catch because the process is technically still alive.
Capture a logic-analyzer trace if Tier 1-3 confirms the service launches cleanly but no hashrate appears. Connect a USB logic analyzer (Saleae or clone) to GPIO14 (TX) and GPIO15 (RX), sample at 1 MHz, decode as serial 115200 8N1. Confirm Pi sends `55 aa` preamble frames and the BM1366 responds with `aa 55` preamble frames within ~1 ms. Silent RX with valid TX = chip dead, escalate to D-Central. Garbled RX = baud drift / noise, fix at unit level (`Environment` clock pin, mini-UART → PL011 swap, supply rail check).
Escalate to D-Central's open-source / pleb-mining queue when the unit file is provably correct and the chip is the suspect. Email support with: Pi model, Raspberry Pi OS version (`cat /etc/os-release`), full unit file (`systemctl cat piaxe`), last 200 lines of journal (`journalctl -u piaxe -n 200`), logic-trace screenshot if captured, and timeline of events (last working date, what changed). Half an hour of preparation saves billable bench time.
Ship the PiAxe to D-Central if chip-level rework is needed. Pack in an anti-static bag, double-box with at least 3 cm of foam every side. Include a printed note with: Pi model, OS version, kernel revision, the full unit file, the last 200 lines of journalctl, any electrical events you suspect, and your contact info. Ship to the address on D-Central's ASIC Repair Service page. Turnaround 3-7 business days Canada-wide, 5-10 days US / international. Ask for the pleb-mining queue.
When to Seek Professional Repair
If the steps above do not resolve the issue, or if you are not comfortable performing these repairs yourself, professional service is recommended. Attempting advanced repairs without proper equipment can cause further damage.
Related Error Codes
Still Having Issues?
Our team of Bitcoin Mining Hackers has been repairing ASIC miners since 2016. We have seen it all and fixed it all. Get a professional diagnosis.
