Setting up Caddy with lets encrypt SSL with duckdns to serve multiple services running as docker containers

Explaining what does work

I have managed to set up Vaultwarden that is only accessible on a local LAN with a lets encrypt SSL certificate using Caddy. Caddy, Vaultwarden and other services run as Docker containers that run on a raspberry pi host.
I have setup a duckdns domain: test111.duckdns.org that points to my raspberry pi private LAN IP address.

I have added a host override in my pfsense DNS resolver settings (this was a crucial step to make it work) like so:

Pfsense DNS resolver host override settings

Host: test111
Domain: duckdns.org
IP Address: <raspberry pi IP address>

Docker compose file

networks:
  docker-mongoose:
    driver: bridge
    ipam:
      driver: default
      config:
        - subnet: "172.16.117.0/27"

services:
  caddy:
    image: caddy:2
    networks:
      docker-mongoose:
        ipv4_address: 172.16.117.10
    container_name: caddy
    restart: always
    ports:
      - 80:80
      - 443:443
      - 443:443/udp # Needed for HTTP/3.
    volumes:
      - ./caddy:/usr/bin/caddy  
      - ./Caddyfile:/etc/caddy/Caddyfile:ro
      - ./caddy-config:/config
      - ./caddy-data:/data
    environment:
      DOMAIN: "test111.duckdns.org" 
      #EMAIL: ""
      DUCKDNS_TOKEN: "<duckdns token>>"
      LOG_FILE: "/data/access.logs

   unifi-network-application:
    container_name: unifi-network-application
    image: lscr.io/linuxserver/unifi-network-application:latest
    networks:
      docker-mongoose:
        ipv4_address: 172.16.117.9
    sysctls:
      - net.ipv6.conf.all.disable_ipv6=1s
    ports:
      - 8443:8443
      - 3478:3478/udp
      - 10001:10001/udp
      - 8080:8080
      - 1900:1900/udp #optional
      #- 8843:8843 #optional
      #- 8880:8880 #optional
      #- 6789:6789 #optional
      #- 5514:5514/udp #optional
    environment:
      - PUID=1000
      - PGID=1000
      - TZ=Europe/London
      - MONGO_USER=user
      - MONGO_PASS=password
      - MONGO_HOST=unifi-db
      - MONGO_PORT=27017
      - MONGO_DBNAME=unifi-db
      - MEM_LIMIT=1024 #optional
      - MEM_STARTUP=1024 #optional
      #- MONGO_TLS= #optional
      #- MONGO_AUTHSOURCE= #optional
    volumes:
      - /home/user/docker/unifi-network-application/config:/config
    restart: unless-stopped

    unifi-db:
      etc....

  vaultwarden:
    image: vaultwarden/server:latest
    networks:
      docker-mongoose:
        ipv4_address: 172.16.117.8
    container_name: vaultwarden
    restart: always
    environment:
      DOMAIN: "https://test111.duckdns.org"
      SIGNUPS_ALLOWED: "false"
      INVITATIONS_ALLOWED: "false"
      SHOW_PASSWORD_HINT: "false"
      LOG_FILE: "/data/vaultwarden.log"
      LOG_LEVEL: "warn"
    volumes:
      - ./vw-data:/data # the path before the : can be changed
    #ports:
      #- 8888:80 # you can replace the 11001 with your preferred port

Caddyfile

{$DOMAIN} {
	log {
		level INFO
		output file {$LOG_FILE} {
			roll_size 10MB
			roll_keep 10
		}
	}

	# Use the ACME DNS-01 challenge to get a cert for the configured domain.
	tls {
		dns duckdns {$DUCKDNS_TOKEN}
	}

	# This setting may have compatibility issues with some browsers
	# (e.g., attachment downloading on Firefox). Try disabling this
	# if you encounter issues.
	encode zstd gzip

	# Proxy everything to Rocket
	reverse_proxy vaultwarden:80
}

This setup works fine, I can access my Vaultwarden over SSL by going to https://test111.duckdns.org and it uses a lets encrypt certificate. I used this guide to achieve this.

1. The problem I’m having:

However I would like it so I could use Caddy for multiple docker services. For example to visit Vaultwarden I could visit https://vaultwarden.test111.duckdns.org or https://service.test111.duckdns.org etc.

