DoH Wire Format (RFC 8484)

Send application-scoped ResolveDB DNS queries over HTTPS using RFC 8484 wire format.

Endpoint

https://doh.resolvedb.io/dns-query

ResolveDB is authoritative-only. Use this endpoint for ResolveDB qnames; do not configure it as a browser or operating-system resolver for unrelated domains.

GET

Build a DNS message, encode it as unpadded Base64url, and send it in the dns query parameter:

GET /dns-query?dns={base64url-dns-message}
Accept: application/dns-message

The encoded parameter is limited to 8 KiB and the decoded DNS message is limited to 4 KiB. Generate the bytes with a DNS library rather than hand-encoding names.

POST

Send a DNS wire message as the request body:

POST /dns-query
Content-Type: application/dns-message
Accept: application/dns-message

{binary DNS message}

The request body is limited to 4 KiB.

Python Example

import dns.message
import dns.query

query = dns.message.make_query(
    "get.newyork.weather.public.v1.resolvedb.net",
    "TXT",
)
response = dns.query.https(
    query,
    "https://doh.resolvedb.io/dns-query",
)

for rrset in response.answer:
    for record in rrset:
        print(record.to_text())

Go Example

package main

import (
    "encoding/base64"
    "fmt"
    "io"
    "net/http"
    "net/url"
    "strings"

    "github.com/miekg/dns"
)

func main() {
    message := new(dns.Msg)
    message.SetQuestion("get.newyork.weather.public.v1.resolvedb.net.", dns.TypeTXT)

    wire, err := message.Pack()
    if err != nil {
        panic(err)
    }

    encoded := base64.RawURLEncoding.EncodeToString(wire)
    endpoint := "https://doh.resolvedb.io/dns-query?dns=" + url.QueryEscape(encoded)
    request, err := http.NewRequest(http.MethodGet, endpoint, nil)
    if err != nil {
        panic(err)
    }
    request.Header.Set("Accept", "application/dns-message")

    response, err := http.DefaultClient.Do(request)
    if err != nil {
        panic(err)
    }
    defer response.Body.Close()

    body, err := io.ReadAll(response.Body)
    if err != nil {
        panic(err)
    }
    answer := new(dns.Msg)
    if err := answer.Unpack(body); err != nil {
        panic(err)
    }

    for _, rr := range answer.Answer {
        if txt, ok := rr.(*dns.TXT); ok {
            fmt.Println(strings.Join(txt.Txt, ""))
        }
    }
}

Responses

Successful HTTP handling returns application/dns-message; inspect the DNS RCODE for query-level failures.

RCODEMeaning
NOERRORAnswer or NODATA
FORMERRMalformed UQRP input
SERVFAILService or storage failure
REFUSEDAuthorization, namespace, or reserved-resource denial

Wire responses with answers use Cache-Control: max-age=<minimum DNS TTL>. Errors, empty answers, and private TTL-0 answers use Cache-Control: no-store. TXT records may contain multiple 255-byte character strings; concatenate them in order before parsing the UQRP envelope.

HTTP Errors

StatusMeaning
200DNS response returned; inspect its RCODE
400Missing, malformed, or oversized DNS message
406Accept does not allow application/dns-message
415POST body has the wrong media type

Next Steps