Using Caddy to setup DoH over pihole-FTL

1. The problem I’m having:

I am trying to setup Caddy-l4 to be used as a DoH (have pihole-ftl running on port 53).

2. Error messages and/or full log output:

loading handler modules: position 0: loading module 'reverse_proxy': provision http.handlers.reverse_proxy: loading transport: loading module 'dns': unknown module: http.reverse_proxy.transport.dns

3. Caddy version:

v2.8.4 h1:q3pe0wpBj1OcHFZ3n/1nl4V4bxBrYoSoab7rL9BMYNk

4. How I installed and ran Caddy:

a. System environment:

Linux vps-abc 6.1.0-23-cloud-amd64 #1 SMP PREEMPT_DYNAMIC Debian 6.1.99-1 (2024-07-15) x86_64 GNU/Linux

b. Command:

go install github.com/caddyserver/xcaddy/cmd/xcaddy@latest
xcaddy build --with github.com/mholt/caddy-l4
sudo caddy run --config caddy.json

c. My complete Caddy config:

{
  "admin": {
    "disabled": true
  },
  "logging": {
    "logs": {
      "default": {
        "level": "ERROR",
        "writer": {
          "output": "file",
          "filename": "/var/log/caddy/caddy.log",
          "roll_size_mb": 10,
          "roll_keep_days": 3
        }
      }
    }
  },
  "apps": {
    "http": {
      "servers": {
        "srv0": {
          "listen": [
            ":443"
          ],
          "routes": [
            {
              "match": [
                {
                  "host": [
                    "abc.domain.com"
                  ]
                }
              ],
              "handle": [
                {
                  "handler": "subroute",
                  "routes": [
                    {
                      "handle": [
                        {
                          "handler": "vars",
                          "root": ""
                        }
                      ]
                    },
                    {
                      "match": [
                        {
                          "path": [
                            "/admin*"
                          ]
                        }
                      ],
                      "handle": [
                        {
                          "handler": "reverse_proxy",
                          "upstreams": [
                            {
                              "dial": "127.0.0.1:8080"
                            }
                          ]
                        }
                      ]
                    },
                    {
                      "match": [
                        {
                          "path": [
                            "/dns-doh-query"
                          ]
                        }
                      ],
                      "handle": [
                        {
                          "handler": "reverse_proxy",
                          "upstreams": [
                            {
                              "dial": "127.0.0.1:53"
                            }
                          ],
                          "transport": {
                            "protocol": "dns"
                          }
                        }
                      ]
                    },
                    {
                      "handle": [
                        {
                          "handler": "static_response",
                          "status_code": 404
                        }
                      ]
                    }
                  ]
                }
              ],
              "terminal": true
            }
          ]
        }
      }
    },
    "tls": {
      "automation": {
        "policies": [
          {
            "subjects": [
              "abc.domain.com"
            ]
          }
        ]
      }
    }
  }
}

5. Links to relevant resources:

That’s not a thing. I’m not sure where you got the idea that would work. The error is accurate, there’s no such “dns” transport module for reverse_proxy.

If you’re trying to proxy DoH (aka DNS over HTTPS), then you need to use the http protocol (which is the default anyway).

You’re not using caddy-l4 here, this is the http app you’re configuring, not the layer4 app.

1 Like

You are right that its not layer4. Could you please point me to some sample/config that I could use to proxy DoH which is serviced by pihole-ftl running at port 53. Other samples that I saw are on adguard or other services that have some

On Nginx I used njs (nginx javascript module) for dns and it worked fine. Ref: Using NGINX as a DoT or DoH Gateway

Liked caddy’s way of generating and updating certificates just based on DNS entries for host hence trying to figure DoH on caddy

Try just removing the protocol part from your config, i.e. using the default http protocol. Show the behaviour with that.

1 Like

Hi @francislavoie thanks for your response. Was held up with few other things and hence could not apply your recommendations.

After removing the transport section from the json, DoH is still not working with the above configuration.
Pasting the configuration again for the sake of completeness