I have tried to change this in the Caddyfile by using wildcards:

Updated Caddyfile with wildcards

# Wildcard SSL for all subdomains under the domain defined in the {$DOMAIN} variable
*.{$DOMAIN} {
    tls {
        dns duckdns {$DUCKDNS_TOKEN}
    }

    # Logs configuration (optional, adjust as necessary)
    log {
        level INFO
        output file {$LOG_FILE} {
            roll_size 10MB
            roll_keep 10
        }
    }

    # Default reverse proxy to a generic service if no specific service matches
    reverse_proxy service_default:80
}

# Vaultwarden Service
vaultwarden.{$DOMAIN} {
    reverse_proxy vaultwarden:80
    log {
        level INFO
        output file {$LOG_FILE} {
            roll_size 10MB
            roll_keep 10
        }
    }
}


unifi.{$DOMAIN} {
    reverse_proxy unifi-network-application:8443
    log {
        level INFO
        output file {$LOG_FILE} {
            roll_size 10MB
            roll_keep 10
        }
    }
}

I have also added the host overrides in pfsenses DNS resolver settings for the different services to point to my docker IP addresses:

Updated Pfsense DNS resolver host override settings

Host: unifi 	
Domain: test111.duckdns.org 	
IP Address: 172.16.117.9
Host: vaultwarden 	
Domain: test111.duckdns.org 	
IP Address: 172.16.117.8

And it can find these with nslookup:

Testing nslookup with my subdomains

nslookup vaultwarden.test111.duckdns.org
Server:		127.0.0.53
Address:	127.0.0.53#53

Non-authoritative answer:
Name:	vaultwarden.test111.duckdns.org
Address: 172.16.117.8

However this doesn’t work I can’t access my docker services and I get the following errors in my caddy container:

2. Error messages and/or full log output:

{"level":"info","ts":1731770427.407683,"msg":"using config from file","file":"/etc/caddy/Caddyfile"}

{"level":"info","ts":1731770427.4159002,"msg":"adapted config to JSON","adapter":"caddyfile"}

{"level":"info","ts":1731770427.4204524,"logger":"admin","msg":"admin endpoint started","address":"localhost:2019","enforce_origin":false,"origins":["//localhost:2019","//[::1]:2019","//127.0.0.1:2019"]}

{"level":"info","ts":1731770427.4216182,"logger":"tls.cache.maintenance","msg":"started background certificate maintenance","cache":"0x383a900"}

{"level":"info","ts":1731770427.4221516,"logger":"http.auto_https","msg":"server is listening only on the HTTPS port but has no TLS connection policies; adding one to enable TLS","server_name":"srv0","https_port":443}

{"level":"info","ts":1731770427.4224873,"logger":"http.auto_https","msg":"enabling automatic HTTP->HTTPS redirects","server_name":"srv0"}

{"level":"info","ts":1731770427.4248602,"logger":"http","msg":"enabling HTTP/3 listener","addr":":443"}

{"level":"info","ts":1731770427.4254677,"msg":"failed to sufficiently increase receive buffer size (was: 208 kiB, wanted: 7168 kiB, got: 416 kiB). See https://github.com/quic-go/quic-go/wiki/UDP-Buffer-Sizes for details."}

{"level":"info","ts":1731770427.4263346,"logger":"http.log","msg":"server running","name":"srv0","protocols":["h1","h2","h3"]}

{"level":"info","ts":1731770427.4268074,"logger":"http.log","msg":"server running","name":"remaining_auto_https_redirects","protocols":["h1","h2","h3"]}

{"level":"info","ts":1731770427.4269671,"logger":"http","msg":"enabling automatic TLS certificate management","domains":["vaultwarden.test111.duckdns.org","unifi.test111.duckdns.org","*.test111.duckdns.org"]}

{"level":"info","ts":1731770427.4284034,"msg":"autosaved config (load with --resume flag)","file":"/config/caddy/autosave.json"}

{"level":"info","ts":1731770427.4289424,"msg":"serving initial configuration"}

{"level":"info","ts":1731770427.4288747,"logger":"tls.obtain","msg":"acquiring lock","identifier":"vaultwarden.test111.duckdns.org"}

{"level":"info","ts":1731770427.4296653,"logger":"tls.obtain","msg":"acquiring lock","identifier":"unifi.test111.duckdns.org"}

