A lightweight Prometheus exporter for Technitium DNS Server (github) that exposes dashboard, DNS, zone, DHCP, and top‑query statistics for single servers or clusters. Includes an accompanying dashboard for visualization in Grafana.
Inspired by pihole-exporter and Pi-hole Exporter Grafana dashboard. After migrating from Pi-hole to Technitium, I couldn’t find any exporter that provided the metrics I needed while remaining simple and reliable — so I built one.
- Cluster-Aware: Can monitor a complete Technitium cluster from a single exporter instance by proxying requests through the primary controller.
- All metrics represent the current Technitium dashboard window, not cumulative counters.
- No artificial counters or rate conversions
- DNS traffic metrics reflect Technitium’s current dashboard statistics window (e.g.
LastHour), not lifetime counters. Zone and DHCP metrics reflect current server state at scrape time. - Logging is explicit on API failures
- Metric cardinality is intentionally bounded
- Suitable for home labs and small/medium installations. Use of python over golang comes from the need for a quick and easy to understand solution without the need to define specific golang structs for each Technitium DNS API output, which could change any time and break strict golang exporter.
All configuration is done via environment variables.
| Variable | Description | Default |
|---|---|---|
TECHNITIUM_BASE_URL |
Base URL of Technitium DNS API (the controller if using clustering) | http://technitium:5380 |
TECHNITIUM_TOKEN |
Required API token | (none) |
TECHNITIUM_VERIFY_SSL |
SSL Verification (set to "false" if using self-signed certs) | true |
TECHNITIUM_STATS_RANGE |
Stats window (The duration type for which valid values are: LastHour, LastDay, LastWeek, LastMonth, LastYear, Custom. ) |
LastHour |
TECHNITIUM_TOP_LIMIT |
Number of entries in Top lists (Clients/Domains). | 50 |
EXPORTER_PORT |
Port exporter listens on | 9105 |
LOG_LEVEL |
Logging level | INFO |
SERVER_LABEL |
Single Mode: Custom label for the server tag in Grafana. | technitium |
TECHNITIUM_NODES |
Cluster Mode: Comma-separated list of node names to scrape (optional). | (unset) |
The exporter supports two modes of Technitium DNS operation: single server and clustering.
If TECHNITIUM_NODES is left empty, the server label in Prometheus/Grafana will be set to the value of SERVER_LABEL (default: technitium).
If you are running a Technitium cluster, you only need one exporter instance.
Point TECHNITIUM_BASE_URL to your primary controller.
Set TECHNITIUM_NODES to a comma-separated list of the node names exactly as they appear in your Technitium panel.
Example: TECHNITIUM_NODES="primary-node,secondary-01,secondary-02"
The exporter will iterate through this list, asking the Controller to proxy requests to each node. Metrics will be tagged automatically: server="primary-node", server="secondary-01", etc.
Note: SERVER_LABEL is ignored in this mode.
The exporter needs a read-only API token to talk to Technitium DNS server.
Here’s only way I am aware of to generate one:
- Open your Technitium instance in a browser.
- Navigate to Administration > Groups and create a group named “read-only”.
- Now create a user named “readonly” and make it a member of "read-only" group.
- Go to Administration > Permissions and verify that Everyone has read-only access to everything.
- Go to Zones > select zone > Permissions,
- In Group Permissions > add group "read-only", Assign View permission to "read-only" group and click Save
- Repeat for all Zones you wish to export
- Log out of Technitium. Log back in as the readonly user you just created.
- Click on readonly user's profile in the top-right corner and select “Create API token”.
Available images are:
- linux/amd64
- linux/arm64
- linux/arm/v7
Example docker-compose.yml for single Technitium instance :
technitium_exporter:
image: ghcr.io/guycalledseven/technitium-dns-prometheus-exporter:latest
container_name: technitium_exporter
depends_on:
- prometheus
restart: unless-stopped
environment:
TECHNITIUM_BASE_URL: http://technitium:5380
# TECHNITIUM_BASE_URL: https://technitium::53443
# TECHNITIUM_VERIFY_SSL: false
TECHNITIUM_TOKEN: your-api-token-here
SERVER_LABEL: technitium
TECHNITIUM_STATS_RANGE: LastHour
TECHNITIUM_TOP_LIMIT: 50
EXPORTER_PORT: 9105
ports:
- "9105:9105"Example for Technitium DNS cluster:
technitium_exporter:
image: ghcr.io/guycalledseven/technitium-dns-prometheus-exporter:latest
container_name: technitium_exporter
depends_on:
- prometheus
restart: unless-stopped
environment:
TECHNITIUM_BASE_URL: https://technitium:5380 # your controller
TECHNITIUM_VERIFY_SSL: false # eg. has self signed cert
TECHNITIUM_TOKEN: your-api-token-here
TECHNITIUM_NODES: "primary-node,secondary-01,secondary-02" # your nodes
TECHNITIUM_STATS_RANGE: LastHour
TECHNITIUM_TOP_LIMIT: 50
EXPORTER_PORT: 9105
ports:
- "9105:9105"scrape_configs:
- job_name: technitium
static_configs:
- targets:
- technitium-exporter:9105
⚠️ Important: Due to how Technitium DNS currently exposes data - DNS traffic metrics are window-based snapshots, not lifetime counters.
They reflect Technitium’s current dashboard statistics window (TECHNITIUM_STATS_RANGE— e.g.,LastHour,LastDay,LastWeek).
Values may increase or decrease over time as the window slides.
These metrics come from /api/dashboard/stats/get and /api/dashboard/stats/getTop and represent “stats over the last X”:
technitium_dns_queries_window{server, category}technitium_dns_response_type_total{server, type}technitium_dns_query_type_total{server, qtype}technitium_dns_protocol_queries{server, protocol}technitium_dns_top_domain_hits{server, domain}technitium_dns_top_blocked_domain_hits{server, domain}technitium_dns_top_client_hits{server, client_ip, client_name}technitium_dns_clients_window{server}
Use these for:
- % blocked
- Cache hit ratio
- Query mix breakdown
- Error & NXDOMAIN analysis
- Top talkers and Pi-hole-style dashboards
These reflect the current state of the DNS server, unaffected by the stats window:
technitium_dns_zones{server}technitium_dns_cached_entries{server}technitium_dns_allowed_zones{server}technitium_dns_blocked_zones{server}technitium_dns_allowlist_zones{server}technitium_dns_blocklist_zones{server}technitium_zone_info{server, zone, type, disabled, internal, serial}technitium_dhcp_leases_total{server, scope, type}
Use these for:
- Zone inventory and health
- Blocklist/allowlist statistics
- Cache size monitoring
- DHCP scope usage
Exporter + API health:
technitium_up{server}— API reachability (0/1)technitium_scrape_duration_seconds{server}— exporter scrape duration
Python client process metrics:
python_gc_*python_info
technitium_up{server}technitium_scrape_duration_seconds{server}
technitium_dns_queries_window{server, category}
Categories:
all
no_error
nxdomain
servfail
refused
authoritative
recursive
cached
blocked
dropped
technitium_dns_response_type_total{server, type}technitium_dns_query_type_total{server, qtype}technitium_dns_protocol_queries{server, protocol}
technitium_dns_top_client_hits{server, client_ip, client_name}technitium_dns_top_domain_hits{server, domain}technitium_dns_top_blocked_domain_hits{server, domain}
technitium_zone_info{server, zone, type, disabled, internal, serial}technitium_dns_zones{server}technitium_dns_allowed_zones{server}technitium_dns_blocked_zones{server}technitium_dns_allowlist_zones{server}technitium_dns_blocklist_zones{server}technitium_dns_cached_entries{server}
technitium_dhcp_leases_total{server, scope, type}
-
Per‑client block rate
-
Per‑zone query statistics
-
DNSSEC / DoH / DoT breakdowns
-
Rate‑limited client metrics
-
Optional caching layer to reduce API calls?
-
Experiment with custom length window with
Customtype which takes start/end into consideration:if (type == DashboardStatsType.Custom) { string strStartDate = request.GetQueryOrForm("start"); string strEndDate = request.GetQueryOrForm("end");
Available in repo and Grafana dashboards.
Apache License 2.0
