01Introduction
Let's Encrypt provides free SSL/TLS certificates that renew automatically. Combined with Docker reverse proxies, you get HTTPS with zero ongoing maintenance. This guide explains how it works and how to set it up.
02How Let's Encrypt Works
Let's Encrypt uses the ACME protocol to verify you control a domain before issuing a certificate. There are two main challenge types: HTTP-01 and DNS-01.
1# HTTP-01 Challenge (most common)21. You request certificate for app.example.com32. Let's Encrypt gives you a random token43. You make token available at: http://app.example.com/.well-known/acme-challenge/TOKEN54. Let's Encrypt fetches the URL to verify65. If successful, certificate is issued78# DNS-01 Challenge (for wildcards)91. You request certificate for *.example.com102. Let's Encrypt gives you a random value113. You create TXT record: _acme-challenge.example.com124. Let's Encrypt checks DNS to verify135. If successful, wildcard certificate is issued1415# Certificates are valid for 90 days16# Renew when 30 days remain03Traefik with Let's Encrypt
Traefik has built-in Let's Encrypt support. It handles certificate issuance and renewal automatically.
1services: 2 traefik: 3 image: traefik:v3.04 command: 5 - "--providers.docker=true"6 - "--providers.docker.exposedbydefault=false"7 - "--entrypoints.web.address=:80"8 - "--entrypoints.websecure.address=:443"9 # Redirect HTTP to HTTPS10 - "--entrypoints.web.http.redirections.entrypoint.to=websecure"11 # Let's Encrypt configuration12 - "--certificatesresolvers.letsencrypt.acme.email=you@example.com"13 - "--certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json"14 - "--certificatesresolvers.letsencrypt.acme.httpchallenge.entrypoint=web"15 ports: 16 - "80:80"17 - "443:443"18 volumes: 19 - /var/run/docker.sock:/var/run/docker.sock:ro20 - letsencrypt:/letsencrypt2122 app: 23 image: myapp:1.024 labels: 25 - "traefik.enable=true"26 - "traefik.http.routers.app.rule=Host(`app.example.com`)"27 - "traefik.http.routers.app.entrypoints=websecure"28 - "traefik.http.routers.app.tls.certresolver=letsencrypt"2930volumes: 31 letsencrypt: Use the Let's Encrypt staging server while testing to avoid rate limits: --certificatesresolvers.letsencrypt.acme.caserver=https://acme-staging-v02.api.letsencrypt.org/directory
04Caddy with Automatic HTTPS
Caddy enables HTTPS by default—no configuration needed. Just specify your domain.
1services: 2 caddy: 3 image: caddy:2-alpine4 ports: 5 - "80:80"6 - "443:443"7 volumes: 8 - ./Caddyfile:/etc/caddy/Caddyfile9 - caddy_data:/data10 - caddy_config:/config1112volumes: 13 caddy_data: # Stores certificates14 caddy_config: 05Caddyfile for Multiple Services
The Caddyfile syntax is minimal. HTTPS is automatic for any domain you specify.
1# Caddyfile2{3 email you@example.com4}56app.example.com {7 reverse_proxy app:808}910api.example.com {11 reverse_proxy api:300012}1314# That's it! Caddy:15# - Gets certificates from Let's Encrypt16# - Redirects HTTP to HTTPS17# - Renews certificates automatically06Nginx Proxy Manager Setup
NPM provides a GUI for Let's Encrypt certificate management. Point-and-click SSL.
1services: 2 npm: 3 image: jc21/nginx-proxy-manager:latest4 ports: 5 - "80:80"6 - "443:443"7 - "81:81" # Admin UI8 volumes: 9 - npm_data:/data10 - npm_letsencrypt:/etc/letsencrypt1112volumes: 13 npm_data: 14 npm_letsencrypt: 1516# After setup:17# 1. Access http://your-ip:8118# 2. Login (admin@example.com / changeme)19# 3. Add Proxy Host20# 4. Go to SSL tab21# 5. Select "Request a new SSL Certificate"22# 6. Check "Force SSL"23# Done!07DNS Challenge for Wildcards
For wildcard certificates (*.example.com), you need DNS-01 challenge. This requires API access to your DNS provider.
1# Traefik with Cloudflare DNS2services: 3 traefik: 4 environment: 5 - CF_API_EMAIL=you@example.com6 - CF_API_KEY=${CLOUDFLARE_API_KEY}7 command: 8 - "--certificatesresolvers.letsencrypt.acme.dnschallenge=true"9 - "--certificatesresolvers.letsencrypt.acme.dnschallenge.provider=cloudflare"10 - "--certificatesresolvers.letsencrypt.acme.email=you@example.com"11 - "--certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json"1213 app: 14 labels: 15 - "traefik.http.routers.app.tls.certresolver=letsencrypt"16 - "traefik.http.routers.app.tls.domains[0].main=example.com"17 - "traefik.http.routers.app.tls.domains[0].sans=*.example.com"1819# Supported DNS providers:20# Cloudflare, Route53, DigitalOcean, Namecheap, Google Cloud DNS, etc.Wildcard certificates are useful when you frequently add subdomains and don't want to issue new certificates each time.
08Let's Encrypt Rate Limits
Let's Encrypt has rate limits to prevent abuse. Know them to avoid issues.
1# Rate Limits (as of 2024):2# - 50 certificates per registered domain per week3# - 5 duplicate certificates per week4# - 5 failed validations per hour per account5# - 300 new orders per account per 3 hours67# Avoid hitting limits:8# 1. Use staging server for testing9# 2. Persist certificate storage (don't lose acme.json)10# 3. Use wildcard certificates when possible11# 4. Don't restart proxy unnecessarily during setup1213# Check current status:14# https://tools.letsdebug.net/cert-searchIf you hit rate limits, you'll have to wait up to a week. Always use staging for testing.
09Troubleshooting Certificate Issues
Common issues and how to fix them.
1# Issue: Certificate not issued2# Check DNS is pointing to your server3dig +short app.example.com45# Check port 80 is open (required for HTTP challenge)6curl -I http://app.example.com/.well-known/acme-challenge/test78# Check Traefik logs9docker logs traefik 2>&1 | grep -i acme1011# Issue: Certificate expired12# Check certificate dates13echo | openssl s_client -connect app.example.com:443 2>/dev/null | openssl x509 -noout -dates1415# Force renewal (Traefik)16rm acme.json && docker compose restart traefik1718# Issue: Wrong certificate served19# Check what's in acme.json20cat acme.json | jq '.letsencrypt.Certificates[].domain'10Best Practices
1. Always persist certificate storage (acme.json or /data directory)
2. Use staging server during testing
3. Set up monitoring for certificate expiry
4. Have a backup certificate strategy
5. Use HTTP to HTTPS redirect
6. Enable HSTS after confirming HTTPS works
7. Consider wildcard certificates for many subdomains
1# Traefik with HSTS2services: 3 traefik: 4 labels: 5 # HSTS header6 - "traefik.http.middlewares.hsts.headers.stsSeconds=31536000"7 - "traefik.http.middlewares.hsts.headers.stsIncludeSubdomains=true"8 - "traefik.http.middlewares.hsts.headers.stsPreload=true"910 app: 11 labels: 12 - "traefik.http.routers.app.middlewares=hsts"