Contact Us

DNS over HTTPS in Angie with ACME Certificates

You have probably come across the Angie server's features as a reverse proxy and web server with HTTPS support. Angie can also proxy and balance TCP and UDP services, including DNS. The new DoH module combines these capabilities, implementing the DNS over HTTPS protocol for clients. Add the ACME module for automatic TLS certificates and you get a complete solution.

Video version

A video version of this article is available on YouTube.

What is DoH and why is it needed?

The DNS (Domain Name System) service plays a key role in how the modern internet works. However, the early versions of the standard were designed without security and privacy requirements in mind. So DNS is an open binary protocol, vulnerable to interception and tampering on public networks. From a security standpoint, the natural next step is to use HTTPS as the transport for DNS. HTTPS encrypts DNS queries and responses, which solves part of the security problem.

Client-side DoH support is fairly wide: major browsers (Chrome, Firefox, Safari), operating systems (OpenWRT, Router OS, KeeneticOS), and command-line utilities (dig, curl, q).

Technically, with DoH the client first establishes an ordinary HTTPS connection, usually over the standard TCP port 443. If the DoH server address is given as a name, a helper DNS server is needed to resolve that name to an address. The client then sends a query via GET or POST carrying the DNS query body, with the content type set to application/dns-message. A successful response comes back with code 200 OK. The response also carries a cache-control header whose max-age parameter equals the minimum TTL (time to live) if one could be obtained from the DNS server's answer.

Time to try the DoH module in action.

The DoH module in Angie

The module implementing DoH appeared in version 1.12. It accepts DoH connections and proxies DNS queries to regular servers over TCP and UDP. Besides plain proxying, you can configure a group of DNS servers for load balancing and fault tolerance.

Here is a basic working configuration:

http {
  upstream dns_upstream {
    server 127.0.0.53:53;
  }

  server {
      listen 443 ssl;

      ssl_certificate      /etc/angie/ssl/1.pem;
      ssl_certificate_key  /etc/angie/ssl/1.key;

      server_name dns.test.ru;

      location / {
        doh_pass dns_upstream;
      }

      location /dns-query {
        doh_pass dns_upstream;
      }
  }
}

This creates a server group (dns_upstream) that will hold the list of DNS servers. For now the group contains only the local systemd-resolved DNS server (127.0.0.53:53). The main server block describes the HTTPS side of the service. As usual, the socket is declared with listen, the domain with server_name, and the certificate and key with ssl_certificate and ssl_certificate_key. Queries can be handled in a dedicated location matching the required URI (/dns-query), or without a path (/).

The key directive here is doh_pass, which proxies DNS queries to a server or a group of servers. The example above already defines the server group (dns_upstream), which makes it easy to add DNS servers later. When picking a server, keep the security requirement in mind: the server must be in a trusted network. You can also point straight at a server address:

location / {
  doh_pass 127.0.0.53:53;
}

The basic DoH setup is done; all that's left is to check that the scheme works. The most accessible tool with DoH support is curl. Testing is easier with the extra debug flag (-v), which gives maximum detail on the interaction with the server. Example command:

curl -v --head --doh-url https://dns.test.ru/dns-query https://ya.ru

For the test, a HEAD request (--head) and the DoH server address (--doh-url) are enough; the last parameter is the site to check. If the test uses untrusted certificates, add the --doh-insecure option. Out of the command's extensive output, let's pick out the HTTP responses and the DNS records:

...
* using HTTP/1.x
> POST / HTTP/1.1
> Host: dns.metodlab.ru
> Accept: */*
> Content-Type: application/dns-message
> Content-Length: 23
...
< HTTP/1.1 200 OK
< Server: Angie/1.12.1
< Date: Mon, 31 Aug 2026 11:32:06 GMT
< Content-Type: application/dns-message
< Content-Length: 71
< Connection: keep-alive
< Cache-Control: max-age=77
...
* a DoH request is completed, 0 to go
* DoH Host name: ya.ru
* TTL: 77 seconds
* DoH A: 5.255.255.242
* DoH A: 77.88.55.242
* DoH A: 77.88.44.242
* Host ya.ru:443 was resolved.
...

The first two fragments show the HTTP request and the response. The response carries the Cache-Control header (the record's TTL). The third fragment holds the content of the DNS response (the A records and the TTL).

The core functionality works. Next, a look at what can be tuned. First, curl supports HTTP/2, so enable the protocol in the server block:

server {
    http2 on;
}

By default, Angie picks the protocol for talking to the DNS server (TCP or UDP) automatically. You can restrict it to a single protocol with doh_transport:

server {
  doh_transport tcp;
}

As with regular proxying, keepalive connections (TCP) can be enabled for a server group:

upstream dns_upstream {
  server 192.168.0.1:53;
  server 192.168.0.2:53;
  keepalive 16;
}

You can also set the socket option SO_KEEPALIVE for outgoing connections to the DNS server:

server {
  doh_socket_keepalive on;
}

Timeouts are configurable for sending queries (doh_send_timeout) and receiving responses (doh_read_timeout). So are the response buffer (doh_buffer_size) and the client request size limit (doh_max_size). All the settings are described in the module documentation.

So the DoH server is configured and verified. One question has been left aside: obtaining the TLS certificate — and that is where the ACME module helps.

Automatic certificates with ACME

The ACME module's operation was covered in detail in a separate earlier article. A quick reminder: the ACME module for Angie automates obtaining TLS certificates over the ACME protocol, using HTTP, ALPN and DNS validation and external services.

Extend the server configuration to get automatic certificates:

http {
  upstream dns_upstream {
    server 127.0.0.53:53;
  }

  acme_client ecdsa    https://acme-v02.api.letsencrypt.org/directory;

  server {
      listen 443 ssl;

      acme ecdsa;

      ssl_certificate      $acme_cert_ecdsa;
      ssl_certificate_key  $acme_cert_key_ecdsa;

      server_name dns.test.ru;

      location / {
        doh_pass dns_upstream;
      }

      location /dns-query {
        doh_pass dns_upstream;
      }
  }
}

This adds the certificate description (acme_client) and references it in the server. On the first start, Angie runs the certificate issuance process with an HTTP challenge. The certificate then renews automatically, with no configuration reload. In current versions the resolver directive is not required. The HTTP socket is also opened automatically.

Keep in mind that issuing a wildcard certificate requires DNS validation.

The configuration is now complete.

Summary

This article covered the basic principles of the DNS over HTTPS (DoH) protocol. Using the DoH module of the Angie server, we set up a basic configuration with automatic TLS certificates via ACME, and looked at the fine-tuning options for working with DNS servers.