{"level":"info","ts":1731770427.429877,"logger":"tls.obtain","msg":"acquiring lock","identifier":"*.test111.duckdns.org"}

{"level":"info","ts":1731770427.4420304,"logger":"tls","msg":"storage cleaning happened too recently; skipping for now","storage":"FileStorage:/data/caddy","instance":"a16163dc-5a65-4977-a1d2-99f3861efde9","try_again":1731856827.4420183,"try_again_in":86399.999995129}

{"level":"info","ts":1731770427.4445798,"logger":"tls","msg":"finished cleaning storage units"}

{"level":"info","ts":1731770427.44627,"logger":"tls.obtain","msg":"lock acquired","identifier":"*.test111.duckdns.org"}

{"level":"info","ts":1731770427.4462702,"logger":"tls.obtain","msg":"lock acquired","identifier":"vaultwarden.test111.duckdns.org"}

{"level":"info","ts":1731770427.446822,"logger":"tls.obtain","msg":"obtaining certificate","identifier":"*.test111.duckdns.org"}

{"level":"info","ts":1731770427.4474423,"logger":"tls.obtain","msg":"obtaining certificate","identifier":"vaultwarden.test111.duckdns.org"}

{"level":"info","ts":1731770427.4468448,"logger":"tls.obtain","msg":"lock acquired","identifier":"unifi.test111.duckdns.org"}

{"level":"info","ts":1731770427.4486356,"logger":"tls.obtain","msg":"obtaining certificate","identifier":"unifi.test111.duckdns.org"}

{"level":"info","ts":1731770427.4698937,"logger":"tls","msg":"waiting on internal rate limiter","identifiers":["unifi.test111.duckdns.org"],"ca":"https://acme-v02.api.letsencrypt.org/directory","account":""}

{"level":"info","ts":1731770427.4699652,"logger":"tls","msg":"done waiting on internal rate limiter","identifiers":["unifi.test111.duckdns.org"],"ca":"https://acme-v02.api.letsencrypt.org/directory","account":""}

{"level":"info","ts":1731770427.4700146,"logger":"tls","msg":"using ACME account","account_id":"https://acme-v02.api.letsencrypt.org/acme/acct/1972895377","account_contact":[]}

{"level":"info","ts":1731770427.4704852,"logger":"tls","msg":"waiting on internal rate limiter","identifiers":["vaultwarden.test111.duckdns.org"],"ca":"https://acme-v02.api.letsencrypt.org/directory","account":""}

{"level":"info","ts":1731770427.4709487,"logger":"tls","msg":"done waiting on internal rate limiter","identifiers":["vaultwarden.test111.duckdns.org"],"ca":"https://acme-v02.api.letsencrypt.org/directory","account":""}

{"level":"info","ts":1731770427.472356,"logger":"tls","msg":"using ACME account","account_id":"https://acme-v02.api.letsencrypt.org/acme/acct/1972895377","account_contact":[]}

{"level":"info","ts":1731770427.4715934,"logger":"tls.issuance.acme","msg":"waiting on internal rate limiter","identifiers":["*.test111.duckdns.org"],"ca":"https://acme-v02.api.letsencrypt.org/directory","account":""}

{"level":"info","ts":1731770427.4725082,"logger":"tls.issuance.acme","msg":"done waiting on internal rate limiter","identifiers":["*.test111.duckdns.org"],"ca":"https://acme-v02.api.letsencrypt.org/directory","account":""}

{"level":"info","ts":1731770427.4725654,"logger":"tls.issuance.acme","msg":"using ACME account","account_id":"https://acme-v02.api.letsencrypt.org/acme/acct/1972895377","account_contact":[]}

{"level":"info","ts":1731770428.6145887,"logger":"tls.acme_client","msg":"trying to solve challenge","identifier":"vaultwarden.test111.duckdns.org","challenge_type":"tls-alpn-01","ca":"https://acme-v02.api.letsencrypt.org/directory"}

{"level":"info","ts":1731770428.686017,"logger":"tls.acme_client","msg":"trying to solve challenge","identifier":"unifi.test111.duckdns.org","challenge_type":"tls-alpn-01","ca":"https://acme-v02.api.letsencrypt.org/directory"}

