A production-ready installer and management suite for running Pi-hole + Unbound on a Raspberry Pi.
DNS chain:
Client → Pi-hole (port 53) → Unbound (port 5335) → Internet
Pi-hole handles ad/tracker blocking; Unbound handles recursive DNS resolution with full DNSSEC validation — no third-party DNS resolver required.
Documentation last checked: 2026-10-28
Latest project update: 2026-10-28 — PR #37 fixes upstream detection under systemd hardening. The README refresh and stale-artifact cleanup are in PR #35; the dashboard and maintenance updates are in PR #32, PR #33, and PR #34.
- Integrated the read-only DNS dashboard with the authenticated maintenance panel.
- Hardened upgrades, bounded DNS-log parsing, kept the API key out of persistent browser storage, and enabled least-privilege log access.
- Restricted the test workflow token to read-only repository contents.
- Rechecked production DNS and DNSSEC behavior, both web interfaces, and authenticated API routes.
NetAlertX is optional. It was not configured on the production system last checked, so /devices reports HTTP 503 rather than inventing an empty device list. Pi-hole and Unbound monitoring and DNS are independent of this optional feature. See CHANGELOG.md for the change history.
git clone https://github.com/TimInTech/Pi-hole-Unbound-PiAlert-Setup.git
cd Pi-hole-Unbound-PiAlert-Setup
chmod +x install.sh
sudo ./install.shClone as a normal user (
pi), not root. The installer requiressudo ./install.sh.
For an existing suite with the maintenance interface, sudo ./install.sh --upgrade-only updates the suite and web files without changing DNS configuration or the Python environment. See docs/MAINTENANCE_WEB.md for prerequisites and rollback.
| Requirement | Details |
|---|---|
| Platform | Raspberry Pi 3/4/5, Debian Bookworm/Trixie (64-bit) |
| Pi-hole | v6.x (installed by this script) |
| Unbound | Installed and configured to port 5335 |
| Python | 3.10+ (for the optional Suite API and maintenance panel) |
| User | Normal user with sudo |
Install prerequisites manually (optional):
sudo apt-get update
sudo apt-get install -y unbound unbound-host unbound-anchor dns-root-data \
ca-certificates curl dnsutils iproute2 python3 python3-venv python3-pip \
git openssl sqlite3 jqInstall docker.io as well when you want the optional NetAlertX or containerized Pi-hole mode.
Without this, the stack is broken — DNSSEC is bypassed and you are using an external resolver.
Pi-hole must forward DNS queries to Unbound on 127.0.0.1#5335.
Verify via Pi-hole admin → Settings → DNS:
Or via the installer / Rescue Menu:
sudo pihole-rescue # Option 9: Pi-hole → Unbound standard fixThis repo ships three complementary management interfaces:
General-purpose interactive menu for everyday management.
bash ~/Pi-hole-Unbound-PiAlert-Setup/scripts/console_menu.sh
# or with forced text mode:
bash ~/Pi-hole-Unbound-PiAlert-Setup/scripts/console_menu.sh --text| Option | Action |
|---|---|
| 1 | Post-Install Check (Quick) |
| 2 | Post-Install Check (Full) — requires sudo |
| 3 | Show Service URLs |
| 4 | Manual Steps Guide |
| 5 | Maintenance Pro — requires sudo |
| 6 | View Logs |
| 7 | Rescue & Backup Menu |
| 8 | Exit |
Standalone recovery and diagnostic tool. Accessible globally:
sudo pihole-rescue| Option | Action |
|---|---|
| 1 | System status check (services, DNS, ports, temperature) |
| 2 | DNS loop / upstream check |
| 3 | Nightly / diagnostic test |
| 4 | Create backup (pihole.toml + Unbound config + systemd drop-ins) |
| 5 | Restore from backup |
| 6 | Delete old backups |
| 7 | Last-Known-Good restore |
| 8 | Emergency DNS bypass (Pi → 8.8.8.8/1.1.1.1, reversible) |
| 9 | Pi-hole → Unbound standard fix |
| 10 | Router / client DNS hint (FritzBox guide) |
| 11 | Show last report / log |
| 0 | Exit |
Batch maintenance script (apt updates, Pi-hole update, gravity update, security scan).
sudo bash tools/pihole_maintenance_pro.sh
# or with flags:
sudo bash tools/pihole_maintenance_pro.sh --no-apt --no-upgradeAvailable flags: --no-apt, --no-upgrade, --no-gravity, --restart-ftl, --backup, --json
Pi-hole-Unbound-PiAlert-Setup/
├── install.sh # Main host/container installer
├── start_suite.py # Optional authenticated API and DNS dashboard
├── maintenance_web.py # Browser maintenance panel and session security
├── maintenance_runner.py # Privileged maintenance job runner
├── maintenance_backups.py # Verified backup operations
├── maintenance_config.py # Caddy and LAN configuration validation
├── maintenance_install.py # Maintenance panel provisioning
├── requirements.txt # Python API and test dependencies
├── .env.example # Environment variable template
├── dashboard/ # Read-only DNS monitoring dashboard
├── web/ # Maintenance panel assets
├── deploy/ # systemd, Caddy, and sudo policy files
├── scripts/
│ ├── install_maintenance_web.sh # Staged/live maintenance installer
│ ├── rollback_maintenance_web.sh # Validated deployment rollback
│ ├── console_menu.sh # Interactive management menu
│ ├── rescue_menu.sh # Rescue and backup menu
│ └── lib/ui.sh # Shared shell UI helpers
├── tools/pihole_maintenance_pro.sh # Batch maintenance script
├── tests/ # Python, shell, browser, and upgrade tests
├── docs/ # Operations, design notes, reports, screenshots
├── CHANGELOG.md
└── README.de.md
# Quick check (no sudo)
bash scripts/post_install_check.sh --quick
# Full check (requires sudo)
sudo bash scripts/post_install_check.sh --fullWhen Pi-hole or Unbound is broken and you have no DNS:
sudo pihole-rescue # Option 8: Emergency DNS bypassThis sets the Pi itself to use 8.8.8.8/1.1.1.1 directly. Fully reversible — the menu stores your previous config and lets you restore it.
Restores your last known-working backup and verifies DNS is working:
sudo pihole-rescue # Option 7: Last-Known-Good restoresudo pihole-rescue # Option 4: Create backup
sudo pihole-rescue # Option 5: Restore from backupBackups are stored in /home/pi/pihole-rescue-backups/ and include:
/etc/pihole/pihole.toml/etc/unbound/unbound.conf.d/- Systemd drop-in files
start_suite.py is an optional FastAPI server providing a JSON API for monitoring.
It also hosts a modern, responsive, read-only web Dashboard for monitoring your DNS stack.
- Dashboard Access: On a configured host, open
https://<pihole-host>:8443/dashboard/through Caddy. For local development, usehttp://127.0.0.1:8090/dashboard/. - Maintenance Panel: The root path
https://<pihole-host>:8443/serves the separate maintenance panel when enabled. - Remote Access: The API binds to
127.0.0.1:8090. Do not expose that port directly; use the configured Caddy LAN endpoint or an SSH tunnel. - Authentication: Dashboard login and API endpoints require the
SUITE_API_KEY. - The dashboard holds the key in page memory only. Reloading the page requires signing in again.
# Setup
cp .env.example .env
nano .env # set SUITE_API_KEY
# Install deps
python3 -m venv .venv && source .venv/bin/activate
pip install -r requirements.txt
# Start
set -a
. ./.env
set +a
python3 start_suite.py
# The API stays on 127.0.0.1:8090; production API documentation is disabled.The DNS dashboard reports overall health, Pi-hole FTL status and version,
configured upstreams and the count of parsed queries from the latest 100 log
entries; Unbound status, port and DNS probe; optional NetAlertX reachability; and
Suite API uptime, version and detected host IP. Missing source data is shown as
unavailable, not as zero. The API provides /health, /version,
/urls, /pihole, /unbound, /netalertx, /stats, /dns, /leases, and
/devices. Maintenance endpoints are under /api/session and
/api/maintenance/. Device discovery returns HTTP 503 until NetAlertX
integration is configured.
The maintenance panel shows the latest job result, its checks and system snapshot. It can run a system check, create and verify backups (up to 10, with download and deletion), and run a controlled update. Restore is performed locally through the documented recovery procedure, not from the browser.
Not required for the core Pi-hole + Unbound stack.
The optional maintenance panel provides a system check, verified backups, controlled updates, the latest job result, system snapshot, and check steps. It remains disabled by default. On a fresh host installation, enable it with:
sudo ./install.sh --with-maintenance-webFor an existing installation that already has the maintenance panel, update it
with sudo ./install.sh --upgrade-only. Browser sign-in uses the existing
SUITE_API_KEY.
See docs/MAINTENANCE_WEB.md for prerequisites, certificate trust, security boundaries and rollback.
Network device monitoring (separate install):
sudo ./install.sh --with-netalertxWithout NetAlertX, device discovery remains unavailable; DNS monitoring and Pi-hole/Unbound operation continue normally.
The host Pi-hole installer uses the official https://install.pi-hole.net installer and does not pin a patch release. Container mode uses pihole/pihole:latest; NetAlertX uses jokobsk/netalertx:latest.
Run these checks manually during maintenance windows only:
pihole -v
sudo pihole -up
apt list --upgradable
docker pull jokobsk/netalertx:latestUse the Rescue Menu or create a backup before applying updates on a production Raspberry Pi.
sudo pihole-rescue # Option 2: DNS check, or Option 8: Emergency bypasssudo pihole-rescue # Option 9: Pi-hole → Unbound fixsystemctl status pihole-FTL unbound
dig +short @127.0.0.1 google.com
dig +short @127.0.0.1 -p 5335 google.comsudo bash scripts/post_install_check.sh --full
sudo bash scripts/nightly_test.sh- The Suite API (
start_suite.py) binds to 127.0.0.1 only by default - Always set a strong
SUITE_API_KEY - The Pi-hole admin interface is protected by Pi-hole's own authentication
- Unbound runs on port 5335 (not exposed externally unless you configure it)
- See SECURITY.md for vulnerability reporting
MIT License — © TimInTech


