One idempotent, logged, cron-friendly maintenance run for Pi-hole v6 β safe to test before you trust it.
Languages: π¬π§ English (this file) β’ π©πͺ Deutsch
pihole-maintenance-pro is a single Bash script that runs the recurring maintenance a Pi-hole v6
host needs β OS package updates, pihole -up, gravity/blocklist refresh, plus DNS and system
health checks β as one repeatable, timestamped, log-producing run instead of a pile of
hand-typed commands and half-remembered cron lines.
The problem it solves: manual Pi-hole upkeep is easy to forget, easy to get wrong under cron's
reduced PATH, and hard to audit after the fact. This script makes the run idempotent, logs
everything to /var/log, takes a backup before it touches Pi-hole, and lets you dry-test it
without root and without changing anything first.
- Pi-hole v6 users on Raspberry Pi OS (Bookworm / Trixie) (also works on comparable Debian-based hosts).
- Anyone running Pi-hole maintenance unattended via cron who wants logs, health checks, and a safe way to verify the run before scheduling it.
| Concern | A plain apt update && pihole -up && pihole -g |
This project |
|---|---|---|
| Test before trusting | none | non-destructive selftest, no root, no changes |
| Backups | none | auto backup before update/gravity |
| Health checks | none | port 53, dig, DNS local/external, FTL analytics |
| Skip specific steps | edit the command | granular --no-* flags |
| Output | raw stdout | timestamped log + summary + optional JSON |
| Cron reliability | breaks on reduced PATH |
sets a full PATH early |
| Interrupt handling | partial state | clean, once-only cleanup on Ctrl-C |
1. Try it safely first β no root, no system changes:
git clone https://github.com/TimInTech/pihole-maintenance-pro.git
cd pihole-maintenance-pro
RUN_SELFTEST=1 bash pihole_maintenance_pro.sh --no-apt --no-upgrade --no-gravity --no-dnsreload2. Install (script + healthcheck + weekly cron):
bash -c "$(curl -fsSL https://raw.githubusercontent.com/TimInTech/pihole-maintenance-pro/main/scripts/install.sh)"3. Run a real maintenance pass on your Pi-hole host:
sudo /usr/local/bin/pihole_maintenance_pro.sh- Non-destructive selftest.
RUN_SELFTEST=1skips the root check and, combined with the--no-*flags, performs no APT, upgrade, or gravity changes β ideal for CI and a first look. - Backup before change. A snapshot of
/etc/pihole(*.db,pihole.toml, host lists) is written to/etc/pihole/backup_<timestamp>beforepihole -upand beforepihole -g. Add--backupfor an extra rotated snapshot under/var/backups/pihole/<timestamp>(keeps the last 5). - Opt out per step.
--no-apt,--no-upgrade,--no-gravitylet you scope exactly what runs. - Predictable exit codes (see table below) make it safe to gate in scripts and cron.
- Clean interrupts.
SIGINT/SIGTERM(Ctrl-C) render the summary and clean up the temp directory exactly once. - Reversible install. A one-command uninstall removes the script, logs, temp data, and cron entry.
- APT
update/upgrade/autoremove/autoclean - Pi-hole update (
pihole -up), gravity refresh (pihole -g), optional FTL restart (--restart-ftl, only when needed on v6) - Health checks: port 53 listeners,
dig, local & external DNS resolution, GitHub reachability - Optional Tailscale info and FTL toplists via
sqlite3 - Performance dashboard and an end-of-run summary
- Automatic pre-change Pi-hole backup; optional rotated backup via
--backup - Machine-readable output via
--json - Installer provisions a weekly cron (
0 4 * * 0) idempotently - Timestamped logs in
/var/log/pihole_maintenance_pro_<timestamp>.log
- Pi-hole v6 with the
piholeCLI onPATH - Raspberry Pi OS (Bookworm / Trixie) or a comparable Debian-based system
- Bash 5+,
sudo/root for real runs - Optional:
sqlite3(FTL toplists), a configuredPIHOLE_API_URLfor the healthcheck tool
Compatibility note: this is designed for current Pi-hole v6 maintenance workflows. Verify on your own host with the non-destructive selftest before scheduling it in cron.
Installer (recommended) β installs the maintenance script and
tools/pihole_api_healthcheck.sh to /usr/local/bin and adds a weekly cron:
bash -c "$(curl -fsSL https://raw.githubusercontent.com/TimInTech/pihole-maintenance-pro/main/scripts/install.sh)"Manual installation:
cd ~
wget -O pihole_maintenance_pro.sh https://raw.githubusercontent.com/TimInTech/pihole-maintenance-pro/main/pihole_maintenance_pro.sh
chmod +x pihole_maintenance_pro.sh
sudo install -m 0755 pihole_maintenance_pro.sh /usr/local/bin/pihole_maintenance_pro.shRe-run the installer to pull and overwrite with the latest version (idempotent):
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/TimInTech/pihole-maintenance-pro/main/scripts/install.sh)"Clean removal of the script, logs, temp data, and the cron entry:
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/TimInTech/pihole-maintenance-pro/main/scripts/uninstall.sh)"Run this from a clone to validate behavior without root and without changing your system. It skips
the root check (RUN_SELFTEST=1) and disables every mutating step (--no-*):
RUN_SELFTEST=1 bash pihole_maintenance_pro.sh --no-apt --no-upgrade --no-gravity --no-dnsreloadThis is the same self-test CI runs. It still requires the pihole CLI to get past the environment
check: on a host without Pi-hole the script stops early with exit code 127 and makes no
changes (CI sets CI=1, which turns that case into a clean exit 0). For a pure syntax check on
any machine, use bash -n pihole_maintenance_pro.sh.
# Full interactive run
sudo /usr/local/bin/pihole_maintenance_pro.sh
# OS-only maintenance, leave Pi-hole untouched
sudo /usr/local/bin/pihole_maintenance_pro.sh --no-upgrade --no-gravity
# Pi-hole-only maintenance, skip APT
sudo /usr/local/bin/pihole_maintenance_pro.sh --no-apt
# Machine-readable output
sudo /usr/local/bin/pihole_maintenance_pro.sh --json| Flag | Effect |
|---|---|
--no-apt |
Skip APT update/upgrade/autoremove/autoclean |
--no-upgrade |
Skip pihole -up |
--no-gravity |
Skip pihole -g (blocklists / gravity) |
--no-dnsreload |
Deprecated on v6 (no-op; kept for compatibility) |
--restart-ftl |
Restart pihole-FTL at the end (v6: only if needed) |
--backup |
Extra rotated backup under /var/backups/pihole/ before Pi-hole ops |
--json |
Emit a machine-readable JSON block in place of the human-readable end-of-run summary |
-h, --help |
Show usage |
Every run writes a timestamped log to /var/log/pihole_maintenance_pro_<timestamp>.log.
Per-step live lines use a unified, timestamped format ([HH:MM:SS] OK|WARN|ERR β¦). --json
replaces the end-of-run summary with a machine-readable JSON block. Note that the progress/step
lines are still printed to stdout before it, so capture or extract the trailing JSON rather
than piping the whole run straight into jq.
Captured on a Raspberry Pi with Pi-hole Core 6.1.4, Web 6.2.1, FTL 6.2.3 β the live dashboard and summary rendered by the current release:
ββββββββββββββββ PERFORMANCE DASHBOARD ββββββββββββββββ
β π Load: 1.81 πΎ RAM: 23% π‘ Temp: 50Β°C π Disk: 9% β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
ββββββββ INTELLIGENTE ZUSAMMENFASSUNG ββββββββ
#00 π Network IP: 192.168.178.21 β OK
#03 π‘ Pi-hole v6.1.4 β OK
#07 π Health 4 listeners β OK
#08 π DNS Ext 172.217.16.78 β OK
#09 π DNS Local 127.0.0.1 β OK
#12 π FTL Query 24h: 141222 queries, 1% blocked β OK
#13 π₯ FTL Client 25 active clients β OK
| Code | Meaning |
|---|---|
0 |
Success (also --help, and CI without the pihole CLI) |
1 |
Not run as root without RUN_SELFTEST, or a critical step failed |
2 |
Unknown option |
127 |
pihole CLI not found (outside CI) |
130 |
Interrupted (SIGINT / Ctrl-C) |
143 |
Terminated (SIGTERM) |
The installer adds a weekly cron (0 4 * * 0) automatically. To schedule manually, set a full
PATH β Trixie's cron runs with a reduced one:
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
*/30 * * * * /usr/local/bin/pihole_api_healthcheck.sh >> /var/log/pihole_healthcheck.log 2>&1
30 3 * * * /usr/local/bin/pihole_maintenance_pro.sh >> /var/log/pihole_maintenance_pro.log 2>&1setupVars.confis gone; configuration lives in/etc/pihole/pihole.toml- The API is served at
/api(not/api.php) - Authentication uses session tokens:
POST /api/authreturnssid, passed via theX-FTL-SIDheader - The healthcheck tool (
tools/pihole_api_healthcheck.sh) can query endpoints whenPIHOLE_API_URLis set; setPIHOLE_PASSWORDto enable session login (see.env.example) - Unbound is optional and not required by this script
Missing sqlite3 (FTL toplists):
sudo apt update && sudo apt install -y sqlite3Test FTL database read access:
sudo sqlite3 -readonly /etc/pihole/pihole-FTL.db "SELECT COUNT(*) FROM queries;"Locale warnings:
echo -e "en_GB.UTF-8 UTF-8\nde_DE.UTF-8 UTF-8" | sudo tee /etc/locale.gen >/dev/null
sudo locale-gen && sudo update-locale LANG=de_DE.UTF-8 LC_ALL=de_DE.UTF-8- Cron didn't run reliably: ensure a full
PATHin the crontab (see Scheduling). - Pi 3B
linux-image-rpi-v8note: ignorable on ARMv7.
Contributions are welcome. See AGENTS.md for structure and conventions, and run the
local checks before opening a PR:
make check # bash -n + shellcheck + shfmtPlease follow Conventional Commits (feat:, fix(scope):, docs:, ci:, chore:).
MIT. See LICENSE.
If this project helps you, you can support it here: buymeacoffee.com/timintech