AdGuardHome icon indicating copy to clipboard operation
AdGuardHome copied to clipboard

DNSoverHTTPS does not works when nginx is used to reverse proxy with certbot https

Open cleanerspam opened this issue 3 months ago • 3 comments

Prerequisites

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

cleanerspam avatar Sep 24 '25 14:09 cleanerspam

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 "";

iHaveAstream avatar Sep 28 '25 16:09 iHaveAstream

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.

quaintdev avatar Oct 31 '25 03:10 quaintdev

It is working for Win11 client but not for my Pixel 7, has somebody an clue?

lex87-star avatar Nov 17 '25 18:11 lex87-star