{
  "admin": {
    "disabled": true
  },
  "logging": {
    "logs": {
      "default": {
        "level": "ERROR",
        "writer": {
          "output": "file",
          "filename": "/var/log/caddy/caddy.log",
          "roll_size_mb": 10,
          "roll_keep_days": 3
        }
      }
    }
  },
  "apps": {
    "http": {
      "servers": {
        "srv0": {
          "listen": [":443"],
          "routes": [
            {
              "match": [
                {
                  "host": ["abc.domain.com"]
                }
              ],
              "handle": [
                {
                  "handler": "subroute",
                  "routes": [
                    {
                      "handle": [
                        {
                          "handler": "vars",
                          "root": ""
                        }
                      ]
                    },
                    {
                      "match": [
                        {
                          "path": ["/admin*"]
                        }
                      ],
                      "handle": [
                        {
                          "handler": "reverse_proxy",
                          "upstreams": [
                            {
                              "dial": "127.0.0.1:8080"
                            }
                          ]
                        }
                      ]
                    },
                    {
                      "match": [
                        {
                          "path": ["/doh-dns-query"]
                        }
                      ],
                      "handle": [
                        {
                          "handler": "reverse_proxy",
                          "upstreams": [
                            {
                              "dial": "127.0.0.1:53"
                            }
                          ]
                        }
                      ]
                    },
                    {
                      "handle": [
                        {
                          "handler": "static_response",
                          "status_code": 404
                        }
                      ]
                    }
                  ]
                }
              ],
              "terminal": true
            }
          ]
        }
      }
    },
    "tls": {
      "automation": {
        "policies": [
          {
            "subjects": ["abc.domain.com"]
          }
        ]
      }
    }
  }
}

Had configured caddy to emit DEBUG logs. Below are some logs from the log file

