A security proxy for Technitium DNS Server that adds fine-grained access control to the Technitium HTTP API. It runs as a standalone FastAPI service, forwarding allowed requests to the upstream Technitium server while enforcing token-based policies.
Clients use the standard Technitium API. They just point at the proxy instead of Technitium directly.
- YAML-driven configuration (
config.yml) with per-token access policies - Zone-scoped tokens that restrict which DNS zones a token can access
- Operation filtering to limit tokens to specific CRUD operations (
get,add,update,delete) - Record type filtering to restrict tokens to specific DNS record types (
A,AAAA,CNAME,TXT, etc.) - Subdomain filtering to limit tokens to manage records under a specific subdomain prefix
- Global read-only tokens that allow full read access across all zones without write permissions
- Tiered endpoint classification where only record and zone-list endpoints are proxied; zone management and admin endpoints are blocked
- Zone list filtering where
/api/zones/listresponses only show zones the token is allowed to access - Structured audit logging via structlog
- Multi-arch Docker images (linux/amd64, linux/arm64)
- Standalone binary builds via PyInstaller
The proxy sits between clients and the Technitium DNS Server:
Client --> Proxy (port 31399) --> Technitium (port 5380)
- Client sends a request with their token (via
X-API-Tokenheader or?token=query param) - Proxy validates the token against the configured policy
- If allowed, the request is forwarded to Technitium
- The response is returned to the client (with zone list filtering applied if applicable)
| Tier | Endpoints | Access |
|---|---|---|
| Tier 1 | /api/zones/records/*, /api/zones/list |
Allowed (with policy checks) |
| Tier 2 | /api/zones/create, /api/zones/delete, /api/zones/enable, /api/zones/disable, /api/zones/import, /api/zones/export |
Blocked |
| Tier 3 | All other /api/* (admin, settings, etc.) |
Blocked |
services:
technitium-api-proxy:
image: spaleks/technitium-api-proxy:latest
# or quay.io/spaleks/technitium-api-proxy:latest
ports:
- "31399:31399"
volumes:
- ./config.yml:/app/config.yml:rodocker run --rm \
-p 31399:31399 \
-v "$(pwd)/config.yml:/app/config.yml:ro" \
spaleks/technitium-api-proxy:latesttechnitium:
url: "http://localhost:5380"
token: "your-admin-api-token"
verify_ssl: true
tokens:
# Full access to a zone
- name: "full-access"
token: "client-secret-token"
zones:
- name: "example.com"
allowed_record_types: ["A", "AAAA", "CNAME", "TXT"]
allowed_operations: ["list", "get", "add", "update", "delete"]
# Only manage records under app.example.com (regex pattern)
# Allows: app.example.com, api.app.example.com, v2.app.example.com
# Denies: www.example.com, mail.example.com
- name: "app-team"
token: "app-team-secret"
zones:
- name: "example.com"
subdomain_filter: '^app\.'
allowed_record_types: ["A", "AAAA", "CNAME"]
allowed_operations: ["list", "get", "add", "update", "delete"]
# Read-only access to all zones (no zone scoping)
- name: "monitoring"
token: "monitoring-secret"
global_read_only: true| Field | Type | Default | Description |
|---|---|---|---|
name |
string | required | Display name for audit logs |
token |
string | required | The secret token clients use to authenticate |
global_read_only |
bool | false |
Allow read-only access to all zones (ignores zones) |
zones |
list | [] |
Zone-level access policies |
| Field | Type | Default | Description |
|---|---|---|---|
name |
string | required | DNS zone name (e.g. example.com) |
allowed_record_types |
list | [] (all) |
Restrict to specific record types (A, AAAA, CNAME, TXT, MX, etc.) |
allowed_operations |
list | [] (all) |
Restrict to specific operations (get, add, update, delete) |
subdomain_filter |
string | null |
Regex pattern to match against the domain (case-insensitive) |
Empty lists mean "all allowed". Omit allowed_record_types to allow all record types, omit allowed_operations to allow all operations.
Download the binary from the releases page:
chmod +x technitium-api-proxy
CONFIG_PATH=./config.yml ./technitium-api-proxygit clone https://github.com/spaaleks/technitium-api-proxy.git
cd technitium-api-proxy
bin/start.sh| Variable | Default | Description |
|---|---|---|
CONFIG_PATH |
config.yml |
Path to the YAML configuration file |
HOST |
0.0.0.0 |
Host/IP to bind |
PORT |
31399 |
Port to bind |
LOG_LEVEL |
info |
Log level (debug, info, warning, error) |
Clients authenticate by passing their token in one of two ways:
Header (preferred):
curl -H "X-API-Token: your-token" http://proxy:31399/api/zones/listQuery parameter:
curl http://proxy:31399/api/zones/list?token=your-tokenThe header takes precedence if both are provided.
Inspired by powerdns-api-proxy and the Technitium feature request TechnitiumSoftware/DnsServer#958.
MIT