1. The problem I’m having:
I’m trying to self host a bunch of services, Immich and Jellyfin to start with. I’d like to share it publicly using DDNS while keeping it secure enough for daily personal needs. I do not want to use a VPN, that’s non negotiable. So after long hours of research I’ve picked a few tools to achieve the goal - Caddy, Authentik, DuckDNS, Let’s Encrypt and Docker. The thing is, I’m not IT specialist, not even much of a hobbyst (most I’ve ever done was port forwarding for minecraft servers) and I have no idea how to do any of that. I’ve spent 2 days trying to set it up and all I’ve managed to do was proxying localhost:2283 to localhost. I clearly have no idea what I’m doing and all the tutorials in the world couldn’t help me. Currently I’m stuck on getting a certificate from DDNS. I’d like to have nice, safe encrypted https on every device I connect from, with no loud, red warnings. Can’t use Cloudflare since their ToS don’t allow Jellyfin.
2. Error messages and/or full log output:
Got 2 different errors depending on the config. “Expected OK but got KO” and “Couldn’t get certificate from issuer”. 2nd one logs are below, 1st one couldn’t replicate.
2024-05-23 20:40:43 caddy-1 | {"level":"info","ts":1716489643.1647232,"msg":"using provided configuration","config_file":"/etc/caddy/Caddyfile","config_adapter":"caddyfile"}
2024-05-23 20:40:43 caddy-1 | {"level":"warn","ts":1716489643.1653807,"msg":"Caddyfile input is not formatted; run 'caddy fmt --overwrite' to fix inconsistencies","adapter":"caddyfile","file":"/etc/caddy/Caddyfile","line":10}
2024-05-23 20:40:43 caddy-1 | {"level":"info","ts":1716489643.1657922,"logger":"admin","msg":"admin endpoint started","address":"localhost:2019","enforce_origin":false,"origins":["//[::1]:2019","//127.0.0.1:2019","//localhost:2019"]}
2024-05-23 20:40:43 caddy-1 | {"level":"info","ts":1716489643.1659787,"logger":"tls.cache.maintenance","msg":"started background certificate maintenance","cache":"0xc0005e1b00"}
2024-05-23 20:40:43 caddy-1 | {"level":"info","ts":1716489643.1661506,"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}
2024-05-23 20:40:43 caddy-1 | {"level":"info","ts":1716489643.1661656,"logger":"http.auto_https","msg":"enabling automatic HTTP->HTTPS redirects","server_name":"srv0"}
2024-05-23 20:40:43 caddy-1 | {"level":"debug","ts":1716489643.1661828,"logger":"http.auto_https","msg":"adjusted config","tls":{"automation":{"policies":[{"subjects":["danielowy.duckdns.org"]},{}]}},"http":{"servers":{"remaining_auto_https_redirects":{"listen":[":80"],"routes":[{},{}]},"srv0":{"listen":[":443"],"routes":[{"handle":[{"handler":"subroute","routes":[{"handle":[{"handler":"reverse_proxy","upstreams":[{"dial":"jellyfin:8096"}]}]}]}],"terminal":true}],"tls_connection_policies":[{}],"automatic_https":{}}}}}
2024-05-23 20:40:43 caddy-1 | {"level":"info","ts":1716489643.1663315,"logger":"http","msg":"enabling HTTP/3 listener","addr":":443"}
2024-05-23 20:40:43 caddy-1 | {"level":"info","ts":1716489643.1663868,"msg":"failed to sufficiently increase receive buffer size (was: 208 kiB, wanted: 2048 kiB, got: 416 kiB). See https://github.com/quic-go/quic-go/wiki/UDP-Buffer-Sizes for details."}
2024-05-23 20:40:43 caddy-1 | {"level":"debug","ts":1716489643.166445,"logger":"http","msg":"starting server loop","address":"[::]:443","tls":true,"http3":true}
2024-05-23 20:40:43 caddy-1 | {"level":"info","ts":1716489643.1664739,"logger":"http.log","msg":"server running","name":"srv0","protocols":["h1","h2","h3"]}
2024-05-23 20:40:43 caddy-1 | {"level":"debug","ts":1716489643.1664965,"logger":"http","msg":"starting server loop","address":"[::]:80","tls":false,"http3":false}
2024-05-23 20:40:43 caddy-1 | {"level":"info","ts":1716489643.1665096,"logger":"http.log","msg":"server running","name":"remaining_auto_https_redirects","protocols":["h1","h2","h3"]}
2024-05-23 20:40:43 caddy-1 | {"level":"info","ts":1716489643.1665115,"logger":"http","msg":"enabling automatic TLS certificate management","domains":["danielowy.duckdns.org"]}
2024-05-23 20:40:43 caddy-1 | {"level":"info","ts":1716489643.166624,"msg":"autosaved config (load with --resume flag)","file":"/config/caddy/autosave.json"}
2024-05-23 20:40:43 caddy-1 | {"level":"info","ts":1716489643.1666381,"msg":"serving initial configuration"}
2024-05-23 20:40:43 caddy-1 | {"level":"info","ts":1716489643.1666844,"logger":"tls.obtain","msg":"acquiring lock","identifier":"danielowy.duckdns.org"}
2024-05-23 20:40:43 caddy-1 | {"level":"info","ts":1716489643.1683922,"logger":"tls.obtain","msg":"lock acquired","identifier":"danielowy.duckdns.org"}
2024-05-23 20:40:43 caddy-1 | {"level":"warn","ts":1716489643.1684337,"logger":"tls","msg":"storage cleaning happened too recently; skipping for now","storage":"FileStorage:/data/caddy","instance":"3e553092-fbc6-4db7-914c-667624d37dda","try_again":1716576043.1684327,"try_again_in":86399.99999972}
2024-05-23 20:40:43 caddy-1 | {"level":"info","ts":1716489643.1684852,"logger":"tls","msg":"finished cleaning storage units"}
2024-05-23 20:40:43 caddy-1 | {"level":"info","ts":1716489643.1685073,"logger":"tls.obtain","msg":"obtaining certificate","identifier":"danielowy.duckdns.org"}
2024-05-23 20:40:43 caddy-1 | {"level":"debug","ts":1716489643.168519,"logger":"events","msg":"event","name":"cert_obtaining","id":"183937af-32a4-456c-bc84-36809894a9d6","origin":"tls","data":{"identifier":"danielowy.duckdns.org"}}
2024-05-23 20:40:43 caddy-1 | {"level":"debug","ts":1716489643.1686676,"logger":"tls.obtain","msg":"trying issuer 1/2","issuer":"acme-v02.api.letsencrypt.org-directory"}
2024-05-23 20:40:43 caddy-1 | {"level":"info","ts":1716489643.1688538,"logger":"tls.issuance.acme","msg":"waiting on internal rate limiter","identifiers":["danielowy.duckdns.org"],"ca":"https://acme-v02.api.letsencrypt.org/directory","account":""}
2024-05-23 20:40:43 caddy-1 | {"level":"info","ts":1716489643.168864,"logger":"tls.issuance.acme","msg":"done waiting on internal rate limiter","identifiers":["danielowy.duckdns.org"],"ca":"https://acme-v02.api.letsencrypt.org/directory","account":""}
2024-05-23 20:40:43 caddy-1 | {"level":"debug","ts":1716489643.7153373,"logger":"tls.issuance.acme.acme_client","msg":"http request","method":"GET","url":"https://acme-v02.api.letsencrypt.org/directory","headers":{"User-Agent":["Caddy/2.7.6 CertMagic acmez (linux; amd64)"]},"response_headers":{"Cache-Control":["public, max-age=0, no-cache"],"Content-Length":["746"],"Content-Type":["application/json"],"Date":["Thu, 23 May 2024 18:40:43 GMT"],"Server":["nginx"],"Strict-Transport-Security":["max-age=604800"],"X-Frame-Options":["DENY"]},"status_code":200}
2024-05-23 20:40:43 caddy-1 | {"level":"debug","ts":1716489643.884846,"logger":"tls.issuance.acme.acme_client","msg":"http request","method":"HEAD","url":"https://acme-v02.api.letsencrypt.org/acme/new-nonce","headers":{"User-Agent":["Caddy/2.7.6 CertMagic acmez (linux; amd64)"]},"response_headers":{"Cache-Control":["public, max-age=0, no-cache"],"Date":["Thu, 23 May 2024 18:40:43 GMT"],"Link":["<https://acme-v02.api.letsencrypt.org/directory>;rel=\"index\""],"Replay-Nonce":["gEGAwOe-U9PDISuheQ7ZgRA64h2k_SwPygjBmPEntbCYJ9Sw93g"],"Server":["nginx"],"Strict-Transport-Security":["max-age=604800"],"X-Frame-Options":["DENY"]},"status_code":200}
2024-05-23 20:40:44 caddy-1 | {"level":"debug","ts":1716489644.172973,"logger":"tls.issuance.acme.acme_client","msg":"http request","method":"POST","url":"https://acme-v02.api.letsencrypt.org/acme/new-order","headers":{"Content-Type":["application/jose+json"],"User-Agent":["Caddy/2.7.6 CertMagic acmez (linux; amd64)"]},"response_headers":{"Boulder-Requester":["1741042762"],"Cache-Control":["public, max-age=0, no-cache"],"Content-Length":["347"],"Content-Type":["application/json"],"Date":["Thu, 23 May 2024 18:40:44 GMT"],"Link":["<https://acme-v02.api.letsencrypt.org/directory>;rel=\"index\""],"Location":["https://acme-v02.api.letsencrypt.org/acme/order/1741042762/271954006772"],"Replay-Nonce":["YEjsK0piNh9z7vogXbZVRkY0p6T7u7J8fuQzIXre7HCHt41UZZs"],"Server":["nginx"],"Strict-Transport-Security":["max-age=604800"],"X-Frame-Options":["DENY"]},"status_code":201}
2024-05-23 20:40:44 caddy-1 | {"level":"debug","ts":1716489644.3412802,"logger":"tls.issuance.acme.acme_client","msg":"http request","method":"POST","url":"https://acme-v02.api.letsencrypt.org/acme/authz-v3/354515874452","headers":{"Content-Type":["application/jose+json"],"User-Agent":["Caddy/2.7.6 CertMagic acmez (linux; amd64)"]},"response_headers":{"Boulder-Requester":["1741042762"],"Cache-Control":["public, max-age=0, no-cache"],"Content-Length":["805"],"Content-Type":["application/json"],"Date":["Thu, 23 May 2024 18:40:44 GMT"],"Link":["<https://acme-v02.api.letsencrypt.org/directory>;rel=\"index\""],"Replay-Nonce":["gEGAwOe-qhOyV8YOuOaiHMVGZRbHKoGsXBR5vj4yTl0tJFssFCk"],"Server":["nginx"],"Strict-Transport-Security":["max-age=604800"],"X-Frame-Options":["DENY"]},"status_code":200}
2024-05-23 20:40:44 caddy-1 | {"level":"info","ts":1716489644.341411,"logger":"tls.issuance.acme.acme_client","msg":"trying to solve challenge","identifier":"danielowy.duckdns.org","challenge_type":"dns-01","ca":"https://acme-v02.api.letsencrypt.org/directory"}
2024-05-23 20:40:48 caddy-1 | {"level":"error","ts":1716489648.342362,"logger":"tls.issuance.acme.acme_client","msg":"cleaning up solver","identifier":"danielowy.duckdns.org","challenge_type":"dns-01","error":"no memory of presenting a DNS record for \"_acme-challenge.danielowy.duckdns.org\" (usually OK if presenting also failed)"}
2024-05-23 20:40:48 caddy-1 | {"level":"debug","ts":1716489648.521384,"logger":"tls.issuance.acme.acme_client","msg":"http request","method":"POST","url":"https://acme-v02.api.letsencrypt.org/acme/authz-v3/354515874452","headers":{"Content-Type":["application/jose+json"],"User-Agent":["Caddy/2.7.6 CertMagic acmez (linux; amd64)"]},"response_headers":{"Boulder-Requester":["1741042762"],"Cache-Control":["public, max-age=0, no-cache"],"Content-Length":["809"],"Content-Type":["application/json"],"Date":["Thu, 23 May 2024 18:40:48 GMT"],"Link":["<https://acme-v02.api.letsencrypt.org/directory>;rel=\"index\""],"Replay-Nonce":["YEjsK0pi-UColGD4mYaqcOeavwss5EvYjWHdPS0XWVRCH-fiGb4"],"Server":["nginx"],"Strict-Transport-Security":["max-age=604800"],"X-Frame-Options":["DENY"]},"status_code":200}
2024-05-23 20:40:48 caddy-1 | {"level":"error","ts":1716489648.5214741,"logger":"tls.obtain","msg":"could not get certificate from issuer","identifier":"danielowy.duckdns.org","issuer":"acme-v02.api.letsencrypt.org-directory","error":"[danielowy.duckdns.org] solving challenges: presenting for challenge: could not determine zone for domain \"_acme-challenge.danielowy.duckdns.org\": unexpected response code 'SERVFAIL' for _acme-challenge.danielowy.duckdns.org. (order=https://acme-v02.api.letsencrypt.org/acme/order/1741042762/271954006772) (ca=https://acme-v02.api.letsencrypt.org/directory)"}
2024-05-23 20:40:48 caddy-1 | {"level":"debug","ts":1716489648.5214932,"logger":"tls.obtain","msg":"trying issuer 2/2","issuer":"acme.zerossl.com-v2-DV90"}
2024-05-23 20:40:48 caddy-1 | {"level":"info","ts":1716489648.5217586,"logger":"tls.issuance.zerossl","msg":"waiting on internal rate limiter","identifiers":["danielowy.duckdns.org"],"ca":"https://acme.zerossl.com/v2/DV90","account":"caddy@zerossl.com"}
2024-05-23 20:40:48 caddy-1 | {"level":"info","ts":1716489648.5217772,"logger":"tls.issuance.zerossl","msg":"done waiting on internal rate limiter","identifiers":["danielowy.duckdns.org"],"ca":"https://acme.zerossl.com/v2/DV90","account":"caddy@zerossl.com"}
2024-05-23 20:40:48 caddy-1 | {"level":"debug","ts":1716489648.768832,"logger":"tls.issuance.zerossl.acme_client","msg":"http request","method":"GET","url":"https://acme.zerossl.com/v2/DV90","headers":{"User-Agent":["Caddy/2.7.6 CertMagic acmez (linux; amd64)"]},"response_headers":{"Access-Control-Allow-Origin":["*"],"Content-Length":["645"],"Content-Type":["application/json"],"Date":["Thu, 23 May 2024 18:40:48 GMT"],"Server":["nginx"],"Strict-Transport-Security":["max-age=15724800; includeSubDomains"]},"status_code":200}
2024-05-23 20:40:49 caddy-1 | {"level":"debug","ts":1716489649.0781674,"logger":"tls.issuance.zerossl.acme_client","msg":"http request","method":"HEAD","url":"https://acme.zerossl.com/v2/DV90/newNonce","headers":{"User-Agent":["Caddy/2.7.6 CertMagic acmez (linux; amd64)"]},"response_headers":{"Access-Control-Allow-Origin":["*"],"Cache-Control":["max-age=0, no-cache, no-store"],"Content-Type":["application/octet-stream"],"Date":["Thu, 23 May 2024 18:40:49 GMT"],"Link":["<https://acme.zerossl.com/v2/DV90>;rel=\"index\""],"Replay-Nonce":["Xegd8C04lSDqEiDCWBE0jhqvZNfw79oBQf2h2r9jCjE"],"Server":["nginx"],"Strict-Transport-Security":["max-age=15724800; includeSubDomains"]},"status_code":200}
2024-05-23 20:40:49 caddy-1 | {"level":"debug","ts":1716489649.4409516,"logger":"tls.issuance.zerossl.acme_client","msg":"http request","method":"POST","url":"https://acme.zerossl.com/v2/DV90/newOrder","headers":{"Content-Type":["application/jose+json"],"User-Agent":["Caddy/2.7.6 CertMagic acmez (linux; amd64)"]},"response_headers":{"Access-Control-Allow-Origin":["*"],"Cache-Control":["max-age=0, no-cache, no-store"],"Content-Length":["283"],"Content-Type":["application/json"],"Date":["Thu, 23 May 2024 18:40:49 GMT"],"Location":["https://acme.zerossl.com/v2/DV90/order/PgnvetJqm8ddLjWOoV1Z3Q"],"Replay-Nonce":["O0TSW0C1CVTbJB37JJPrAdCcM-IU1uC3vftsAyJehLY"],"Server":["nginx"],"Strict-Transport-Security":["max-age=15724800; includeSubDomains"]},"status_code":201}
2024-05-23 20:40:49 caddy-1 | {"level":"debug","ts":1716489649.765614,"logger":"tls.issuance.zerossl.acme_client","msg":"http request","method":"POST","url":"https://acme.zerossl.com/v2/DV90/authz/Cy_EbVtDVe75K2Zg8Aqepg","headers":{"Content-Type":["application/jose+json"],"User-Agent":["Caddy/2.7.6 CertMagic acmez (linux; amd64)"]},"response_headers":{"Access-Control-Allow-Origin":["*"],"Cache-Control":["max-age=0, no-cache, no-store"],"Content-Length":["451"],"Content-Type":["application/json"],"Date":["Thu, 23 May 2024 18:40:49 GMT"],"Link":["<https://acme.zerossl.com/v2/DV90>;rel=\"index\""],"Replay-Nonce":["7Owj_LixgHNe73f-G7k1ppVvCkWLFh9zBMOBTdYgBQo"],"Retry-After":["5"],"Server":["nginx"],"Strict-Transport-Security":["max-age=15724800; includeSubDomains"]},"status_code":200}
2024-05-23 20:40:49 caddy-1 | {"level":"info","ts":1716489649.765708,"logger":"tls.issuance.zerossl.acme_client","msg":"trying to solve challenge","identifier":"danielowy.duckdns.org","challenge_type":"dns-01","ca":"https://acme.zerossl.com/v2/DV90"}
2024-05-23 20:40:53 caddy-1 | {"level":"error","ts":1716489653.7670057,"logger":"tls.issuance.zerossl.acme_client","msg":"cleaning up solver","identifier":"danielowy.duckdns.org","challenge_type":"dns-01","error":"no memory of presenting a DNS record for \"_acme-challenge.danielowy.duckdns.org\" (usually OK if presenting also failed)"}
2024-05-23 20:40:54 caddy-1 | {"level":"debug","ts":1716489654.1021712,"logger":"tls.issuance.zerossl.acme_client","msg":"http request","method":"POST","url":"https://acme.zerossl.com/v2/DV90/authz/Cy_EbVtDVe75K2Zg8Aqepg","headers":{"Content-Type":["application/jose+json"],"User-Agent":["Caddy/2.7.6 CertMagic acmez (linux; amd64)"]},"response_headers":{"Access-Control-Allow-Origin":["*"],"Cache-Control":["max-age=0, no-cache, no-store"],"Content-Length":["133"],"Content-Type":["application/json"],"Date":["Thu, 23 May 2024 18:40:54 GMT"],"Link":["<https://acme.zerossl.com/v2/DV90>;rel=\"index\""],"Replay-Nonce":["1dbZ-8cUjewsrg0xlTOxOZ_L7HIUdukz3uo-IM81U8Q"],"Server":["nginx"],"Strict-Transport-Security":["max-age=15724800; includeSubDomains"]},"status_code":200}
2024-05-23 20:40:54 caddy-1 | {"level":"error","ts":1716489654.1022627,"logger":"tls.obtain","msg":"could not get certificate from issuer","identifier":"danielowy.duckdns.org","issuer":"acme.zerossl.com-v2-DV90","error":"[danielowy.duckdns.org] solving challenges: presenting for challenge: could not determine zone for domain \"_acme-challenge.danielowy.duckdns.org\": unexpected response code 'SERVFAIL' for _acme-challenge.danielowy.duckdns.org. (order=https://acme.zerossl.com/v2/DV90/order/PgnvetJqm8ddLjWOoV1Z3Q) (ca=https://acme.zerossl.com/v2/DV90)"}
2024-05-23 20:40:54 caddy-1 | {"level":"debug","ts":1716489654.1022816,"logger":"events","msg":"event","name":"cert_failed","id":"91930939-9441-4b5e-a836-818cc83de3f1","origin":"tls","data":{"error":{},"identifier":"danielowy.duckdns.org","issuers":["acme-v02.api.letsencrypt.org-directory","acme.zerossl.com-v2-DV90"],"renewal":false}}
2024-05-23 20:40:54 caddy-1 | {"level":"error","ts":1716489654.1023266,"logger":"tls.obtain","msg":"will retry","error":"[danielowy.duckdns.org] Obtain: [danielowy.duckdns.org] solving challenges: presenting for challenge: could not determine zone for domain \"_acme-challenge.danielowy.duckdns.org\": unexpected response code 'SERVFAIL' for _acme-challenge.danielowy.duckdns.org. (order=https://acme.zerossl.com/v2/DV90/order/PgnvetJqm8ddLjWOoV1Z3Q) (ca=https://acme.zerossl.com/v2/DV90)","attempt":1,"retrying_in":60,"elapsed":10.933917722,"max_duration":2592000}
3. Caddy version:
docker-compose exec caddy caddy version
returned
no configuration file provided: not found
Docker Desktop says it’s Caddy:2 (“Newer image available”) and Alpine:3.18. I checked out github Dockerfile for the new version but it’s long, complicated and I don’t want to mess something up even more.
4. How I installed and ran Caddy:
I’m using custom build with this Dockerfile
FROM caddy:builder-alpine AS builder
RUN xcaddy build --with github.com/caddy-dns/duckdns
FROM caddy:latest
COPY --from=builder /usr/bin/caddy /usr/bin/caddy
a. System environment:
Docker Desktop 4.30.0
Windows 10 19045.4412 64
AMD Ryzen 5-5600X CPU,
“Connect Box” Router, it’s an ISP local brand afaik. Pretty restrictive software, but has some form of DDNS and port forwarding, which should be enough.
b. Command:
I’m on Windows, I’m not using commands unless I really have to. I know what you’re thinking, please don’t laugh also, since I’m using Windows, I don’t know where to paste Linux commands. WSL? Docker?
c. Service/unit/compose file:
services:
caddy:
image: caddy-duckdns:latest
restart: unless-stopped
ports:
- "80:80"
- "443:443"
- "443:443/udp"
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile
- ./site:/srv
- caddy_data:/data
- caddy_config:/config
networks:
- caddy_network
volumes:
caddy_data:
caddy_config:
networks:
caddy_network:
external: true
d. My complete Caddy config:
{
debug
}
danielowy.duckdns.org {
reverse_proxy jellyfin:8096
tls {
dns duckdns (123)
}
}
5. Links to relevant resources:
https://blog.gurucomputing.com.au/Reverse%20Proxies%20with%20Caddy/Introduction/