Running Caddy on high ports

One of the great selling points of Caddy is seamless SSL setup as Caddy will automatically request certificates from Let's Encrypt. However it uses the HTTP-01 or the TLS-ALPN-01 challenge which requires exposing Caddy on port 80 or 443 during the cert renewal process. This may not be possible as there could be other services already running on those ports, or there could be a reluctance to expose the service to widespread scanning.

After some finicking around, I managed to build Caddy with the dns.providers.cloudflare module and set it up to use the DNS-01 challenge which eliminates the need to use port 80 or 443.

The first step is to install xcaddy, create a Dockerfile with the required plugins and build it. I chose to name the image custom-caddy.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
# go install github.com/caddyserver/xcaddy/cmd/xcaddy@latest
# cat Dockerfile
FROM caddy:builder AS builder

RUN xcaddy build \
    --with github.com/caddy-dns/cloudflare \
    --output /usr/bin/caddy

FROM caddy:latest

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

# docker build -t custom-caddy:latest .

Next up, we create a Caddyfile with the required configuration to use DNS challenge.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
# cat Caddyfile
{
    http_port 80
    https_port 443
}


subdomain.domain.com:443 {
  reverse_proxy yourapphere:80

  tls {
    dns cloudflare <insert key here>
    resolvers 1.1.1.1
  }
}

Finally, we create a docker-compose file which maps the high ports, 29000 and 29001 in my case, on the host machine to port 80/443 on Caddy. We are now ready to spin up the containers, Caddy should now automatically retrieve an SSL certificate using the DNS-01 challenge.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
# cat docker-compose.yml 
services:
  yourapphere:
    image: yourapphere:latest
    container_name: yourapphere
    restart: unless-stopped
    environment:
      - DOMAIN=https://subdomain.domain.com
    networks:
      - internal

  caddy:
    image: custom-caddy
    container_name: custom-caddy
    restart: unless-stopped
    ports:
      - "29000:80"
      - "29001:443"
    volumes:
      - caddy-data:/data
      - caddy-config:/config
      - ./Caddyfile:/etc/caddy/Caddyfile
    networks:
      - internal

volumes:
  caddy-data:
  caddy-config:

networks:
  internal:
    driver: bridge

# docker compose up -d