🌐 Languages: 🇬🇧 English (this file) • 🇩🇪 Deutsch
✅ Pi-hole Core 6.1.4 / FTL 6.1 / Web 6.2 – Built-in Pi-hole web server (no lighttpd)
✅ Target: Raspberry Pi 3/4 (64-bit) on Debian Bookworm/Trixie (incl. Raspberry Pi OS)
✅ One-Click Installation – Single command setup
✅ DNS Security – Pi-hole + Unbound with DNSSEC (optional)
✅ Network Monitoring – NetAlertX device tracking (optional; separate install)
✅ API Monitoring – Python FastAPI + SQLite (optional)
✅ Production Ready – Systemd hardening & auto-restart
✅ Idempotent – Safe to re-run anytime
Tested on Raspberry Pi 3/4 (64-bit) running Debian Bookworm/Trixie (including Raspberry Pi OS). Uses Pi-hole Core 6.1.4 / FTL 6.1 / Web 6.2 with the built-in web server—no lighttpd required.
git clone https://github.com/TimInTech/Pi-hole-Unbound-PiAlert-Setup.git
cd Pi-hole-Unbound-PiAlert-Setup
chmod +x install.sh
sudo ./install.sh- Supported: Debian/Ubuntu-family systems with
apt-getandsystemd. - Clone as a normal user (do not run
sudo git clone/ do not work from a root shell). - Run the installer via
sudo ./install.sh(running as root directly is rejected on purpose).
The installer writes:
- Logs:
/var/log/pihole-suite/install.logand/var/log/pihole-suite/install_errors.log - Suite env (API key):
/etc/pihole-suite/pihole-suite.env
If you want to install prerequisites manually:
sudo apt-get update
sudo apt-get install -y git curl jq dnsutils iproute2 openssl python3 python3-venv python3-pip ca-certificates
⚠️ Important — do NOT skip this. If Pi-hole does not use Unbound as its upstream, this stack is functionally broken (DNSSEC/DoT will be bypassed).
Pi-hole must forward DNS queries to Unbound running locally on port 5335:
Client → Pi-hole → Unbound → Internet
Required upstream value:
127.0.0.1#5335
- When you run
sudo ./install.sh(default), the installer configures Pi-hole v6 upstreams automatically in/etc/pihole/pihole.toml. - If you install Pi-hole manually (interactive installer), or you change DNS settings later, you must set the upstream to
127.0.0.1#5335yourself.
If Pi-hole asks you to Specify Upstream DNS Provider(s), choose Custom and enter:
127.0.0.1#5335
If you select Google/Cloudflare (or any public DNS):
- ❌ Unbound will NOT be used
- ❌ DNSSEC / DoT will be bypassed
- ❌ The setup is technically “installed” but logically wrong
sudo grep -A5 '^\[dns\]' /etc/pihole/pihole.tomlExpected:
[dns]
upstreams = ["127.0.0.1#5335"]Done! 🎉 Your complete DNS security stack is now running.
This repo ships a read-only verification tool to quickly confirm that Pi-hole, Unbound (and optionally NetAlertX) are up and configured correctly.
Note: The script output is English-only (messages are not localized). If you see German output, you're likely running a modified/older copy — check ./scripts/post_install_check.sh --version.
# Quick check
./scripts/post_install_check.sh --quick
# Full check (recommended with sudo)
sudo ./scripts/post_install_check.sh --full
# Show URLs only
./scripts/post_install_check.sh --urls
# View manual steps
./scripts/post_install_check.sh --steps | lessIf you see German output, you're not running the repo version (it is English-only). Check:
./scripts/post_install_check.sh --version
readlink -f ./scripts/post_install_check.shNetAlertX / Pi.Alert Next (Docker): This repo no longer installs NetAlertX in the one-click installer by default. NetAlertX's Docker setup can be configuration-sensitive (mounts/tmpfs/permissions).
- Install separately using the upstream docs: https://github.com/jokob-sk/NetAlertX/blob/main/docs/DOCKER_COMPOSE.md
- If you still want the installer to attempt it: run
sudo ./install.sh --install-netalertx(best-effort).
sudo docker inspect -f '{{.HostConfig.NetworkMode}}' netalertx
# expected: hostWeb UI: http://[your-ip]:20211
Python API (pihole-suite, optional): A local FastAPI service bound to 127.0.0.1:8090 with API-key auth (X-API-Key). It exposes read-only endpoints like /health, /dns, /leases, /stats. Some endpoints may return empty data depending on logs/permissions.
--help output:
Usage: post_install_check.sh [OPTIONS]
Post-installation verification script for Pi-hole + Unbound setup (optionally with NetAlertX).
Performs read-only checks to verify service health and configuration.
OPTIONS:
--version Show script version
--quick Run quick check (summary only)
--full Run full check (all sections)
--urls Show service URLs only
--steps Show manual step-by-step verification guide
-h, --help Show this help message
INTERACTIVE MODE:
Run without arguments to enter interactive menu mode.
EXAMPLES:
post_install_check.sh --version # Show version
post_install_check.sh --quick # Quick status check
post_install_check.sh --full # Comprehensive check
post_install_check.sh --urls # Display service URLs
post_install_check.sh --steps | less # View manual verification steps
post_install_check.sh # Interactive menu
NOTES:
- This script performs read-only checks only
- Some checks may require sudo privileges
- Running with sudo is recommended for complete checks
- Pi-hole v6 uses /etc/pihole/pihole.toml as authoritative config
Interactive mode:
[1] Quick Check (summary only)
[2] Full Check (all sections)
[3] Show Service URLs
[4] Service Status
[5] Network Info
[6] Exit
Shortened real-world example from a Raspberry Pi run (sudo ./scripts/post_install_check.sh --full). Exact values vary by system.
────────────────────────────────────────────────────────────────
POST-INSTALL CHECK — Pi-hole v6 / Unbound / Docker / Pi.Alert
Script: post_install_check.sh v1.0.0 (output language: English)
────────────────────────────────────────────────────────────────
Time 2026-01-01T14:38:40+00:00
Host raspberrypi
OS Debian GNU/Linux 13 (trixie)
Kernel 6.12.47+rpt-rpi-v8
Default IF / GW eth0 / 192.168.178.1
IPv4 192.168.178.52,172.17.0.1
IPv6 none
URLs (best guess)
• Pi-hole Admin: http://192.168.178.52/admin
• Pi.Alert/NetAlertX: (port 20211/8081 not detected — check if service/container is running)
Unbound
Service unbound.service ✔ running
Listener 127.0.0.1:5335 ✔ TCP/UDP bound
dig @127.0.0.1#5335 cloudflare.com ✔ 104.16.133.229
Pi-hole v6
Service pihole-FTL ✔ running
DNS Listener :53 ✔ at least one listener active
pihole.toml Upstream ⚠
dig @127.0.0.1 example.org ✔ Pi-hole answered DNS
Docker
docker ✔ docker reachable
Running containers:
• netalertx (Image: jokobsk/netalertx:latest) Ports:
Pi.Alert Next / NetAlertX
Service (pialert/netalertx) ⚠ no systemd service found
Docker container (pialert/netalertx) ✔ container running
Summary
⚠ Basically OK, but there are warnings (check upstream/services).
Optional hard proof (if tcpdump installed):
sudo tcpdump -i lo port 5335 -n # parallel: dig example.org @127.0.0.1
NetAlertX is opt-in. Use
--install-netalertxif you want the installer to attempt it (best-effort). For a slim install, use--skip-python-apior--minimal.
| Component | Purpose | Access | Notes |
|---|---|---|---|
| 🕳️ Pi-hole | DNS ad-blocker & web UI | http://[your-ip]/admin |
Core 6.1.4 / FTL 6.1 / Web 6.2 (built-in web server) |
| 🔐 Unbound | Recursive DNS + DNSSEC | 127.0.0.1:5335 |
Optional (replace with your own upstream resolver) |
| 📡 NetAlertX | Network device monitoring | http://[your-ip]:20211 |
Optional (separate install; or --install-netalertx) |
| 🐍 Python API | Monitoring & stats API | http://127.0.0.1:8090 |
Optional (--skip-python-api or --minimal) |
NetAlertX (optional)
NetAlertX is installed separately. Use upstream Docker Compose docs: https://github.com/jokob-sk/NetAlertX/blob/main/docs/DOCKER_COMPOSE.md
If you still want the installer to attempt it: sudo ./install.sh --install-netalertx (best-effort).
┌─────────────┐ ┌──────────────┐ ┌─────────────┐
│ Clients │───▶│ Pi-hole │───▶│ Unbound │
│ 192.168.x.x │ │ :53 │ │ :5335 │
└─────────────┘ └──────┬───────┘ └─────────────┘
│ │
▼ ▼
┌─────────────┐ ┌─────────────┐
│ NetAlertX │ │ Root Servers│
│ :20211 │ │ + Quad9 │
└─────────────┘ └─────────────┘
│
▼
┌─────────────┐
│ Python API │
│ :8090 │
└─────────────┘
Data Flow:
- Clients → Pi-hole (DNS filtering)
- Pi-hole → Unbound (recursive resolution)
- Unbound → Root servers (DNSSEC validation)
- NetAlertX → Network monitoring
- Python API → Aggregated monitoring data
The installer generates an API key in /etc/pihole-suite/pihole-suite.env (SUITE_API_KEY). You can inspect it with sudo cat /etc/pihole-suite/pihole-suite.env.
# Load API key from the installer env file
SUITE_API_KEY="$(sudo awk -F= '/^SUITE_API_KEY=/{print $2}' /etc/pihole-suite/pihole-suite.env)"
# Ensure the service is running
sudo systemctl restart pihole-suite
sudo systemctl --no-pager --full status pihole-suite
# Call health endpoint
curl -s -H "X-API-Key: $SUITE_API_KEY" http://127.0.0.1:8090/healthReturns API version + uptime.
Returns best-guess URLs for Pi-hole / NetAlertX and the local Suite bind.
Returns Pi-hole version/FTL status and configured v6 upstreams (from pihole.toml).
Checks Unbound service + a quick dig against 127.0.0.1:${UNBOUND_PORT}.
Checks whether NetAlertX responds on http://127.0.0.1:20211 (host mode).
{
"ok": true,
"message": "Pi-hole Suite API is running",
"version": "1.0.0"
}[
{
"ip": "192.168.1.101",
"mac": "aa:bb:cc:dd:ee:ff",
"hostname": "printer",
"lease_start": null,
"lease_end": "2026-01-01T14:38:40+00:00"
}
]Note: lease_start may be null (not available in all lease sources).
[
{
"timestamp": "Dec 21 10:30:45",
"client": "192.168.1.100",
"query": "example.com",
"action": "query"
}
][]Note: Device data depends on NetAlertX/Pi.Alert APIs/DB and is not populated in this minimal Suite API yet.
{
"total_dns_logs": 89,
"total_devices": 0,
"recent_queries": 89,
"note": "DNS stats are derived from best-effort log parsing; may be empty depending on Pi-hole logging/permissions."
}- Open
http://[your-ip]/admin - Go to Settings → DNS
- Verify Custom upstream:
127.0.0.1#5335 - Configure devices to use Pi-hole as DNS server
NetAlertX is installed separately. Follow upstream docs: https://github.com/jokob-sk/NetAlertX/blob/main/docs/DOCKER_COMPOSE.md
If you want the installer to attempt it: sudo ./install.sh --install-netalertx (best-effort).
For automated checks, use ./scripts/post_install_check.sh (see Post-Install Verification (post_install_check.sh) earlier in this README for commands and options).
What it checks:
✅ System information (OS, network, routes)
✅ Unbound service status and DNS resolution
✅ Pi-hole FTL service and port 53 listener
✅ Pi-hole v6 upstream configuration in /etc/pihole/pihole.toml
✅ Docker container checks (only if NetAlertX installed)
✅ Network configuration and DNS settings
Example output:
=== Pi-hole v6 Configuration ===
[PASS] Pi-hole v6 config file exists: /etc/pihole/pihole.toml
[PASS] Pi-hole v6 upstreams configured: upstreams = ["127.0.0.1#5335"]
┌─────────────────────────────────────────────────────────────────┐
│ Check Summary │
├─────────────────────────────────────────────────────────────────┤
│ PASS: 12 │
│ WARN: 1 │
│ FAIL: 0 │
└─────────────────────────────────────────────────────────────────┘
Status meanings:
- [PASS] - Component is working correctly
- [WARN] - Component may need attention but system is functional
- [FAIL] - Critical issue detected, requires action
Note: Running with
sudois recommended for complete checks. The script performs read-only operations and does not modify any configuration.
Pi-hole v6 uses /etc/pihole/pihole.toml as the authoritative configuration file for all settings, including DNS upstreams. The installer automatically configures:
[dns]
upstreams = ["127.0.0.1#5335"]This ensures Pi-hole v6 always uses Unbound as its DNS upstream. The legacy setupVars.conf is maintained for backward compatibility but is not the primary configuration source in v6.
To verify your Pi-hole v6 upstream configuration:
# Check the authoritative config
sudo grep -A2 '^\[dns\]' /etc/pihole/pihole.toml
# Or use the post-install check script
sudo ./scripts/post_install_check.sh --fullAccess all verification and maintenance tools through an interactive menu:
# Start the console menu
./scripts/console_menu.sh
# Or create an alias for convenience
echo "alias pihole-suite='bash ~/Pi-hole-Unbound-PiAlert-Setup/scripts/console_menu.sh'" >> ~/.bash_aliases
source ~/.bash_aliases
pihole-suite- Quick and full system checks
- Service URL display
- Manual verification steps guide
- Maintenance Pro access (with confirmations)
- Log viewing
- Dialog-based UI (if installed) or text fallback
See docs/CONSOLE_MENU.md for detailed usage.
dig @127.0.0.1 -p 5335 example.com # Test Unbound
pihole status # Test Pi-hole
docker logs netalertx # Test NetAlertX
curl -H "X-API-Key: $SUITE_API_KEY" http://127.0.0.1:8090/health # Test APIsystemctl status pihole-suite unbound pihole-FTL
journalctl -u pihole-suite -f
journalctl -u unbound -f
docker ps| Issue | Solution |
|---|---|
| Port 53 in use (systemd-resolved) | sudo systemctl disable --now systemd-resolved; re-run sudo ./install.sh. Check with `sudo ss -tulpen |
| FTL DB/UI corruption after upgrade | Check logs with sudo journalctl -u pihole-FTL -n 50, then restart: sudo systemctl restart pihole-FTL. |
| DNS outages / upstream failing | Verify Unbound with dig @127.0.0.1 -p 5335 example.com; check config with ./scripts/post_install_check.sh --full; reapply with ./install.sh --force. |
| Missing API key | Check /etc/pihole-suite/pihole-suite.env or re-run the installer to regenerate (SUITE_API_KEY). |
- Auto-generated API keys (32-byte hex)
- CORS restricted to localhost
- Authentication required for all endpoints
- NoNewPrivileges prevents escalation
- ProtectSystem=strict read-only protection
- PrivateTmp isolated temp dirs
- Memory limits to prevent exhaustion
- Unbound bound to localhost only
- DNS over TLS to upstream resolvers
- DNSSEC validation enabled
- Fork the repository
- Create a feature branch:
git checkout -b feature/amazing-feature - Commit changes:
git commit -m 'feat: add amazing feature' - Run tests:
ruff check . && pytest - Push and create a Pull Request
This project is licensed under the MIT License - see LICENSE.
See CHANGELOG.md for history and updates.


