Automation, caddy-l4 and "dummy" HTTPS server

  • Foreword: I am aware that in the config I am showing below, the domain name was replaced. I did read the warning about this, but as my question is about suggestions in Caddyfile samples, not about a bug in some actual use of Caddy, there are no (for the moment) actual domain names involved.

1. The problem I’m having:

I am using Caddy and layer4 to handle Let’s Encrypt certificate automation and TLS termination then forward cleartext traffic to a local server. The commonly suggested solution is a Caddyfile like this one:

{
	layer4 {
		:1234 {
			@example_com_route tls sni example.com

			route @example_com_route {
				tls
				proxy 127.0.0.1:4567
			}
		}
	}
}

example.com {
	respond "OK" 200
}

The last part is the one which causes Caddy to provision the LE certificate. It does so by declaring a “dummy” HTTPS server, which I do not need otherwise.

I would like to have LE automation for the domains mentioned in the layer4 configuration but without the “dummy” HTTPS server.

2. Error messages and/or full log output:

N/A.

3. Caddy version:

# caddy version
v2.10.0 h1:fonubSaQKF1YANl8TXqGcn4IbIRUDdfAkpcsfI/vX5U=
# _

4. How I installed and ran Caddy:

Using Debian 13 (Trixie) on arm64 hardware, I ran the following commands:

# extrepo enable caddyserver
# apt update
# apt install caddy

However this package did not include layer4 support, so I downloaded the (arm64, again) binary from Download Caddy , selecting mholt/caddy-l4 as the only extra feature, and overwrote /usr/bin/caddy with the downloaded binary.

a. System environment:

Debian 13 Trixie up to date, Caddy package from extrepo caddyserver, with /usr/bin/caddy replaced (see above), all running on actual (arm64) hardware.

b. Command:

Caddy is enabled and started by systemd. The only command I use is to reload the configuration:

# /usr/bin/caddy reload --config /etc/caddy/Caddyfile

c. Service/unit/compose file:

Provided by the Debian 13 package

# caddy.service
#
# For using Caddy with a config file.
#
# Make sure the ExecStart and ExecReload commands are correct
# for your installation.
#
# See https://caddyserver.com/docs/install for instructions.
#
# WARNING: This service does not use the --resume flag, so if you
# use the API to make changes, they will be overwritten by the
# Caddyfile next time the service is restarted. If you intend to
# use Caddy's API to configure it, add the --resume flag to the
# `caddy run` command or use the caddy-api.service file instead.

[Unit]
Description=Caddy
Documentation=https://caddyserver.com/docs/
After=network.target network-online.target
Requires=network-online.target

[Service]
Type=notify
User=caddy
Group=caddy
ExecStart=/usr/bin/caddy run --environ --config /etc/caddy/Caddyfile
ExecReload=/usr/bin/caddy reload --config /etc/caddy/Caddyfile --force
TimeoutStopSec=5s
LimitNOFILE=1048576
PrivateTmp=true
ProtectSystem=full
AmbientCapabilities=CAP_NET_ADMIN CAP_NET_BIND_SERVICE

[Install]
WantedBy=multi-user.target

d. My complete Caddy config:

{
	layer4 {
		:1234 {
			@example_com_route tls sni example.com

			route @example_com_route {
				tls
				proxy 127.0.0.1:4567
			}
		}
	}
}

example.com {
	respond "OK" 200
}

5. Links to relevant resources:

Thanks for your help!

Albert

The “dummy” HTTP/S server is needed for the HTTP-01 and/or TLS-ALPN-01 challenges. One requires port 80, the other port 443. Hence the need for a simple web server to handle those.

If you’d prefer not to run a web server just for that, your options are to either provide your own certificate (obtained externally) or use the DNS-01 challenge instead. Check if there’s a Caddy module available for your DNS provider to automate that process.

More details on the different challenge types:

Adding some additional useful info:

Thanks @timelordx.

It appears I haven’t clearly expressed my question.

I was not asking how “dummy” websites help layer4 sites obtain certificate, nor was I asking how ACME works from a client perspective.

What I am asking is:

