DNS-over-TLS server certificate

Hi, first I’m not sure if my question makes sense because I lack knowledge on that topic. My “problem” may not have anything to do with Caddy, but I ask anyway. I’m trying to setup a private DNS server with DoT support (DNS over TLS).
So I configured a blocky instance (via docker). It works as expected, from my clients machines dns queries are resolved etc. In the other hand, I’m using Caddy as a reverse proxy server (installed also via docker, present on the same network as blocky service). And I got the idea to setup DoT.

Now I need to somehow provision a certificate for the dns TLS needs. Does it make sense to use the caddy instance to handle that?
In a naive approach, I came up with this in my Caddyfile (simplified), trying to somehow reverse proxy a specific subdomain for DoT dot.domain.fr to blocky server on the TLS port but without any success

*.domain.fr, domain.fr {
    tls {
        dns cloudflare {env.CLOUDFLARE_API_TOKEN} # Im using CloudFlare as a DNS provider for my domain
    }

    # blocky DoT, not working
    @dot {
        host dot.domain.fr
        import privateSubnets
    }
    handle @dot {
        reverse_proxy blocky:853 # blocky tls port is set to 853 in its config
    }

    # blocky DoH, working fine
    @doh {
        host dns.domain.fr
        import privateSubnets
    }
    handle @doh {
        reverse_proxy blocky:1234
    }

But when I try to use the dns server from my client machines with systemd-resolved for instance (with DNSOverTLS=yes). Using 11.22.33.123#dot.domain.fr as dns address (11.22.33.123 being the public IP of my server), it fails to resolve any dns queries.
Any help would be really appreciated!

caddy version

v2.6.4 h1:2hwYqiRwk1tf3VruhMpLcYTg+11fCdr8S3jhNAdnPy8=

system environment

Arch Linux, Docker

First of all,

This version is old. Please upgrade to v2.7.4

Yes, I have that setup for myself, so it definitely makes sense and is possible. I have CoreDNS running in the backend with Caddy managing the TLS on the front for both DoH and DoT. The key information you need to keep in mind is that vanilla Caddy is an HTTP server, with other types being plugins. For DoH, vanilla Caddy can handle this use case without extra modules.

For DoT, note that TLS is not wrapping an HTTP protocol, rather plain TCP without the HTTP semantics. To handle such connection, you need to use the layer4 module. You can build Caddy with the layer4 module using xcaddy or the download page, just like you did for the Cloudflare module. The module does not support Caddyfile config format yet, so you’ll have to use the JSON format for configuration. You can start by running caddy adapt for your current Caddyfile, then edit the parts you need. So, first remove the DoT parts you currently have in your Caddyfile, adapt it, then add the configuration for the layer4 module and DoT. One neat trick is to have the same domain working for DoT and DoH.

Anyways, the final configuration will look something like this:

{
	"apps": {
		"http": {
			"servers": {
				"srv0": {
					"listen": [
						":443"
					],
					"routes": [
						{
							"match": [
								{
									"host": [
										"dns.domain.fr"
									]
								}
							],
							"handle": [
								{
									"handler": "subroute",
									"routes": [
										{
											"handle": [
												{
													"handler": "reverse_proxy",
													"upstreams": [
														{
															"dial": "blocky:1234"
														}
													]
												}
											]
										}
									]
								}
							],
							"terminal": true
						}
					]
				}
			}
		},
		"layer4": {
			"servers": {
				"dot": {
					"listen": [":853"],
					"routes": [
						{
							"match": [
								{
									"tls": {
										"sni": ["dns.domain.fr"]
									}
								}
							],
							"handle": [
								{
									"handler": "tls"
								},
								{
									"handler": "proxy",
									"upstreams": [
										{"dial": ["blocky:853"]}
									]
								}
							]
						}
					]
				}
			}
		},
		"tls": {
			"automation": {
				"policies": [
					{
						"subjects": [
							"dns.domain.fr"
						],
						"issuers": [
							{
								"email": "your-email-address@example.com",
								"module": "acme"
							},
							{
								"email": "your-email-address@example.com",
								"module": "zerossl"
							}
						]
					}
				]
			}
		}
	}
}


This is roughly what I already have for my personal use, except for the additional sites I have. Yours will have parts for the DNS challenge for the wildcard cert.

2 Likes

@Mohammed90 thanks you for the detailed explanation and the solution!

Yes exactly that! This is the explanation I was looking for to better understand the problem. Make sense now. And glad to see there is a plugin to handle other “protocols” beside HTTP. Caddy flexibility and extra features will never cease to amaze me! (thx for its plugin system)

But, the downside of this is I have to switch to JSON config to use the layer4 module. And I’m not really ready for this, I don’t like JSON config format of Caddy. I tried adapt command, and the generated json config once prettified, well, scared me a bit xD. Compared to Caddyfile, there is a lot of “config fragment” duplications, and the line number of the config (prettified) is multiplied by ~5. But anyway this is not really related to the topic here.

Yes thx for the info, as I build my own caddy docker image using xcaddy from the builder image sometimes I’m getting outdated with the upstream version

2 Likes