Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

DoH and tokens

DNS over HTTPS (RFC 8484) is served by the dnsy-server DoH listener on /dns-query. It speaks HTTP/1.1 and HTTP/2 over TLS (ALPN h2, http/1.1) and uses the same certificate loading and hot reload as DoT: replacing the PEM files takes effect for new connections, and a bad replacement keeps the previous certificate.

Requests

MethodRequest
GET/dns-query?dns=<base64url>, the DNS message in base64url without padding
POST/dns-query, body is the DNS message, Content-Type: application/dns-message

Responses are 200 with Content-Type: application/dns-message and Cache-Control: max-age=<n>, where n is the smallest TTL of any record in the response (0 when there are none). It is Cache-Control: private, max-age=<n> when the request presented a token or trusted_proxies is set, since the answer may then depend on who asked. Responses are padded (RFC 8467) when the query carries an EDNS Padding option.

StatusMeaning
400bad base64, missing dns, not a DNS message, empty body, or more than 65535 octets (body or decoded parameter)
401no token (when required) or a rejected token; WWW-Authenticate: Bearer
403valid token without the dns:query scope
404any path other than the configured [doh] path (default /dns-query) and {path}/{token}
405method other than GET and POST (Allow: GET, POST)
415POST with a content type other than application/dns-message
429too many rejected tokens from this client address (Retry-After)

Tokens

Tokens are created through the API (scope dns:query; see Users, tokens and sessions). A token is required by default. Send it either way:

  • Authorization: Bearer <token>
  • in the path, https://dns.example/dns-query/<token>, for clients that cannot set headers (browsers’ DoH settings, many mobile resolvers)

A presented token is always verified, even when tokens are not required. Verification and secret comparison are constant time and done by the token verifier in dnsy-auth. The authenticated principal is attached to the request, so the query log shows which token asked.

A successful verification is cached for 5 s (DOH_TOKEN_CACHE_TTL, at most 4096 tokens, DOH_TOKEN_CACHE_CAPACITY), so a busy client costs one database read per token every 5 s instead of one per query. Rejected tokens are never cached. A revoked token, a disabled or deleted owner or a role change takes effect on the next request when done through the API or the web UI. Changes made with the offline CLI (dnsy user disable, dnsy token create and friends) while the server is running apply to DoH within 5 s (verified tokens are cached for 5 s); dnsy user disable says so when it runs.

Rejected tokens are counted per client (an IPv4 address or an IPv6 /64; default 10 per 60 s). Past that limit the client gets 429 before any token is looked at, so the response never reveals whether a token exists. Malformed, unknown, revoked, expired and wrong-secret tokens all give the same 401. The table holds at most 10 000 clients; clients that reached the limit are never evicted to make room, so flooding it with other addresses cannot unblock them. If a part of the table is full of blocked clients, failures from further clients share one counter that blocks them all once it reaches the limit (the table fails closed).

Secrets are not logged

The request URI and the Authorization header are never logged, never copied into error bodies, and never passed to the engine, so path tokens cannot reach the query log. Access logging records only the client address and status. Prefer the header where the client allows it: path tokens can still be recorded by reverse proxies or browser history in front of or around dnsy.

Settings

The listener is configured through DohConfig: listen addresses, certificate and key, the token authenticator, require_token (default true), max_auth_failures, auth_window and trusted_proxies ([doh] trusted_proxies, empty by default). A request from a listed proxy is attributed to the rightmost X-Forwarded-For entry that is not a listed proxy: that address keys the auth-failure throttle, ACL rules, per-client QPS and the query log (rules and spoofing risks in Security). It shares the idle timeout, connection cap and drain time of the TCP and DoT listeners.