For Caddy, satisfying the ACME TLS-ALPN-01 challenge requires listening on TCP port 443 on the IP to which the requested CN and SANs resolve, handling TLS and more specifically the acme-tls/1 ALPN protocol, and serving content under the /.well-known/acme-challenge/ path. This server does not need to serve any other content, nor to respond on HTTPS without ALPN, nor on any ALPN HTTP protocol.

This makes sense as a certificate obtained from Let’s Encrypt (even through TLS-ALPN-01) will almost certainly be used for TLS, but may never be used for HTTPS.

For instance, my use case is precisely about serving not HTTPS but IMAP, Submission, IRC, etc. over TLS.

Therefore, I have no use for additionally serving HTTPS on the domains mentioned in my layer4 servers, and thus, I’d like them not to exist.

Also, the same single acme-tls/1 server (with one valid certificate) can respond to as many TLS-ALPN-01 certificate requests as one wants (as long as all FQDNs point to the server’s IP). So, if a Caddyfile contains HTTPS server declarations, then Caddy will provide an acme-tls/1 server, which Caddy could also use to provision certificates for layer 4 servers too.

So, to rephrase my question: what prevents Caddy from directly provisioning a certificate for each domain mentioned in a layer4 server declaration, just like it does for HTTPS server declarations?

Albert.

I’m going to repeat myself:

  • HTTP-01 → port 80
  • TLS-ALPN-01 → port 443
  • A dummy HTTPS web server → port 443

So, TLS-ALPN-01 basically is a dummy HTTPS web server.

That’s exactly what this does:

example.com {
	respond "OK" 200
}

The Caddy-L4 module doesn’t handle certificate management. It relies on Caddy for that. From the Caddy-L4 docs about the tls handler:

This handler is one of the most frequently used handlers this package includes, but it’s also known for having caused some misunderstanding. It’s crucial to keep in mind that the handler does no SSL certificates management, i.e. it can’t obtain or generate them - you have to do it by means of configuring Caddy. The only thing the handler does is TLS termination using a set of SSL certificates Caddy already has and applying a set of connection policies.

If you don’t want to run a dummy HTTPS server, you can configure a global acme_dns for your DNS provider and use the DNS-01 challenge to get a TLS cert for Caddy. That way, you won’t need a dummy HTTPS server at all:

Or, if none of that suits your needs, you can always write your own Caddy module to do exactly what you want, your way.

You could also try something like this:

http://example.com {
	@not_acme {
		not path /.well-known/acme-challenge/*
	}
	abort @not_acme
}

This allows you to use the HTTP-01 challenge without serving anything else. Caddy just drops any request that isn’t part of the ACME challenge.

You might also experiment with this:

https://example.com {
	abort
}

to see if TLS-ALPN-01 works in this setup. Or try:

example.com {
	@not_acme {
		not path /.well-known/acme-challenge/*
	}
	abort @not_acme
}

to potentially support both HTTP-01 and TLS-ALPN-01.

I haven’t tested any of this myself though. I use DNS-01 for my own setup. But give it try, it might do what you’re after.

The dummy HTTPS server technique is not exactly what the TLS-ALPN-01 challenge describes, as the dummy server technique creates an HTTPS server (albeit a trivial one) which the TLS-ALPN-01 challenge does not mandate nor require. Correct?

Albert.

At this point, you have all the information you need for your task. Everything else is just walking in circles.

Thanks for your input.

Don’t get me wrong: you gave perfectly valid answers to the question “how do I get certification automation for a layer 4 server domain”, including an alternate formulation for the “dummy” HTTPS server which will indeed prevent it from doing anything else than enabling TLS-ALPN-01. I should have expressed my appreciation for this, and I apologize for not having done so.

However, I hope you understand that my question was “how do I get certificate automation for a layer 4 server without declaring a HTTPS server with the same domain at all”, which is different. From your answers, I gather that there is no provision for this in caddy-l4 and/or Caddy itself right now and, as far as I could see from the caddy-l4 github, that no one is working on such a provision so far.

Thanks again for your time and patience.

[EDIT] Right after I posted this, I checked on the github issue I’d opened on caddy-l4. Based on this reply, it should be feasible and a PR would be welcome.