DNSoverHTTPS does not works when nginx is used to reverse proxy with certbot https
Prerequisites
-
[x] I have checked the Wiki and Discussions and found no answer
-
[x] I have searched other issues and found no duplicates
-
[x] I want to report a bug and not ask a question or ask for help
-
[x] I have set up AdGuard Home correctly and configured clients to use it. (Use the Discussions for help with installing and configuring clients.)
Platform (OS and CPU architecture)
Linux, AMD64 (aka x86_64)
Installation
Docker
Setup
On one machine
AdGuard Home version
Version: v0.107.66
Action
curl -vk https://domain.tld/dns-query
`* using HTTP/1.x
> GET /dns-query HTTP/1.1
> Host: domain.tld
> User-Agent: curl/8.5.0
> Accept: */*
>
* TLSv1.3 (IN), TLS handshake, Newsession Ticket (4):
* TLSv1.3 (IN), TLS handshake, Newsession Ticket (4):
* old SSL session ID is stale, removing
< HTTP/1.1 404 Not Found
< Server: nginx/1.24.0 (Ubuntu)
< Date: Wed, 24 Sep 2025 14:13:39 GMT
< Content-Type: text/plain; charset=utf-8
< Content-Length: 10
< Connection: keep-alive
< Access-Control-Allow-Origin: http://domain.tld
< Vary: Origin
< X-Content-Type-Options: nosniff
<
Not Found
* Connection #0 to host domain.tld left intact
I have installed adguardhome using docker compose with config
services:
adguard:
image: adguard/adguardhome:latest
container_name: adguard
restart: unless-stopped
ports:
- "53:53/udp" #DNS
- "53:53/tcp" #DNS
- "853:853/tcp" #TLS
- "8085:80" #Dashboard
- "3000:3000" # SetupUI
volumes:
- ./conf:/opt/adguardhome/conf:rw
- ./work:/opt/adguardhome/work:rw
environment:
- TZ=UTC
networks:
- adguard-net
networks:
adguard-net:
driver: bridge
and nginx with sudo nano /etc/nginx/sites-available/domain.tld with content
server {
server_name domain.tld;
# Handle .well-known paths (used by mobile apps, WebDAV auto-discovery, ACME challenges, etc.)
location ^~ /.well-known {
proxy_pass http://127.0.0.1:8085;
proxy_http_version 1.1;
# Preserve headers
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# WebSocket support (if needed)
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
# Regular web UI / app traffic -> AdGuard HTTP port
location / {
proxy_pass http://127.0.0.1:8085;
proxy_http_version 1.1;
# Preserve headers
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# WebSocket support (if needed)
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
# ----------------- AdGuard DoH endpoint -----------------
location = /dns-query {
# Let AdGuard see the original client IP chain & proto
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_bind 127.0.0.1;
# Point to the AdGuard HTTP port published on the host
proxy_pass http://127.0.0.1:8085/dns-query;
proxy_http_version 1.1;
# Avoid sending "Connection: keep-alive" to upstream (some upstreams prefer blank)
proxy_set_header Connection "";
# Optional timeouts (tune if you see slow requests)
# proxy_connect_timeout 5s;
# proxy_send_timeout 30s;
# proxy_read_timeout 60s;
# Optional: increase buffer sizes if you proxy large responses (not usually needed for DoH)
# proxy_buffer_size 16k;
# proxy_buffers 4 32k;
# proxy_busy_buffers_size 64k;
}
listen 443 ssl; # managed by Certbot
ssl_certificate /etc/letsencrypt/live/domain.tld/fullchain.pem; # managed by Certbot
ssl_certificate_key /etc/letsencrypt/live/domain.tld/privkey.pem; # managed by Certbot
include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot
}
server {
if ($host = domain.tld) {
return 301 https://$host$request_uri;
} # managed by Certbot
listen 80;
server_name domain.tld;
return 404; # managed by Certbot
}
Expected result
- Able to access dashboard securely
- Able to use DNS over TLS
- Able to use DNS over HTTPS
Actual result
- Able to access dashboard with https ✅
- Able to use DNS over TLS in Android's Private DNS ✅
- Unable to use DNS over HTTPS ❌
Additional information and/or screenshots
Direct port 443 cant be given only to adguardhome as other services are also running and they require nginx reverse proxy to provide https domain access docker is running adguardhome and nginx is being run on host which is Ubuntu 24.04
Same issue here with Nginx-NPM. DoT works, Dashboard via https works but not DoH. I am not using Docker but Proxmox LXC for both, AGH and Nginx-NPM (both Debian 12 base). this is my AdGuardHome.yaml:
tls:
enabled: true
server_name: dns-domain.tld
force_https: false
port_https: 0
port_dns_over_tls: 58853
port_dns_over_quic: 0
port_dnscrypt: 0
dnscrypt_config_file: ""
allow_unencrypted_doh: true
certificate_chain: ""
private_key: ""
certificate_path: /etc/ssl/adguard/fullchain.pem
private_key_path: /etc/ssl/adguard/privkey.pem
strict_sni_check: false
[...]
trusted_proxies:
- 127.0.0.0/8
- ::1/128
- 192.168.20.9/32
The Cert is only for DoT. It's the very same one as in Nginx-NPM (copied via rsync). TLS Termination for DoH shall be done by the Reverse Proxy.
this is the Nginx-NPM Proxy Host config for /dns-query location:
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_bind 192.168.20.9;
#proxy_pass http://192.168.20.7:3000/dns-query;
proxy_http_version 1.1;
proxy_set_header Connection "";
I am using Caddy and I have the same issue. All DoH queries fail as show below
$ curl -H 'accept: application/dns-json' 'http://localhost:8443/dns-query?name=google.com&type=A'
Client sent an HTTP request to an HTTPS server.
Since I have enabled allow_unencrypted_doh flag DoH server should accept http connections.
It is working for Win11 client but not for my Pixel 7, has somebody an clue?