DNSCrypt
3D render: a stack of white and metal server units with status lights, beside a glass query capsule

For developers

The DNSCrypt Protocol for Developers: Spec, Stamps and Servers

The dnscrypt protocol is compact and openly documented, with Frank Denis named as the author of its current draft. Its version 2 specification is published at dnscrypt.info/protocol, and an Internet-Draft edition, draft-denis-dprive-dnscrypt, lives in the DNSCrypt/dnscrypt-protocol repository together with a reference implementation and the Anonymized DNSCrypt extension, all kept together in one place.

The draft covers more than the original text: certificate discovery, packet formats, the classical X25519 encryption system, relay operation and PQDNSCrypt, a hybrid post-quantum system built on X-Wing (ML-KEM-768 combined with X25519). If you are starting a new implementation, work from the draft and treat the dnscrypt.info page as the readable overview.

Hop 01

The dnscrypt protocol message flow

A client starts with a provider name, a 32-byte Ed25519 provider public key and the server’s address. It sends a plain TXT query for the provider name, of the form 2.dnscrypt-cert.example.com, first over UDP and over TCP on failure. Every TXT record in the answer holds one binary certificate, never hex or base64 encoded.

A certificate starts with the magic bytes 0x44 0x4e 0x53 0x43, followed by the encryption system version, a minor version, a 64-byte Ed25519 signature, the resolver’s short-term public key, an 8-byte client magic, a serial and a validity window. For the Box-XChaChaPoly system it is 124 bytes without extensions. The client verifies the signature and keeps the highest valid serial.

An encrypted query is the client magic, the client’s X25519 public key, half of the nonce and the boxed DNS message. The plaintext is padded in ISO/IEC 7816-4 style, a 0x80 byte followed by zeros. The resolver answers with its own magic and the other nonce half, and refuses anything with a bad key exchange, tag or padding.

Transport rules are short. Clients and resolvers must support TCP and should support UDP, with port 443 recommended for both. Responses that do not fit are truncated with the TC bit set before padding and encryption, so the client retries over TCP. Certificates must be refreshed before they expire, and the draft considers an hourly refresh suitable.

Hop 02

libsodium and the dnscrypt protocol primitives

Everything the classical encryption system needs is in libsodium, a portable fork of NaCl: X25519 for key exchange, XChaCha20 and XSalsa20 stream ciphers, Poly1305 authentication and Ed25519 signatures. Its default crypto_box is X25519 with XSalsa20 and Poly1305, the older DNSCrypt construction, so the current XChaCha20 system needs the dedicated XChaCha20 functions instead.

dnscrypt-wrapper, a server written in C, requires libsodium and libevent, and the original C version of dnscrypt-proxy depended on libsodium as well. The v1 wiki comparison notes that its XChaCha20 support only worked when compiled against a recent libsodium. Recent libsodium documentation also covers ML-KEM-768, one half of the post-quantum construction.

Hop 03

DNSCrypt server implementations

The table summarises the server side as listed on dnscrypt.info/implementations and checked against each project’s own documentation in October 2026. Any of them can turn an existing recursive resolver into a dnscrypt server; only some of them can also act as an Anonymized DNSCrypt relay or generate their own provider keys.

ServerLanguageDNSCrypt notes
encrypted-dns-serverRustDNSCrypt v2 incl. PQDNSCrypt, Anonymized DNSCrypt relay, DoH forwarding on the same port; needs a recursive resolver such as Unbound, Knot or PowerDNS
dnscrypt-server-dockerDocker imageBundles a caching resolver with encrypted-dns-server and setup scripts
dnscrypt-wrapperCServer-side proxy in front of any resolver; requires libsodium and libevent
dnsdistC++DNSCrypt binds via addDNSCryptBind() or YAML binds; generates provider keys from its console
UnboundCBuilt with --enable-dnscrypt; options dnscrypt-enable, dnscrypt-port, dnscrypt-provider; keys come from dnscrypt-wrapper

encrypted-dns-server covers the most ground in the table: it serves DNSCrypt, Anonymized DNSCrypt relaying and DoH forwarding on one port, usually 443, and detects the protocol per client. Unbound’s manual is strict about details, such as the provider name having to end with a dot. For a first public server, the Docker image is the shortest path.

Hop 04

DNS stamps: one string per server

DNS stamps encode connection parameters as sdns:// followed by base64url of a binary record. The first byte names the protocol: 0x01 DNSCrypt, 0x02 DoH, 0x03 DoT, 0x04 DoQ, 0x05 an ODoH target, 0x81 an Anonymized DNSCrypt relay, 0x85 an ODoH relay and 0x00 plain DNS. Length-prefixed fields follow, and base64 is applied only once, to the whole record.

For DNSCrypt, a stamp holds a 64-bit properties field, the address with an optional port, the raw provider key and the provider name. Property bits mark DNSSEC support (1), no logging (2) and no intentional blocking (4). The stamps specification lists libraries for Go, Rust, .NET, Python, JavaScript and Swift, and stamps.dnscrypt.info decodes or builds a stamp in the browser.

Hop 05

From the v1 plugin API to v2 Go code

dnscrypt-proxy 1.x was written in C and offered a plugin API in C, which in practice required libldns to handle DNS packets. The v2 rewrite in Go replaced it: custom additions are simple Go structures built on the miekg/dns library. The project wiki’s comparison of the two versions lists this change alongside asynchronous filters and multicore support.

Hop 06

Where to start

Read the draft end to end, then decode a few real stamps from the resolver directory to see live parameters. Implement certificate retrieval first and test it against a public server before encrypting anything. The DNSCrypt vs DoH comparison and the glossary cover the terms used throughout the dnscrypt protocol specification.

Hop 08

Frequently asked questions

Is the DNSCrypt protocol an IETF standard?

No. The protocol is documented on dnscrypt.info and as an independent-submission Internet-Draft named draft-denis-dprive-dnscrypt, maintained in the DNSCrypt/dnscrypt-protocol repository on GitHub. DNS over TLS, DNS over HTTPS and DNS over QUIC are the encrypted DNS protocols published as IETF RFCs. If you start a new implementation, work from the draft and treat the dnscrypt.info page as the readable overview.

Which library should I use to implement DNSCrypt?

libsodium provides every primitive the classic encryption system needs: X25519, XChaCha20, Poly1305 and Ed25519 signatures. dnscrypt-wrapper is built on it. In Go, the dnscrypt library by Andrey Meshkov is listed on dnscrypt.info, and Rust implementations exist in encrypted-dns-server. Its default crypto_box uses XSalsa20, the older DNSCrypt construction, so the current system needs the dedicated XChaCha20 functions.

How do I generate keys for a DNSCrypt server?

encrypted-dns-server and the dnscrypt-server-docker image generate and rotate keys on their own. dnsdist offers generateDNSCryptProviderKeys and related console functions. Unbound cannot generate the keys and its manual points to dnscrypt-wrapper for that step. For a first public server, the Docker image is the shortest path, since key handling is already built in.

What is a DNS stamp?

A DNS stamp is a single string starting with sdns:// that encodes everything a client needs to reach a server: the protocol, informal properties such as DNSSEC support and logging, the address, and the provider key or certificate hashes. dnscrypt-proxy and other clients accept stamps directly. The stamps specification lists libraries for Go, Rust, .NET, Python, JavaScript and Swift.

↑