Unbound with DoT and DoH behind Caddy as reverse proxy

1. Caddy version (caddy version):

Caddy v2.4.5

2. How I run Caddy:

I run the official (Alpine) Docker image of Caddy as a reverse proxy to serve web servers or other services on a VPS in the cloud.

a. System environment:

Docker v20.10.9

3. What I want to achieve:

Unbound is a validating, recursive and caching DNS resolver which supports encrypted DNS requests via DNS-over-HTTPS (DoH, port 443) and DNS-over-TLS (DoT, port 853).

My objective is to setup Unbound behind Caddy to resolve downstream encrypted DNS queries coming from my LAN or my mobile phone.

The drawing below illustrates the architecture:

4. My problem:

I am not too sure about the TLS part but it would be very convenient if Caddy could manage the certificate and the key.

Unbound needs to be supplied with the private key for the TLS session (tls-service-key) and the public certificate (tls-service-pem).

5. Solutions?

  1. Feed Unbound with Caddy’s .key and .crt files (of the related domain) located in the Data directory. The only constraint would be to restart Unbound every time the certificates change but a cron job should probably do the job

  2. Leverage local HTTPS (Caddy’s own certificate authority) and feed the certificate to Unbound same as solution 1

Any help would be greatly appreciated so that I understand what I should do regarding this TLS topic.

Thank you!

There’s a few tricky bits to this:

  • Caddy, with the standard installation, ships only an HTTP server. This means it could not terminate DoT because that doesn’t use HTTP. The workaround for this would be to use GitHub - mholt/caddy-l4: Layer 4 (TCP/UDP) app for Caddy instead which could allow you to terminate TLS for non-HTTP protocols like DoT. But YMMV, I’ve not heard of anyone trying this yet. Keep in mind caddy-l4 only support JSON configuration at this time, no Caddyfile support yet.

  • DoH is HTTP as far as I understand, so it should be pretty simple to just use the standard reverse_proxy module to proxy HTTP to the upstream. Caddy can terminate TLS in this case, but I don’t know if Unbound actually requires you to use HTTPS or if it can simply use HTTP, trusting that a proxy is terminating. Either way, you can configure Unbound with a self-signed certificate and just get Caddy to trust it – Unbound doesn’t need to use a publicly trusted cert, because only Caddy would see it anyways.

DoH is HTTP as far as I understand, so it should be pretty simple to just use the standard reverse_proxy module to proxy HTTP to the upstream.

Yes, that’s right. In my configuration with Unbound on the same VPS as Caddy, it would work, I won´t even need to setup DoH in Unbound. On the other hand, if Unbound is on another VPS and I want to keep the E2E encryption, the self certificate solution is probably the way to go.

The workaround for this would be to use GitHub - mholt/caddy-l4: Layer 4 (TCP/UDP) app for Caddy instead which could allow you to terminate TLS for non-HTTP protocols like DoT.

I will dockerize it and give it a try.

Thanks a lot @francislavoie !

Updated diagram with the proposed solution:

FYI, if you plan on running 2 instances of Caddy (which is totally valid in this situation I think), then if you share the same storage for both instances of Caddy, then one can initiate certificate issuance and the other can solve it. So that means the “http proxy” instance can complete the issuance for a certificate that the “tcp proxy” instance will need/use.

1 Like

Well, actually, it is not exactly that: Unbound does not understand HTTP, only HTTPS (DoH), UDP and TCP. So I don’t have much choice, I have to configure Unbound with a self-signed certificate and get Caddy to trust it.

FYI since it’s only accessible via Caddy, and its inside your own network, you can forgo trust and use the tls_insecure_skip_verify option of reverse_proxy's transport http. Obviously this is less than ideal, but it can save you some effort in making sure Caddy always trusts the self signed cert.

:speak_no_evil:

Thanks for the tips! I’ll it use if I can’t do it properly… but I am a perfectionist so I will try the hard way :slight_smile:

Please share your configs if you get this to work. It’s quite an interesting idea.

Yes, will do!