A comprehensive web-based dashboard for monitoring and managing multiple Pi-hole v6, AdGuard Home, and Technitium DNS servers with real-time alerts, configuration sync, and multi-channel notifications.
- Screenshots
- Features
- Installation
- Configuration
- Architecture
- Docker Images
- API
- Development
- Troubleshooting
- Performance
- License
- Contributing
- Support
| Dashboard | Statistics |
|---|---|
![]() |
![]() |
- Pi-hole v6 - Full REST API integration
- AdGuard Home - Native API support
- Technitium DNS - Full API integration with backup/restore sync
- Monitor unlimited servers from a single dashboard
- Per-server statistics and health monitoring
- Automatic query ingestion with configurable polling intervals
- Advanced search across domain, client IP, and hostname
- Date range filtering with pagination
- Query status tracking (allowed, blocked, cached)
- Query counts (today, week, month, all-time)
- Block rate percentages
- Hourly/daily query trends with charts
- Top domains and top blocked domains
- Top clients with hostname resolution
- Per-server breakdown with blocked/cached counts
- New client detection
- Pattern-based rules with wildcard support:
- Domain patterns (e.g.,
*ads*,*.gambling.*) - Client IP patterns (e.g.,
192.168.1.*) - Client hostname patterns (e.g.,
*kids-tablet*)
- Domain patterns (e.g.,
- Exclusion patterns to reduce false positives
- Configurable cooldown periods to prevent alert spam
- Batched notifications for multiple matches
- Telegram - Bot integration with chat ID support
- Pushover - Priority and sound customization
- Ntfy - Self-hosted or ntfy.sh
- Discord - Webhook integration
- Webhook - Generic HTTP webhooks (POST/PUT/GET)
- Custom message templates with variables
- Per-channel enable/disable and error tracking
Designate a "source" server and sync its configuration to target servers automatically or on-demand.
Pi-hole syncs:
- Gravity database (via Teleporter):
- Adlists (blocklists/subscriptions)
- Whitelist and blacklist domains
- Regex whitelist and blacklist
- Groups and group assignments
- Client definitions and group memberships
- DNS settings:
- Local DNS records (A/AAAA)
- CNAME records
- Upstream DNS servers
- Conditional forwarding rules
AdGuard Home syncs:
- User filtering rules
- DNS configuration and upstream servers
- DNS rewrites
- Blocklist and allowlist subscriptions
- Persistent client settings
Technitium DNS syncs (via backup/restore):
- Block list URLs (subscriptions)
- DNS settings and configuration
- Log settings
- Allowed zones (whitelist)
- Blocked zones (blacklist)
- Authoritative DNS zones
Includes sync history with detailed logs and error tracking.
- Enable/disable blocking per server or globally
- Timed disable with automatic re-enable
- Real-time blocking status display
- View and manage whitelist/blacklist across all servers
- Quick whitelist/blacklist actions from query results
- Regex list support (Pi-hole)
- Create API keys for scripting and automation
- Read-only or admin-level key permissions
- Optional expiration dates
- All REST endpoints accessible via
Authorization: Bearer <key>
- Built-in authentication with session management
- Admin and regular user roles
- API key authentication for programmatic access
- OIDC/SSO integration (Google, Authentik, Keycloak, etc.)
- Rate-limited login attempts
- Secure password hashing (bcrypt)
- Docker and Docker Compose
- Pi-hole v6, AdGuard Home, and/or Technitium DNS server(s)
- Create a directory and
docker-compose.yml:
mkdir dnsmon && cd dnsmon# docker-compose.yml
services:
postgres:
image: postgres:16
container_name: dnsmon-postgres
environment:
POSTGRES_DB: dnsmon
POSTGRES_USER: dnsmon
POSTGRES_PASSWORD: changeme # Change this!
volumes:
- dnsmon_postgres_data:/var/lib/postgresql/data
# Uncomment to tune Postgres memory usage (adjust to your system):
# command:
# - "postgres"
# - "-c"
# - "shared_buffers=256MB"
# - "-c"
# - "work_mem=64MB"
# - "-c"
# - "effective_cache_size=1GB"
healthcheck:
test: ["CMD-SHELL", "pg_isready -U dnsmon"]
interval: 10s
timeout: 5s
retries: 5
restart: unless-stopped
app:
image: ghcr.io/darthnorse/dnsmon:latest
container_name: dnsmon-app
depends_on:
postgres:
condition: service_healthy
environment:
DATABASE_URL: postgresql://dnsmon:changeme@postgres:5432/dnsmon # Match password above
TZ: America/Denver # Your timezone
ports:
- "8000:8000"
restart: unless-stopped
volumes:
dnsmon_postgres_data:- Start the services:
docker compose up -d-
Open
http://localhost:8000and create your admin account -
Go to Settings → DNS Servers to add your Pi-hole, AdGuard Home, or Technitium DNS servers
That's it! DNSMon will start monitoring your DNS servers immediately.
All configuration is done through the web UI - no config files needed!
| Variable | Description | Default |
|---|---|---|
POSTGRES_PASSWORD |
PostgreSQL password | changeme |
TZ |
Timezone for display | UTC |
DNSMON_SECRET_KEY |
Session signing key (recommended for production) | Auto-generated |
DNSMON_COOKIE_SECURE |
Set to true if behind HTTPS |
false |
DNSMon works behind reverse proxies (Nginx, Caddy, Traefik, etc.) with no extra configuration. Proxy headers (X-Forwarded-Proto, X-Forwarded-For) are trusted automatically so that OIDC redirects and client IP detection use the correct external URL.
If your proxy terminates TLS, set the cookie secure flag:
environment:
DNSMON_COOKIE_SECURE: "true"Ensure your reverse proxy forwards the standard headers. Example for Nginx:
location / {
proxy_pass http://127.0.0.1:8000;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}- Go to Settings → DNS Servers
- Click Add Server
- Enter:
- Name: Friendly name (e.g., "Primary Pi-hole")
- Type: Pi-hole, AdGuard Home, or Technitium DNS
- URL: Server URL (e.g.,
http://192.168.1.100) - Password/API Key: Your server's password or API token
- Click Test Connection to verify
- Save and the server will start being monitored
- Go to Settings → Notifications
- Click Add Channel
- Choose your notification type and configure:
- Create a bot via @BotFather
- Get your chat ID by messaging @userinfobot
- Enter bot token and chat ID
- Create an application at pushover.net
- Enter your app token and user key
- Create a webhook in your Discord channel settings
- Paste the webhook URL
- Choose a topic name
- Optionally specify a self-hosted server URL
- Go to Alert Rules
- Click Create Rule
- Configure patterns:
- Domain Pattern:
*ads*,*tracker*,*malware*(comma-separated) - Client IP Pattern:
192.168.1.100or192.168.1.* - Exclude Domains:
safe-site.com,another-safe.com
- Domain Pattern:
- Set cooldown period (minutes between alerts)
- Save - alerts will be sent to all enabled notification channels
┌─────────────────┐
│ Browser │
└────────┬────────┘
│
▼
┌─────────────────┐ ┌─────────────────┐
│ DNSMon App │◄────►│ PostgreSQL │
│ (FastAPI + │ │ (Data Store) │
│ React SPA) │ └─────────────────┘
└────────┬────────┘
│
▼
┌───────────────────────────────────────────────┐
│ DNS Ad-Blockers │
│ ┌─────────┐ ┌─────────┐ ┌────────────┐ │
│ │ Pi-hole │ │ AdGuard │ │ Technitium │ │
│ │ v6 │ │ Home │ │ DNS │ │
│ └─────────┘ └─────────┘ └────────────┘ │
└───────────────────────────────────────────────┘
- Backend: Python FastAPI with async SQLAlchemy
- Frontend: React 18 with TypeScript and Tailwind CSS
- Database: PostgreSQL 16 with timezone-aware timestamps
- Scheduler: APScheduler for background tasks
Pre-built multi-architecture images are available on GitHub Container Registry:
docker pull ghcr.io/darthnorse/dnsmon:latestSupported architectures:
linux/amd64(x86 64-bit)linux/arm64(ARM 64-bit, e.g., Raspberry Pi 4+)
Full interactive API documentation is available at /docs (Swagger UI) or /redoc (ReDoc) on your DNSMon instance.
All endpoints support API key authentication via the Authorization: Bearer <key> header. Create keys in Settings → API Keys.
# Get dashboard statistics
curl -H "Authorization: Bearer YOUR_API_KEY" http://localhost:8000/api/stats
# Search queries
curl -H "Authorization: Bearer YOUR_API_KEY" "http://localhost:8000/api/queries?domain=example.com&limit=10"
# Get blocking status
curl -H "Authorization: Bearer YOUR_API_KEY" http://localhost:8000/api/blocking/status
# Disable blocking on all servers for 5 minutes
curl -X POST -H "Authorization: Bearer YOUR_API_KEY" -H "Content-Type: application/json" \
-d '{"enabled": false, "duration": 300}' http://localhost:8000/api/blocking/allPOST /api/auth/login- LoginPOST /api/auth/logout- LogoutGET /api/auth/check- Check auth status
GET /api/queries- Search queries with filtersGET /api/queries/count- Count matching queries
GET /api/stats- Dashboard statisticsGET /api/statistics- Detailed statistics with time series
GET /api/alert-rules- List rulesPOST /api/alert-rules- Create rulePUT /api/alert-rules/{id}- Update ruleDELETE /api/alert-rules/{id}- Delete rule
GET /api/notification-channels- List channelsPOST /api/notification-channels- Create channelPOST /api/notification-channels/{id}/test- Send test notification
GET /api/settings- Get all settingsPOST /api/settings/pihole-servers- Add DNS serverPOST /api/settings/pihole-servers/test- Test connection
GET /api/blocking/status- Get blocking statusPOST /api/blocking/{server_id}- Set blocking for serverPOST /api/blocking/all- Set blocking for all servers
GET /api/domains/whitelist- Get whitelistPOST /api/domains/whitelist- Add to whitelistPOST /api/domains/blacklist- Add to blacklist
GET /api/sync/preview- Preview sync changesPOST /api/sync/execute- Execute syncGET /api/sync/history- Get sync history
For contributors who want to modify DNSMon:
# Clone repository
git clone https://github.com/darthnorse/DNSMon.git
cd DNSMon
# Start PostgreSQL
docker run -d -p 5432:5432 \
-e POSTGRES_DB=dnsmon \
-e POSTGRES_USER=dnsmon \
-e POSTGRES_PASSWORD=changeme \
postgres:16
# Backend
pip install -r requirements.txt
export DATABASE_URL=postgresql://dnsmon:changeme@localhost:5432/dnsmon
python -m backend.main
# Frontend (separate terminal)
cd frontend
npm install
npm run devTo build a local Docker image:
docker build -t dnsmon .- Check server configuration in Settings → DNS Servers
- Verify credentials with "Test Connection"
- Check logs:
docker compose logs app - Ensure your DNS server's API is accessible
- Verify channel configuration in Settings → Notifications
- Use "Send Test" to verify connectivity
- Check for error messages on the channel card
- Ensure your notification service credentials are correct
- Clear browser cookies and try again
- Check if rate limiting is active (wait 60 seconds)
- Reset password via database if needed
- Ensure PostgreSQL is running:
docker compose ps - Check password matches in
.envand compose file - Restart services:
docker compose restart
- Tested with 250k+ queries/day
- Efficient bulk insert with duplicate detection
- Configurable data retention (default 60 days)
- Connection pooling for database efficiency
- Background task scheduling with overlap prevention
MIT License - See LICENSE file for details.
Contributions are welcome! Please feel free to submit issues and pull requests.
Built with:
- FastAPI - Modern Python web framework
- React - UI library
- PostgreSQL - Database
- Tailwind CSS - Styling
- Recharts - Charts
- SQLAlchemy - ORM
If you find DNSMon useful, consider buying me a coffee!
This project has been developed with vibe coding and AI assistance using Claude Code. The codebase includes clean, well-documented code with proper error handling, comprehensive testing considerations, modern async/await patterns, robust database design, and production-ready deployment configurations.

