Layer4 reverse proxying & SSL termination

1. The problem I’m having:

I have setup caddy as a TCP-level reverse proxy in front of a valkey server.

I would like to setup caddy to do ssl termination as well, ideally with one of the caddy dns providers and the caddy certificate storage.

My current (working) configuration:

{
    storage s3 {
        host "{{caddy_cert_s3_host}}"
        bucket "{{caddy_cert_s3_bucket}}"
        access_id "{{caddy_cert_s3_access_id}}"
        secret_key "{{caddy_cert_s3_secret_key}}"
        prefix "ssl"
        insecure false #disables SSL if true
    } 
    layer4 {
  0.0.0.0:{{valkey_port}} {
    @allowed {
      remote_ip {% for host in groups[valkey_clients_group] | sort %}{{hostvars[host].ansible_default_ipv4.address}}/32 {% endfor %}
    }
    route @allowed {
      proxy valkey:{{valkey_port}}
    }
  }
}

I was hoping naively this would work:

    route @allowed {
      proxy valkey:{{valkey_port}}
      tls {
        dns cloudflare {{cloudflare_caddy_api_token}}
        resolvers 1.1.1.1
      }
    }

I now understand that this cannot work because the tls module in layer4 does not support the caddy certificate providers.

My question then is: is there a way to do what I want with caddy ? Namely, can I setup caddy so that it automatically gets certificates from letsencrypt with the cloudflare dns provider and uses it to do tls termination before doing tcp-level reverse proxy to my valkey server ?

2. Error messages and/or full log output:

The above target configuration file triggers this log output:

Error: adapting config using caddyfile: parsing caddyfile tokens for 'layer4': wrong argument count or unexpected line ending after 'dns', at /etc

3. Caddy version:

Built from:

FROM caddy:2.8.4-builder AS builder

RUN xcaddy build  \
  --with github.com/ggicci/caddy-jwt@latest \
  --with github.com/caddy-dns/cloudflare \
  --with github.com/ss098/certmagic-s3 \
  --with github.com/mholt/caddy-l4

FROM caddy:2.8.4

COPY --from=builder /usr/bin/caddy /usr/bin/caddy

4. How I installed and ran Caddy:

ansible + docker

a. System environment:

root@hive-dev-valkey-1:/opt/caddy/caddy.toplevel.d# lsb_release -a
No LSB modules are available.
Distributor ID:	Debian
Description:	Debian GNU/Linux 12 (bookworm)
Release:	12
Codename:	bookworm
root@hive-dev-valkey-1:/opt/caddy/caddy.toplevel.d# docker version
Client: Docker Engine - Community
 Version:           27.4.1
 API version:       1.47
 Go version:        go1.22.10
 Git commit:        b9d17ea
 Built:             Tue Dec 17 15:45:56 2024
 OS/Arch:           linux/amd64
 Context:           default

Server: Docker Engine - Community
 Engine:
  Version:          27.4.1
  API version:      1.47 (minimum version 1.24)
  Go version:       go1.22.10
  Git commit:       c710b88
  Built:            Tue Dec 17 15:45:56 2024
  OS/Arch:          linux/amd64
  Experimental:     false
 containerd:
  Version:          1.7.24
  GitCommit:        88bf19b2105c8b17560993bee28a01ddc2f97182
 runc:
  Version:          1.2.2
  GitCommit:        v1.2.2-0-g7cb3632
 docker-init:
  Version:          0.19.0
  GitCommit:        de40ad0
root@hive-dev-valkey-1:/opt/caddy/caddy.toplevel.d# 

b. Command:

$ docker container inspect caddy |jq -r '.[].Config.Cmd[]' |tr '\n' ' '
caddy run -c /etc/caddy/Caddyfile

c. Service/unit/compose file:

ansible role:

   49 - name: Install caddy
   50   community.docker.docker_container:
   51     name: caddy
   52     image: "{{docker_registry}}/library/caddy:latest"
   53     state: present
   54     restart_policy: unless-stopped
   55     command: caddy run -c /etc/caddy/Caddyfile
   56     ports:
   57       - 443:443
   58     volumes:
   59       - /opt/caddy/:/etc/caddy:ro
   60     networks:
   61       - name: hive-local-ipv6
   62     etc_hosts:
   63       host.docker.internal: host-gateway
   64     recreate: true

d. My complete Caddy config:

{
    storage s3 {
        host "{{caddy_cert_s3_host}}"
        bucket "{{caddy_cert_s3_bucket}}"
        access_id "{{caddy_cert_s3_access_id}}"
        secret_key "{{caddy_cert_s3_secret_key}}"
        prefix "ssl"
        insecure false #disables SSL if true
    } 
    layer4 {
  0.0.0.0:{{valkey_port}} {
    @allowed {
      remote_ip {% for host in groups[valkey_clients_group] | sort %}{{hostvars[host].ansible_default_ipv4.address}}/32 {% endfor %}
    }
    route @allowed {
      proxy valkey:{{valkey_port}}
    }
  }
}

You can put this inside of an otherwise empty site block (i.e. HTTP) so that Caddy automates TLS for that domain. I don’t think we have wiring for TLS automation otherwise for caddy-l4 via Caddyfile right now.