Run your own private DNS resolver with advanced filtering, DNS over HTTPS (DoH), and DNS over TLS (DoT) — all in a simple Docker setup.
Pi-hole provides customizable DNS filtering and query logging, dnsdist handles DNS/DoT/DoH backends, and Caddy manages HTTPS with automatic Let's Encrypt certificates.
- Custom DNS filtering: Centralized control over which domains are resolved
- Secure DNS: DoH at
https://<DOMAIN_DNS>/dns-queryand DoT on port853 - Automatic TLS: Caddy obtains and renews certificates via Let's Encrypt
- Dashboard: Full query visibility and management at
https://<DOMAIN_DASHBOARD>/admin/ - Persistence & health checks: Data volumes and restart policies included
Set up A/AAAA DNS records pointing both domains to your server's public IP:
DOMAIN_DNS→ e.g.,dns.example.comDOMAIN_DASHBOARD→ e.g.,dashboard.example.com
Ensure the following ports are available (no other services using them):
80, 443→ Caddy (HTTP/HTTPS)53/tcp, 53/udp→ DNS (dnsdist → Pi-hole)853/tcp→ DoT (dnsdist)
-
Clone the repository:
git clone --depth=1 https://github.com/allenhack638/self-hostable-private-dns.git cd self-hostable-private-dns -
Configure environment:
cp .env.example .env # Edit .env with your domain names and password -
Start the stack:
docker compose up -d
-
⚠️ If you see "port 53 already in use" errors, then disable systemd-resolved:# Stop the containers first docker compose down # Disable systemd-resolved sudo systemctl stop systemd-resolved sudo systemctl disable systemd-resolved # Start the stack again docker compose up -d
To re-enable systemd-resolved later (if needed):
docker compose down
sudo systemctl enable systemd-resolved
sudo systemctl start systemd-resolvedWhy this order matters: If you disable
systemd-resolvedfirst, your system will lose DNS resolution temporarily, preventing Docker from pulling images and Caddy from verifying certificates. Starting the stack first allows everything to download properly, then we only disablesystemd-resolvedif there's actually a conflict.
| Variable | Description | Example |
|---|---|---|
TZ |
Timezone in tz database format | Asia/Kolkata |
FTLCONF_webserver_api_password |
Dashboard login password (use a strong password!) | your-secure-password |
DOMAIN_DNS |
Public domain for DoH/DoT (TLS via Caddy) | dns.example.com |
DOMAIN_DASHBOARD |
Public domain for the dashboard | dashboard.example.com |
Timezone reference: See the full list in tz database time zones.
| Protocol | Address / URL | Port |
|---|---|---|
| DNS | <SERVER_IP> |
53 |
| DoT | <DOMAIN_DNS> |
853 |
| DoH | https://<DOMAIN_DNS>/dns-query |
443 |
| Dashboard | https://<DOMAIN_DASHBOARD>/admin/ |
443 |
| Port(s) | Service |
|---|---|
| 53/tcp, 53/udp | DNS (dnsdist → Pi-hole) |
| 80, 443 | Caddy (HTTP/HTTPS) |
| 853/tcp | DoT (dnsdist) |
- Pi-hole configuration: Stored in
data/pihole/ - Caddy certificates: Stored in
data/caddy-data/ - Shared certificates: Copied to
data/shared-certs/for dnsdist access
All data persists between container restarts and updates.
All services (live):
docker compose logs -fIndividual services:
docker compose logs dns-pihole
docker compose logs dns-caddy
docker compose logs dns-dnsdistThe stack includes built-in health checks and restart policies to ensure services stay running.
This project is built with these excellent open-source tools:
- Pi-hole – Network-level DNS filtering and management
- dnsdist – High-performance DNS load balancer with DoH/DoT support
- Caddy – Modern web server with automatic HTTPS
Issues, questions, and feature requests are welcome! Please open an issue or start a discussion in this repository.
Need custom configurations? I can help with:
- Custom ACLs and fine-grained access control
- Advanced dnsdist load-balancing strategies
- Conditional forwarding rules
- Integration with upstream/external resolvers
- Enterprise-grade scaling and security hardening
📬 Contact: allenbenny038@gmail.com