{"level":"info","ts":1731770428.8439467,"logger":"tls.issuance.acme.acme_client","msg":"trying to solve challenge","identifier":"*.test111.duckdns.org","challenge_type":"dns-01","ca":"https://acme-v02.api.letsencrypt.org/directory"}

{"level":"error","ts":1731770429.2492373,"logger":"tls.acme_client","msg":"challenge failed","identifier":"unifi.test111.duckdns.org","challenge_type":"tls-alpn-01","problem":{"type":"urn:ietf:params:acme:error:dns","title":"","detail":"no valid A records found for unifi.test111.duckdns.org; DNS problem: SERVFAIL looking up AAAA for unifi.test111.duckdns.org - the domain's nameservers may be malfunctioning","instance":"","subproblems":[]}}

{"level":"error","ts":1731770429.2495832,"logger":"tls.acme_client","msg":"validating authorization","identifier":"unifi.test111.duckdns.org","problem":{"type":"urn:ietf:params:acme:error:dns","title":"","detail":"no valid A records found for unifi.test111.duckdns.org; DNS problem: SERVFAIL looking up AAAA for unifi.test111.duckdns.org - the domain's nameservers may be malfunctioning","instance":"","subproblems":[]},"order":"https://acme-v02.api.letsencrypt.org/acme/order/1972895377/323713330197","attempt":1,"max_attempts":3}

{"level":"info","ts":1731770430.672126,"logger":"tls.acme_client","msg":"trying to solve challenge","identifier":"unifi.test111.duckdns.org","challenge_type":"http-01","ca":"https://acme-v02.api.letsencrypt.org/directory"}

{"level":"error","ts":1731770431.3256845,"logger":"tls.issuance.acme.acme_client","msg":"cleaning up solver","identifier":"*.test111.duckdns.org","challenge_type":"dns-01","error":"no memory of presenting a DNS record for \"_acme-challenge.test111.duckdns.org\" (usually OK if presenting also failed)"}

{"level":"error","ts":1731770431.5020833,"logger":"tls.obtain","msg":"could not get certificate from issuer","identifier":"*.test111.duckdns.org","issuer":"acme-v02.api.letsencrypt.org-directory","error":"[*.test111.duckdns.org] solving challenges: presenting for challenge: could not determine zone for domain \"_acme-challenge.test111.duckdns.org\": unexpected response code 'SERVFAIL' for _acme-challenge.test111.duckdns.org. (order=https://acme-v02.api.letsencrypt.org/acme/order/1972895377/323713330907) (ca=https://acme-v02.api.letsencrypt.org/directory)"}

{"level":"error","ts":1731770431.5025475,"logger":"tls.obtain","msg":"will retry","error":"[*.test111.duckdns.org] Obtain: [*.test111.duckdns.org] solving challenges: presenting for challenge: could not determine zone for domain \"_acme-challenge.test111.duckdns.org\": unexpected response code 'SERVFAIL' for _acme-challenge.test111.duckdns.org. (order=https://acme-v02.api.letsencrypt.org/acme/order/1972895377/323713330907) (ca=https://acme-v02.api.letsencrypt.org/directory)","attempt":1,"retrying_in":60,"elapsed":4.05621756,"max_duration":2592000}

{"level":"error","ts":1731770438.8788044,"logger":"tls.acme_client","msg":"challenge failed","identifier":"vaultwarden.test111.duckdns.org","challenge_type":"tls-alpn-01","problem":{"type":"urn:ietf:params:acme:error:dns","title":"","detail":"DNS problem: SERVFAIL looking up A for vaultwarden.test111.duckdns.org - the domain's nameservers may be malfunctioning; no valid AAAA records found for vaultwarden.test111.duckdns.org","instance":"","subproblems":[]}}

{"level":"error","ts":1731770438.8789387,"logger":"tls.acme_client","msg":"validating authorization","identifier":"vaultwarden.test111.duckdns.org","problem":{"type":"urn:ietf:params:acme:error:dns","title":"","detail":"DNS problem: SERVFAIL looking up A for vaultwarden.test111.duckdns.org - the domain's nameservers may be malfunctioning; no valid AAAA records found for vaultwarden.test111.duckdns.org","instance":"","subproblems":[]},"order":"https://acme-v02.api.letsencrypt.org/acme/order/1972895377/323713330097","attempt":1,"max_attempts":3}