{"level":"debug","ts":1730652364.4467366,"logger":"http.handlers.reverse_proxy","msg":"upstream roundtrip","upstream":"127.0.0.1:53","duration":0.620585975,"request":{"remote_ip":"10.150.133.2","remote_port":"55934","client_ip":"10.150.133.2","proto":"HTTP/2.0","method":"POST","host":"abc.domain.com","uri":"/doh-dns-query","headers":{"X-Forwarded-For":["10.150.133.2"],"X-Forwarded-Host":["abc.domain.com"],"Accept-Language":["*"],"User-Agent":["Chrome"],"Content-Type":["application/dns-message"],"Priority":["i"],"X-Forwarded-Proto":["https"],"Content-Length":["128"],"Accept":["application/dns-message"],"Accept-Encoding":["identity"]},"tls":{"resumed":false,"version":772,"cipher_suite":4865,"proto":"h2","server_name":"abc.domain.com"}},"error":"context canceled"}
{"level":"debug","ts":1730652406.8176937,"logger":"http.handlers.reverse_proxy","msg":"upstream roundtrip","upstream":"127.0.0.1:53","duration":1.994096531,"request":{"remote_ip":"10.150.133.2","remote_port":"55980","client_ip":"10.150.133.2","proto":"HTTP/2.0","method":"POST","host":"abc.domain.com","uri":"/doh-dns-query","headers":{"Accept-Language":["*"],"Accept-Encoding":["identity"],"Content-Type":["application/dns-message"],"X-Forwarded-For":["10.150.133.2"],"X-Forwarded-Proto":["https"],"Content-Length":["128"],"Accept":["application/dns-message"],"User-Agent":["Chrome"],"Priority":["i"],"X-Forwarded-Host":["abc.domain.com"]},"tls":{"resumed":false,"version":772,"cipher_suite":4865,"proto":"h2","server_name":"abc.domain.com"}},"error":"context canceled"}
{"level":"debug","ts":1730652408.3910503,"logger":"http.handlers.reverse_proxy","msg":"upstream roundtrip","upstream":"127.0.0.1:53","duration":1.019060835,"request":{"remote_ip":"10.150.133.2","remote_port":"55984","client_ip":"10.150.133.2","proto":"HTTP/2.0","method":"POST","host":"abc.domain.com","uri":"/doh-dns-query","headers":{"Content-Type":["application/dns-message"],"X-Forwarded-Host":["abc.domain.com"],"Content-Length":["128"],"Accept-Language":["*"],"User-Agent":["Chrome"],"Accept-Encoding":["identity"],"Accept":["application/dns-message"],"Priority":["i"],"X-Forwarded-For":["10.150.133.2"],"X-Forwarded-Proto":["https"]},"tls":{"resumed":false,"version":772,"cipher_suite":4865,"proto":"h2","server_name":"abc.domain.com"}},"error":"context canceled"}
{"level":"debug","ts":1730652413.1081638,"logger":"http.handlers.reverse_proxy","msg":"upstream roundtrip","upstream":"127.0.0.1:53","duration":2.095746432,"request":{"remote_ip":"10.150.133.2","remote_port":"55981","client_ip":"10.150.133.2","proto":"HTTP/2.0","method":"POST","host":"abc.domain.com","uri":"/doh-dns-query","headers":{"X-Forwarded-For":["10.150.133.2"],"X-Forwarded-Proto":["https"],"Priority":["u=0, i"],"Content-Length":["128"],"Accept":["application/dns-message"],"Accept-Language":["*"],"User-Agent":["Chrome"],"Accept-Encoding":["identity"],"Content-Type":["application/dns-message"],"X-Forwarded-Host":["abc.domain.com"]},"tls":{"resumed":false,"version":772,"cipher_suite":4865,"proto":"h2","server_name":"abc.domain.com"}},"error":"context canceled"}
{"level":"debug","ts":1730652413.1082864,"logger":"http.handlers.reverse_proxy","msg":"upstream roundtrip","upstream":"127.0.0.1:53","duration":2.096128941,"request":{"remote_ip":"10.150.133.2","remote_port":"55981","client_ip":"10.150.133.2","proto":"HTTP/2.0","method":"POST","host":"abc.domain.com","uri":"/doh-dns-query","headers":{"Content-Type":["application/dns-message"],"Priority":["u=0, i"],"User-Agent":["Chrome"],"X-Forwarded-Proto":["https"],"X-Forwarded-Host":["abc.domain.com"],"Accept-Encoding":["identity"],"Accept":["application/dns-message"],"Accept-Language":["*"],"X-Forwarded-For":["10.150.133.2"],"Content-Length":["128"]},"tls":{"resumed":false,"version":772,"cipher_suite":4865,"proto":"h2","server_name":"abc.domain.com"}},"error":"context canceled"}
{"level":"debug","ts":1730652415.7297163,"logger":"http.handlers.reverse_proxy","msg":"upstream roundtrip","upstream":"127.0.0.1:53","duration":2.06867819,"request":{"remote_ip":"10.150.133.2","remote_port":"55981","client_ip":"10.150.133.2","proto":"HTTP/2.0","method":"POST","host":"abc.domain.com","uri":"/doh-dns-query","headers":{"User-Agent":["Chrome"],"Content-Type":["application/dns-message"],"X-Forwarded-For":["10.150.133.2"],"X-Forwarded-Host":["abc.domain.com"],"Accept-Encoding":["identity"],"Priority":["u=0, i"],"Content-Length":["128"],"Accept":["application/dns-message"],"Accept-Language":["*"],"X-Forwarded-Proto":["https"]},"tls":{"resumed":false,"version":772,"cipher_suite":4865,"proto":"h2","server_name":"abc.domain.com"}},"error":"context canceled"}
{"level":"debug","ts":1730652415.729837,"logger":"http.handlers.reverse_proxy","msg":"upstream roundtrip","upstream":"127.0.0.1:53","duration":2.069042816,"request":{"remote_ip":"10.150.133.2","remote_port":"55981","client_ip":"10.150.133.2","proto":"HTTP/2.0","method":"POST","host":"abc.domain.com","uri":"/doh-dns-query","headers":{"Content-Length":["128"],"Accept-Language":["*"],"Content-Type":["application/dns-message"],"Priority":["u=0, i"],"X-Forwarded-For":["10.150.133.2"],"X-Forwarded-Proto":["https"],"Accept":["application/dns-message"],"User-Agent":["Chrome"],"Accept-Encoding":["identity"],"X-Forwarded-Host":["abc.domain.com"]},"tls":{"resumed":false,"version":772,"cipher_suite":4865,"proto":"h2","server_name":"abc.domain.com"}},"error":"context canceled"}
{"level":"debug","ts":1730652415.7298772,"logger":"http.handlers.reverse_proxy","msg":"upstream roundtrip","upstream":"127.0.0.1:53","duration":2.068279482,"request":{"remote_ip":"10.150.133.2","remote_port":"55981","client_ip":"10.150.133.2","proto":"HTTP/2.0","method":"POST","host":"abc.domain.com","uri":"/doh-dns-query","headers":{"Accept-Encoding":["identity"],"Content-Type":["application/dns-message"],"Priority":["u=0, i"],"User-Agent":["Chrome"],"Content-Length":["128"],"Accept":["application/dns-message"],"Accept-Language":["*"],"X-Forwarded-For":["10.150.133.2"],"X-Forwarded-Proto":["https"],"X-Forwarded-Host":["abc.domain.com"]},"tls":{"resumed":false,"version":772,"cipher_suite":4865,"proto":"h2","server_name":"abc.domain.com"}},"error":"context canceled"}
{"level":"debug","ts":1730652415.730409,"logger":"http.handlers.reverse_proxy","msg":"upstream roundtrip","upstream":"127.0.0.1:53","duration":2.069064629,"request":{"remote_ip":"10.150.133.2","remote_port":"55981","client_ip":"10.150.133.2","proto":"HTTP/2.0","method":"POST","host":"abc.domain.com","uri":"/doh-dns-query","headers":{"Content-Length":["128"],"User-Agent":["Chrome"],"Accept-Encoding":["identity"],"X-Forwarded-Proto":["https"],"Content-Type":["application/dns-message"],"Accept":["application/dns-message"],"Accept-Language":["*"],"X-Forwarded-For":["10.150.133.2"],"X-Forwarded-Host":["abc.domain.com"],"Priority":["u=0, i"]},"tls":{"resumed":false,"version":772,"cipher_suite":4865,"proto":"h2","server_name":"abc.domain.com"}},"error":"context canceled"}

