Dynamic DNS for Your Hosts Request a hostname under our DynDNS zones and keep it automatically updated with your current IP.
  • Python 70.7%
  • HTML 24.9%
  • CSS 4%
  • Shell 0.3%
  • Dockerfile 0.1%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
2026-09-11 16:35:31 +02:00
.forgejo/workflows chore: Copy over lint rules, build workflow, Podman files 2026-07-02 09:16:33 +02:00
.vscode feat: Initial implementation of DNS server, tested hosts overrides and forwarding 2025-10-22 16:49:16 +02:00
contrib/podman chore: Copy over lint rules, build workflow, Podman files 2026-07-02 09:16:33 +02:00
mallarddns lint: Apply lots of linting rules 2026-09-11 16:31:26 +02:00
scripts lint: Apply lots of linting rules 2026-09-11 16:31:26 +02:00
.gitignore feat: Make static dir configurable 2025-10-25 20:15:39 +02:00
.pre-commit-config.yaml chore: Pre-commit hook for linting 2026-09-11 16:32:22 +02:00
.snapshotignore feat: Initial implementation of DNS server, tested hosts overrides and forwarding 2025-10-22 16:49:16 +02:00
config.dist.yaml chore: Also listen on v6 2026-07-03 08:37:48 +02:00
LICENSE chore: README 2026-09-11 16:35:17 +02:00
pyproject.toml Release v0.2.0 2026-09-11 16:35:31 +02:00
README.md chore: README 2026-09-11 16:35:17 +02:00

MallardDNS

A Django-based DNS server for small networks. Serves authoritative zones, resolves recursively or via upstream forwarders, supports DNSSEC validation, hosts-file and RPZ block lists, and provides a DynDNS service with email verification.

Features

  • Authoritative DNS — manage zones and records through a web UI or REST API
  • Recursive resolution — full iterative resolver with optional DNSSEC validation
  • Forwarding — per-domain forwarding rules with fallback to iterative resolution
  • Block lists — hosts-file and RPZ format imports, with wildcard support
  • DynDNS — token-based dynamic DNS updates with email verification and IP blocking
  • Organizations — multi-tenant with owner/admin/member roles
  • REST API — zone and record management via DRF
  • Response cache — TTL-aware LRU cache for upstream query results
  • Asyncio server — UDP and TCP listeners built on asyncio protocols

Requirements

  • Python 3.10+
  • A supported Django database (SQLite for development, PostgreSQL recommended for production)
  • An SMTP server for DynDNS verification emails

Installation

git clone https://git.private.coffee/privatecoffee/mallarddns
cd mallarddns
python -m venv venv
venv/bin/pip install -e ".[production]"

Copy and edit the config:

cp config.dist.yaml config.yaml
$EDITOR config.yaml

Apply migrations and create a superuser:

venv/bin/python mallarddns/manage.py migrate
venv/bin/python mallarddns/manage.py createsuperuser

Collect static files:

venv/bin/python mallarddns/manage.py collectstatic

Configuration

MallardDNS reads config.yaml by default. Override the path with the MALLARDDNS_CONFIG environment variable.

Key Default Description
SECRET_KEY (required) Django secret key
DEBUG false Enable Django debug mode
ALLOWED_HOSTS [] Hostnames the web server responds to
SITE_URL Public base URL, used in DynDNS emails
TRUST_PROXY false Trust X-Forwarded-For from a reverse proxy
DATABASE SQLite db.sqlite3 Django database config
DNS_HOST 0.0.0.0 IPv4 bind address for the DNS server
DNS_HOST_V6 (disabled) IPv6 bind address (e.g. ::)
DNS_PORT 5353 DNS listen port
RECURSION_UPSTREAMS [] Upstream resolvers for forwarding mode
RECURSION_UPSTREAM_PORT 53 Port used when querying upstreams
RECURSION_UPSTREAM_TIMEOUT 3 Seconds before upstream query times out
RECURSION_FALLBACK_TO_ITERATIVE true Fall back to iterative resolution if upstreams fail
DYNDNS_TOKEN_EXPIRY 24 Hours before DynDNS verification tokens expire (null = never)
CACHE.remote_max_entries 10000 Maximum entries in the upstream response cache
CACHE.remote_min_ttl 5 Minimum TTL (seconds) for cached upstream responses
CACHE.remote_max_ttl 86400 Maximum TTL (seconds) for cached upstream responses
CACHE.negative_ttl_default 30 TTL for negative (NXDOMAIN/empty) cache entries
EMAIL SMTP config for DynDNS emails

Running

Web server

venv/bin/gunicorn mallarddns.wsgi

Or behind a reverse proxy:

location / {
    proxy_pass http://127.0.0.1:8000;
    proxy_set_header X-Forwarded-For $remote_addr;
}

DNS server

venv/bin/python mallarddns/manage.py rundns

Options:

Flag Default Description
--host HOST DNS_HOST / DNS_HOST_V6 from config Address to listen on; may be repeated
--port PORT DNS_PORT from config Port to listen on
--recursive yes|no yes Enable iterative recursive resolution
--forwarding yes|no no Enable forwarding to upstream resolvers
--dnssec yes|no no Enable DNSSEC validation

Example — listen on a non-standard port with forwarding:

venv/bin/python mallarddns/manage.py rundns --port 53 --forwarding yes

Example — dual-stack on port 53:

venv/bin/python mallarddns/manage.py rundns --host 0.0.0.0 --host :: --port 53

Maintenance commands

# Refresh IANA root server list
venv/bin/python mallarddns/manage.py update_root_servers

# Refresh IANA DNSSEC trust anchors
venv/bin/python mallarddns/manage.py update_trust_anchors

Development

venv/bin/pip install -e ".[dev]"
venv/bin/pre-commit install

Run tests:

venv/bin/python mallarddns/manage.py test mallarddns.records

Lint and format:

venv/bin/ruff check . --fix && venv/bin/ruff format .
venv/bin/djlint mallarddns --reformat

License

MIT — see LICENSE.