{"level":"info","ts":1731770440.2944498,"logger":"tls.acme_client","msg":"trying to solve challenge","identifier":"vaultwarden.test111.duckdns.org","challenge_type":"http-01","ca":"https://acme-v02.api.letsencrypt.org/directory"}

{"level":"error","ts":1731770450.1866465,"logger":"tls.acme_client","msg":"challenge failed","identifier":"unifi.test111.duckdns.org","challenge_type":"http-01","problem":{"type":"urn:ietf:params:acme:error:dns","title":"","detail":"no valid A records found for unifi.test111.duckdns.org; no valid AAAA records found for unifi.test111.duckdns.org","instance":"","subproblems":[]}}

{"level":"error","ts":1731770450.1867352,"logger":"tls.acme_client","msg":"validating authorization","identifier":"unifi.test111.duckdns.org","problem":{"type":"urn:ietf:params:acme:error:dns","title":"","detail":"no valid A records found for unifi.test111.duckdns.org; no valid AAAA records found for unifi.test111.duckdns.org","instance":"","subproblems":[]},"order":"https://acme-v02.api.letsencrypt.org/acme/order/1972895377/323713337107","attempt":2,"max_attempts":3}

{"level":"error","ts":1731770450.1868649,"logger":"tls.obtain","msg":"could not get certificate from issuer","identifier":"unifi.test111.duckdns.org","issuer":"acme-v02.api.letsencrypt.org-directory","error":"HTTP 400 urn:ietf:params:acme:error:dns - no valid A records found for unifi.test111.duckdns.org; no valid AAAA records found for unifi.test111.duckdns.org"}

{"level":"error","ts":1731770450.1870203,"logger":"tls.obtain","msg":"will retry","error":"[unifi.test111.duckdns.org] Obtain: [unifi.test111.duckdns.org] solving challenge: unifi.test111.duckdns.org: [unifi.test111.duckdns.org] authorization failed: HTTP 400 urn:ietf:params:acme:error:dns - no valid A records found for unifi.test111.duckdns.org; no valid AAAA records found for unifi.test111.duckdns.org (ca=https://acme-v02.api.letsencrypt.org/directory)","attempt":1,"retrying_in":60,"elapsed":22.738644345,"max_duration":2592000}

{"level":"error","ts":1731770460.5871239,"logger":"tls.acme_client","msg":"challenge failed","identifier":"vaultwarden.test111.duckdns.org","challenge_type":"http-01","problem":{"type":"urn:ietf:params:acme:error:dns","title":"","detail":"DNS problem: SERVFAIL looking up A for vaultwarden.test111.duckdns.org - the domain's nameservers may be malfunctioning; DNS problem: SERVFAIL looking up AAAA for vaultwarden.test111.duckdns.org - the domain's nameservers may be malfunctioning","instance":"","subproblems":[]}}

{"level":"error","ts":1731770460.5872557,"logger":"tls.acme_client","msg":"validating authorization","identifier":"vaultwarden.test111.duckdns.org","problem":{"type":"urn:ietf:params:acme:error:dns","title":"","detail":"DNS problem: SERVFAIL looking up A for vaultwarden.test111.duckdns.org - the domain's nameservers may be malfunctioning; DNS problem: SERVFAIL looking up AAAA for vaultwarden.test111.duckdns.org - the domain's nameservers may be malfunctioning","instance":"","subproblems":[]},"order":"https://acme-v02.api.letsencrypt.org/acme/order/1972895377/323713378127","attempt":2,"max_attempts":3}

{"level":"error","ts":1731770460.5873518,"logger":"tls.obtain","msg":"could not get certificate from issuer","identifier":"vaultwarden.test111.duckdns.org","issuer":"acme-v02.api.letsencrypt.org-directory","error":"HTTP 400 urn:ietf:params:acme:error:dns - DNS problem: SERVFAIL looking up A for vaultwarden.test111.duckdns.org - the domain's nameservers may be malfunctioning; DNS problem: SERVFAIL looking up AAAA for vaultwarden.test111.duckdns.org - the domain's nameservers may be malfunctioning"}

