Adguard Home DoH over Caddy + Cloudflare tunnel

1. The problem I’m having:

Hi,

I am hosting an Adguard Home instance in Docker, that publishes a DNS over HTTPS server on /dns-query subpath, that I want to make accessible over the internet. Caddy is behind Cloudflare Tunnel via cloudflared because I cannot open ports with my ISP.

I already have the web admin UI of Adguard proxied through Caddy, on a subdomain. I am also using Authentik with forward auth to protect the administation UI. My caddyfile is the following:

adguard.mydomain.com {
	route {
		reverse_proxy /dns-query adguard:443
		reverse_proxy /outpost.goauthentik.io/* authentik-server:9000
		forward_auth authentik-server:9000 {
			copy_headers X-Authentik-Username X-Authentik-Groups X-Authentik-Entitlements X-Authentik-Email X-Authentik-Name X-Authentik-Uid X-Authentik-Jwt X-Authentik-Meta-Jwks X-Authentik-Meta-Outpost X-Authentik-Meta-Provider X-Authentik-Meta-App X-Authentik-Meta-Version
			uri /outpost.goauthentik.io/auth/caddy
		}
		reverse_proxy adguard:3000
	}
}

I have set the reverse_proxy of /dns-query first so that it is not affected by forward_auth. This rule seems to be working as expected.

The web UI working perfectly, and the forward_auth too, but the DoH is not. I tried to connect to adguard.mydomain.com/dns-query from devices that supports DoH, and it does not work.

When trying to access adguard.mydomain.com/dns-query from a browser, I have a Cloudflare error 502, Bad Gateway.

I see no errors appearing on Adguard, Caddy or Cloudflared containers.

I am unsure if the problem comes from Caddy or cloudflare tunnel configuration. To me it looks more like it would come from my Caddy configuration, because if there is an error in my caddy configuration, I have a Cloudflare Bad Gateway error when trying to access.

Any help to get this to work is welcome. Have a nice day !

2. Error messages and/or full log output:

No error message

3. Caddy version:

Caddy v2.9.1

4. How I installed and ran Caddy:

a. System environment:

Docker on Debian

b. Command:

PASTE OVER THIS, BETWEEN THE ``` LINES.
Please use the preview pane to ensure it looks nice.

c. Service/unit/compose file:

services:

  adguard:
    image: adguard/adguardhome:latest
    container_name: adguard
    environment:
      - PUID=${PUID}
      - PGID=${PGID}
      - TZ=${TZ}
    ports:
      - 53:53
    restart: unless-stopped
    networks:
      - services
    volumes:
      - ${CONFIG_FOLDER}/adguard/work:/opt/adguardhome/work
    #  - ${CONFIG_FOLDER}/adguard/conf:/opt/adguardhome/conf
    configs:
      - source: AdGuardHome.yaml
        target: /opt/adguardhome/conf/AdGuardHome.yaml
    labels:
      caddy: adguard.${DOMAIN}
      caddy.route.1_reverse_proxy: /dns-query adguard:443
      caddy.route.2_reverse_proxy: /outpost.goauthentik.io/* authentik-server:9000
      caddy.route.3_forward_auth: authentik-server:9000
      caddy.route.3_forward_auth.uri: /outpost.goauthentik.io/auth/caddy
      caddy.route.3_forward_auth.copy_headers: X-Authentik-Username X-Authentik-Groups X-Authentik-Entitlements X-Authentik-Email X-Authentik-Name X-Authentik-Uid X-Authentik-Jwt X-Authentik-Meta-Jwks X-Authentik-Meta-Outpost X-Authentik-Meta-Provider X-Authentik-Meta-App X-Authentik-Meta-Version
      caddy.route.4_reverse_proxy: adguard:3000

      
networks:
  services:
    name: services
    external: true

configs:
  AdGuardHome.yaml:
    content: |
      http:
        pprof:
          port: 6060
          enabled: false
        address: 0.0.0.0:3000
        session_ttl: 720h
      users:
        - name: admin
          password: REDACTED
      auth_attempts: 5
      block_auth_min: 15
      http_proxy: ""
      language: ""
      theme: auto
      dns:
        bind_hosts:
          - 0.0.0.0
        port: 53
        anonymize_client_ip: false
        ratelimit: 20
        ratelimit_subnet_len_ipv4: 24
        ratelimit_subnet_len_ipv6: 56
        ratelimit_whitelist: []
        refuse_any: true
        upstream_dns:
          - https://dns10.quad9.net/dns-query
        upstream_dns_file: ""
        bootstrap_dns:
          - 9.9.9.10
          - 149.112.112.10
          - 2620:fe::10
          - 2620:fe::fe:10
        fallback_dns: []
        upstream_mode: load_balance
        fastest_timeout: 1s
        allowed_clients: []
        disallowed_clients: []
        blocked_hosts:
          - version.bind
          - id.server
          - hostname.bind
        trusted_proxies:
          - 127.0.0.0/8
          - ::1/128
        cache_size: 4194304
        cache_ttl_min: 0
        cache_ttl_max: 0
        cache_optimistic: false
        bogus_nxdomain: []
        aaaa_disabled: false
        enable_dnssec: false
        edns_client_subnet:
          custom_ip: ""
          enabled: false
          use_custom: false
        max_goroutines: 300
        handle_ddr: true
        ipset: []
        ipset_file: ""
        bootstrap_prefer_ipv6: false
        upstream_timeout: 10s
        private_networks: []
        use_private_ptr_resolvers: false
        local_ptr_upstreams: []
        use_dns64: false
        dns64_prefixes: []
        serve_http3: false
        use_http3_upstreams: false
        serve_plain_dns: true
        hostsfile_enabled: true
      tls:
        enabled: true
        server_name: "adguard.${DOMAIN}"
        force_https: false
        port_https: 443
        port_dns_over_tls: 853
        port_dns_over_quic: 853
        port_dnscrypt: 0
        dnscrypt_config_file: ""
        allow_unencrypted_doh: true
        certificate_chain: ""
        private_key: ""
        certificate_path: ""
        private_key_path: ""
        strict_sni_check: false
      querylog:
        dir_path: ""
        ignored: []
        interval: 2160h
        size_memory: 1000
        enabled: true
        file_enabled: true
      statistics:
        dir_path: ""
        ignored: []
        interval: 24h
        enabled: true
      filters:
        - enabled: true
          url: https://adguardteam.github.io/HostlistsRegistry/assets/filter_1.txt
          name: AdGuard DNS filter
          id: 1
        - enabled: false
          url: https://adguardteam.github.io/HostlistsRegistry/assets/filter_2.txt
          name: AdAway Default Blocklist
          id: 2
      whitelist_filters: []
      user_rules: []
      dhcp:
        enabled: false
        interface_name: ""
        local_domain_name: lan
        dhcpv4:
          gateway_ip: ""
          subnet_mask: ""
          range_start: ""
          range_end: ""
          lease_duration: 86400
          icmp_timeout_msec: 1000
          options: []
        dhcpv6:
          range_start: ""
          lease_duration: 86400
          ra_slaac_only: false
          ra_allow_slaac: false
      filtering:
        blocking_ipv4: ""
        blocking_ipv6: ""
        blocked_services:
          schedule:
            time_zone: Europe/Zurich
          ids: []
        protection_disabled_until: null
        safe_search:
          enabled: false
          bing: true
          duckduckgo: true
          ecosia: true
          google: true
          pixabay: true
          yandex: true
          youtube: true
        blocking_mode: default
        parental_block_host: family-block.dns.adguard.com
        safebrowsing_block_host: standard-block.dns.adguard.com
        rewrites: []
        safe_fs_patterns:
          - /opt/adguardhome/work/userfilters/*
        safebrowsing_cache_size: 1048576
        safesearch_cache_size: 1048576
        parental_cache_size: 1048576
        cache_time: 30
        filters_update_interval: 24
        blocked_response_ttl: 10
        filtering_enabled: true
        parental_enabled: false
        safebrowsing_enabled: false
        protection_enabled: true
      clients:
        runtime_sources:
          whois: true
          arp: true
          rdns: true
          dhcp: true
          hosts: true
        persistent: []
      log:
        enabled: true
        file: ""
        max_backups: 0
        max_size: 100
        max_age: 3
        compress: false
        local_time: false
        verbose: false
      os:
        group: ""
        user: ""
        rlimit_nofile: 0
      schema_version: 29

d. My complete Caddy config:

{
	acme_dns cloudflare REDACTED
	grace_period 10s
	storage postgres {
		connection_string postgres://caddy:REDACTED@caddy-db:5432/caddy?sslmode=disable
	}
}
adguard.mydomain.com {
	route {
		reverse_proxy /dns-query adguard:443
		reverse_proxy /outpost.goauthentik.io/* authentik-server:9000
		forward_auth authentik-server:9000 {
			copy_headers X-Authentik-Username X-Authentik-Groups X-Authentik-Entitlements X-Authentik-Email X-Authentik-Name X-Authentik-Uid X-Authentik-Jwt X-Authentik-Meta-Jwks X-Authentik-Meta-Outpost X-Authentik-Meta-Provider X-Authentik-Meta-App X-Authentik-Meta-Version
			uri /outpost.goauthentik.io/auth/caddy
		}
		reverse_proxy adguard:3000
	}
}
authbeelink.mydomain.com {
	reverse_proxy authentik-server:9000
}
giteabeelink.mydomain.com {
	reverse_proxy gitea:3000
}
portainerbeelink.mydomain.com {
	reverse_proxy portainer:9000
}

5. Links to relevant resources:

I see adguard is listening on port 443. Is it listening with HTTPS? If yes, prefix the upstream address here with https:// and confirm the host name on the certificate supplied by upstream.

Thanks for your answer.
I have set force_https: false and allow_unencrypted_doh: true in my Adguard config. According to the documentation, this should mean that it listens on both HTTP and HTTPS. I have tried with both reverse_proxy /dns-query adguard:443, reverse_proxy /dns-query http://adguard:443 and reverse_proxy /dns-query https://adguard:443.
I always get the same results.

But not on the same port. Anyways, port 443 is typically used for HTTPS, and it’d be wild for them to use it for plain HTTP (no HTTPS).

In AdGuard Home, set this allow_unencrypted_doh: true under tls, then proxy requests for /dns-query to adguard:80.

Thanks for your answer.
I understand, thanks for the explaination.
I have just tried with reverse_proxy /dns-query http://adguard:80 but I am still having the same results.

EDIT: I was wrong. The browser still returns a Cloudflare Bad Gateway error like before, but I am now having an error in Caddy:

ERR ts=1739315918.0595708 logger=http.log.error msg=dial tcp 172.18.0.3:80: connect: connection refused request={"remote_ip":"172.19.0.2","remote_port":"40432","client_ip":"172.19.0.2","proto":"HTTP/1.1","method":"GET","host":"adguard.mydomain.com","uri":"/dns-query","headers":{"Sec-Fetch-Dest":["document"],"X-Forwarded-For":["REDACTED"],"Cf-Ray":["91080f27daf5bb15-ZRH"],"Cookie":["REDACTED"],"Cf-Warp-Tag-Id":["9fc525a7-14d7-4784-bc27-ba1036de88e5"],"Sec-Fetch-Mode":["navigate"],"Cf-Connecting-Ip":["2a02:aa13:8241:1300:431a:8552:330:6f2"],"Cf-Visitor":["{\"scheme\":\"https\"}"],"Sec-Fetch-Site":["none"],"Accept-Encoding":["gzip, br"],"Priority":["u=0, i"],"Upgrade-Insecure-Requests":["1"],"Sec-Fetch-User":["?1"],"Sec-Gpc":["1"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:128.0) Gecko/20100101 Firefox/128.0"],"Connection":["keep-alive"],"X-Forwarded-Proto":["https"],"Accept":["text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"],"Cf-Ipcountry":["CH"],"Dnt":["1"],"Cdn-Loop":["cloudflare; loops=1"],"Accept-Language":["fr-FR,fr;q=0.8,en-US;q=0.5,en;q=0.3"]},"tls":{"resumed":false,"version":772,"cipher_suite":4865,"proto":"","server_name":"adguard.mydomain.com"}} duration=0.001367074 status=502 err_id=a28a6qpxx err_trace=reverseproxy.statusError (reverseproxy.go:1373)

I am now sure what to conclude from that, tought

Can you try the port 3000? If it still doesn’t work with that, I’m afraid I’m out of ideas. This is ultimately an AdGuard Home question of how do they expose plain HTTP for proxied DNS requests.

I have edited my previous message, I have an error in Caddy now. I am not sure what to conclude from it thought. Meanwhile, I will also try with port 3000.

EDIT : port 3000 does not work, but does not produce an error in caddy like http://adguard:80 does. So I think http://adguard:80 is the right way.

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.