Also tried the dig command locally to ensure the dns is listening on port 53.

dig @127.0.0.1 -p 53 yahoo.com

; <<>> DiG 9.18.28-1~deb12u2-Debian <<>> @127.0.0.1 -p 53 yahoo.com
; (1 server found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 3073
;; flags: qr rd ra; QUERY: 1, ANSWER: 6, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 1232
; COOKIE: 4507ce7c2092d331 (echoed)
;; QUESTION SECTION:
;yahoo.com.			IN	A

;; ANSWER SECTION:
yahoo.com.		1274	IN	A	98.137.11.163
yahoo.com.		1274	IN	A	74.6.231.20
yahoo.com.		1274	IN	A	98.137.11.164
yahoo.com.		1274	IN	A	74.6.231.21
yahoo.com.		1274	IN	A	74.6.143.26
yahoo.com.		1274	IN	A	74.6.143.25

;; Query time: 8 msec
;; SERVER: 127.0.0.1#53(127.0.0.1) (UDP)
;; WHEN: Sun Nov 03 17:24:01 UTC 2024
;; MSG SIZE  rcvd: 200

Are you sure DoH is served on port 53 from your DNS server? Port 53 is for regular plaintext DNS traffic, not DoH. When you use dig you’re not using DoH, you’re using regular DNS. Typically DoH is over port 443. You can use the +https flag in dig to use DoH I think, if you have a conpatible version of dig.

1 Like

Hi @francislavoie thanks for the response.
I have DNS running on port 53, its pihole-ftl
The idea is to have caddy take in DoH requests and serve them via DNS running on port

I have been able to achieve this on nginx. Reference link of Nginx Using NGINX as a DoT or DoH Gateway

Right, but DNS is not the same protocol as DoH. DNS runs on port 53 over UDP, and DoH is an HTTP wrapper over DNS which will not use UDP, it will use a different port.

That example config has it proxying to 127.0.0.1:8053 for DoH, not to port 53.

1 Like

Also note in the section “A Simple DoH-DNS Gateway” they’re using a plugin to convert DoH payload into regular DNS. Note how they’re proxying from the HTTPS (443) to DoH (plain HTTP) (8053) then converting the DoH payload to be proxied to DNS on 53.

Similarly, the same can be implemented as Caddy plugin. I prototyped it to prove it works, but I don’t think it’s the best approach so I’m not publishing it.

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