{"level":"error","ts":1731770460.5875442,"logger":"tls.obtain","msg":"will retry","error":"[vaultwarden.test111.duckdns.org] Obtain: [vaultwarden.test111.duckdns.org] solving challenge: vaultwarden.test111.duckdns.org: [vaultwarden.test111.duckdns.org] authorization failed: HTTP 400 urn:ietf:params:acme:error:dns - DNS problem: SERVFAIL looking up A for vaultwarden.test111.duckdns.org - the domain's nameservers may be malfunctioning; DNS problem: SERVFAIL looking up AAAA for vaultwarden.test111.duckdns.org - the domain's nameservers may be malfunctioning (ca=https://acme-v02.api.letsencrypt.org/directory)","attempt":1,"retrying_in":60,"elapsed":33.140588664,"max_duration":2592000}

3. Caddy version:

v2.8.4 h1:q3pe0wpBj1OcHFZ3n/1nl4V4bxBrYoSoab7rL9BMYNk=

4. How I installed and ran Caddy:

Refer back to my docker compose and Caddy file.

a. System environment:

Docker containers running on a Rasberry Pi that runs rasperry pi os 6.1.21-v8+ #1642. Please also refer to the Explaining what does work section

I’m essentially trying to achieve this, but with Caddy and duck dns. But I’m not not sure if this is possible or if i’m configuring Caddy incorrectly.

Let me know if you need any more details.

Any help is appreciated, thanks.

Howdy @bricksun, welcome to the Caddy community.

Your host overrides look good. This strategy is broadly referred to as split-horizon DNS, where the same domain name resolves to a LAN IP within the LAN but an external IP on the internet. Your DNS server seems to be working A-OK.

The problem, to be short, is DuckDNS. It is an incredibly poor quality service. If you google for “duckdns issues” you’ll find a barrage of downtimes, DNS errors and more. I’ve seen people monitoring their apps through DuckDNS and another provider simultaneously on UptimeKuma and they take a ~50% uptime hit on DuckDNS while the other DNS provider is up 100%.

In your case you’re running into endless SERVFAILs resolving your sub-subdomains. Here’s the issue Caddy is getting from LetsEncrypt: HTTP 400 urn:ietf:params:acme:error:dns - DNS problem: SERVFAIL looking up A for vaultwarden.test111.duckdns.org - the domain's nameservers may be malfunctioning; DNS problem: SERVFAIL looking up AAAA for vaultwarden.test111.duckdns.org - the domain's nameservers may be malfunctioning

My advice is to ditch DuckDNS. I recommend Cloudflare’s free tier. You can use Caddy itself as a DDNS client to keep Cloudflare (or any other DNS provider with a Caddy DNS module) pointed at your dynamic IP: GitHub - mholt/caddy-dynamicdns: Caddy app that keeps your DNS records (A/AAAA) pointed at itself.

1 Like

Hi @Whitestrake, thanks for the reply. I will take you advice and try a different subdomain provider. I did make a cloudflare account however it doesn’t seem to have the option to create a subdomain using cloudflares services like you can with duckdns for free. It looks like you can only import an existing subdomain and have cloudflare manage it or buy a domain from cloudflare, unless I’m doing something wrong? Eventually I do intend to buy my own domain but before I do I want to make sure everything works with a free service. Hopefully you’ll be able to point me in the right direction if cloud free tier doesn’t allow you create subdomains unless I’m misunderstanding?
Many thanks.

.com domains through Cloudflare are at $10.44, looks like.

If you need to double check everything before purchasing a domain name you can get a free one at Dot TK - Find a new FREE domain. I’ve used them before - the catch is you need to go back to them and manually confirm you’re still using it every now and again or they’ll take it back off you and put it back in circulation. That’ll give you an entire domain you can point the nameservers at Cloudflare for.

1 Like

Hi again, I checked Freenom but currently their service is unavailable. I did manage to find another eventually, EU.org but you have to wait for them to manually approve your domain. As soon as I get one approved I will add cloud flares nameservers to the domain and try the setup. I will update you when I can.

Thanks.

My last request has been over 6 months (and still waiting), my first request was less than 2 months with EU.org

I managed to figure it out. It turns out I needed to change each host override
in PfSense to the local IP of the raspberry pi that hosts each service instead of the docker address that is assigned to each service container. My DuckDNS domain seems to work fine after this change.

Thanks for providing solution.