Self-hosted DNS filtering service with per-profile configuration. Block ads, trackers, and malware at the DNS level with customizable profiles for different devices or use cases.
- Per-profile DNS filtering via wildcard subdomain (e.g.,
my-devices.filterdns.example.com) - Multiple protocols: DoH (443), DoT (853), Legacy DNS (53)
- Self-service model: Users create and manage their own profiles
- Custom rules: Per-profile allow/deny lists
- Presets: Predefined blocking rule sets (gaming, social media, etc.)
- Popular blocklists: Hagezi, StevenBlack, OISD pre-configured
- Query logging: View and analyze DNS queries with statistics
- Web UI: Modern admin interface built with SvelteKit
- Pause filtering: Temporarily disable filtering (5/15/30/60 min)
- Link devices: Associate legacy devices by IP for DNS filtering
- Maintenance mode: Block all DNS except allowed domains
┌─────────────────────────────────────────────────────────────────┐
│ filterdns.example.com │
│ │
│ DNS Query ({profile}.filterdns.example.com) │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ DNS Gateway (Python) │ │
│ │ - DoH on 443 (Quart/Hypercorn) │ │
│ │ - DoT on 853 (asyncio TLS) │ │
│ │ - Legacy DNS on 53 (UDP/TCP) │ │
│ └────────────────────┬────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ DNS Filter Engine (dnspython) │ │
│ │ - Per-profile blocklist filtering │ │
│ │ - Custom allow/deny rules │ │
│ │ - Preset rule sets │ │
│ │ - Query logging │ │
│ └────────────────────┬────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ PostgreSQL + Web UI (SvelteKit) │ │
│ └─────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
# Clone the repository
git clone https://github.com/zkmkarlsruhe/filterdns.git
cd filterdns
# Copy example environment
cp .env.example .env
# Edit .env with your settings
nano .env
# Start services
docker compose up -d
# Run database migrations
docker compose exec filterdns alembic upgrade head
# Access the web UI
open http://localhost:8080# Install Python dependencies
poetry install
# Start PostgreSQL (or use docker)
docker run -d --name filterdns-db \
-e POSTGRES_USER=filterdns \
-e POSTGRES_PASSWORD=filterdns \
-e POSTGRES_DB=filterdns \
-p 5432:5432 \
postgres:16-alpine
# Run migrations
poetry run alembic upgrade head
# Start the server (skip blocklist loading for faster startup)
SKIP_BLOCKLISTS=1 poetry run python -m filterdns
# In another terminal, build the frontend
cd web && npm install && npm run dev| Protocol | Port | How Profile is Identified |
|---|---|---|
| DoH | 443 | Subdomain: my-profile.filterdns.example.com/dns-query |
| DoT | 853 | SNI: my-profile.filterdns.example.com |
| Legacy DNS | 53 | Source IP lookup in devices table |
Visit the web UI at http://localhost:8080 and click "Create New Profile".
Or via API:
curl -X POST http://localhost:8080/api/profiles \
-H 'Content-Type: application/json' \
-d '{"name": "my-devices", "password": "optional"}'For DoH (recommended):
- URL:
https://my-devices.filterdns.example.com/dns-query - Works in: Firefox, Chrome, iOS, Android
For DoT:
- Hostname:
my-devices.filterdns.example.com - Port: 853
- Works in: Android Private DNS, iOS (with profile)
For Legacy DNS:
- Link your device's IP address to your profile in the web UI
- Point your device to the FilterDNS server IP on port 53
In the web UI, enable/disable blocklists for your profile:
- Hagezi Multi Normal (ads, tracking)
- Hagezi Threat Intelligence (malware, phishing)
- StevenBlack Unified
- OISD Small/Big
Add allow rules to whitelist specific domains:
curl -X POST http://localhost:8080/api/profiles/my-devices/rules \
-H 'Content-Type: application/json' \
-d '{"domain": "example.com", "rule_type": "allow"}'Add deny rules to block specific domains:
curl -X POST http://localhost:8080/api/profiles/my-devices/rules \
-H 'Content-Type: application/json' \
-d '{"domain": "unwanted.com", "rule_type": "deny"}'GET /api/blocklists- List available blocklistsGET /api/presets- List available presetsPOST /api/profiles- Create new profile
GET /api/profiles/{name}- Get profile configPUT /api/profiles/{name}- Update profileDELETE /api/profiles/{name}- Delete profilePOST /api/profiles/{name}/pause- Pause filteringPOST /api/profiles/{name}/resume- Resume filteringGET /api/profiles/{name}/logs- Query logsGET /api/profiles/{name}/stats- StatisticsGET/POST/DELETE /api/profiles/{name}/rules- Custom rulesGET/PUT /api/profiles/{name}/presets- Enabled presetsGET/POST/DELETE /api/profiles/{name}/devices- Linked devices
POST /api/admin/login- Admin loginGET /api/admin/profiles- List all profilesGET /api/admin/stats- Global statisticsPOST /api/admin/blocklists- Add blocklistPOST /api/admin/presets- Create custom preset
| Variable | Default | Description |
|---|---|---|
DATABASE_URL |
postgresql://... |
PostgreSQL connection string |
FILTERDNS_DOMAIN |
filterdns.example.com |
Base domain for DNS |
FILTERDNS_ADMIN_PASSWORD |
(generated) | Admin panel password |
FILTERDNS_UPSTREAM_DNS |
1.1.1.1,8.8.8.8 |
Upstream DNS servers |
FILTERDNS_TLS_CERT |
- | TLS certificate path |
FILTERDNS_TLS_KEY |
- | TLS private key path |
FILTERDNS_PTR_SERVER |
- | PTR lookup server for reverse DNS |
FILTERDNS_LOG_QUERIES |
true |
Enable query logging |
FILTERDNS_DEBUG |
false |
Debug mode |
SKIP_BLOCKLISTS |
- | Skip blocklist loading on startup |
For DoH and DoT, you need a wildcard certificate for *.yourdomain.com.
Place certificates in ./certs/:
cert.pem- Certificate chainkey.pem- Private key
# Run unit tests
poetry run pytest
# Test DNS resolution (legacy)
dig @localhost -p 53 google.com
# Test blocking
dig @localhost -p 53 doubleclick.net # Should return NXDOMAIN
# Test DoH with curl
curl -H 'content-type: application/dns-message' \
'http://localhost:8080/dns-query?dns=AAABAAABAAAAAAAAA3d3dwZnb29nbGUDY29tAAABAAE'
# Test JSON API
curl 'http://localhost:8080/resolve?name=google.com'filterdns/
├── filterdns/ # Python backend
│ ├── api/ # REST API routes
│ ├── blocklist/ # Blocklist engine
│ ├── db/ # Database layer (asyncpg)
│ ├── dns/ # DNS filtering logic
│ ├── gateway/ # DNS servers (DoH/DoT/DNS53)
│ ├── profiles/ # Preset management
│ ├── app.py # Quart app factory
│ └── config.py # Configuration
├── web/ # SvelteKit frontend
├── alembic/ # Database migrations
├── tests/ # Test suite
├── docker-compose.yml # Production setup
└── Dockerfile # Container image
MIT License - see LICENSE for details.
Part of ZKM Open Source
Copyright (c) 2026 ZKM | Center for Art and Media Karlsruhe