docker.recipes
Networking9 min read

Let's Encrypt with Docker: Automatic TLS Explained

Set up automatic SSL/TLS certificates for your Docker services using Let's Encrypt.

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.com
32. Let's Encrypt gives you a random token
43. You make token available at: http://app.example.com/.well-known/acme-challenge/TOKEN
54. Let's Encrypt fetches the URL to verify
65. If successful, certificate is issued
7
8# DNS-01 Challenge (for wildcards)
91. You request certificate for *.example.com
102. Let's Encrypt gives you a random value
113. You create TXT record: _acme-challenge.example.com
124. Let's Encrypt checks DNS to verify
135. If successful, wildcard certificate is issued
14
15# Certificates are valid for 90 days
16# Renew when 30 days remain

03Traefik 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.0
4 command:
5 - "--providers.docker=true"
6 - "--providers.docker.exposedbydefault=false"
7 - "--entrypoints.web.address=:80"
8 - "--entrypoints.websecure.address=:443"
9 # Redirect HTTP to HTTPS
10 - "--entrypoints.web.http.redirections.entrypoint.to=websecure"
11 # Let's Encrypt configuration
12 - "--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:ro
20 - letsencrypt:/letsencrypt
21
22 app:
23 image: myapp:1.0
24 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"
29
30volumes:
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-alpine
4 ports:
5 - "80:80"
6 - "443:443"
7 volumes:
8 - ./Caddyfile:/etc/caddy/Caddyfile
9 - caddy_data:/data
10 - caddy_config:/config
11
12volumes:
13 caddy_data: # Stores certificates
14 caddy_config:

05Caddyfile for Multiple Services

The Caddyfile syntax is minimal. HTTPS is automatic for any domain you specify.
1# Caddyfile
2{
3 email you@example.com
4}
5
6app.example.com {
7 reverse_proxy app:80
8}
9
10api.example.com {
11 reverse_proxy api:3000
12}
13
14# That's it! Caddy:
15# - Gets certificates from Let's Encrypt
16# - Redirects HTTP to HTTPS
17# - Renews certificates automatically

06Nginx 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:latest
4 ports:
5 - "80:80"
6 - "443:443"
7 - "81:81" # Admin UI
8 volumes:
9 - npm_data:/data
10 - npm_letsencrypt:/etc/letsencrypt
11
12volumes:
13 npm_data:
14 npm_letsencrypt:
15
16# After setup:
17# 1. Access http://your-ip:81
18# 2. Login (admin@example.com / changeme)
19# 3. Add Proxy Host
20# 4. Go to SSL tab
21# 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 DNS
2services:
3 traefik:
4 environment:
5 - CF_API_EMAIL=you@example.com
6 - 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"
12
13 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"
18
19# 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 week
3# - 5 duplicate certificates per week
4# - 5 failed validations per hour per account
5# - 300 new orders per account per 3 hours
6
7# Avoid hitting limits:
8# 1. Use staging server for testing
9# 2. Persist certificate storage (don't lose acme.json)
10# 3. Use wildcard certificates when possible
11# 4. Don't restart proxy unnecessarily during setup
12
13# Check current status:
14# https://tools.letsdebug.net/cert-search

If 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 issued
2# Check DNS is pointing to your server
3dig +short app.example.com
4
5# Check port 80 is open (required for HTTP challenge)
6curl -I http://app.example.com/.well-known/acme-challenge/test
7
8# Check Traefik logs
9docker logs traefik 2>&1 | grep -i acme
10
11# Issue: Certificate expired
12# Check certificate dates
13echo | openssl s_client -connect app.example.com:443 2>/dev/null | openssl x509 -noout -dates
14
15# Force renewal (Traefik)
16rm acme.json && docker compose restart traefik
17
18# Issue: Wrong certificate served
19# Check what's in acme.json
20cat 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 HSTS
2services:
3 traefik:
4 labels:
5 # HSTS header
6 - "traefik.http.middlewares.hsts.headers.stsSeconds=31536000"
7 - "traefik.http.middlewares.hsts.headers.stsIncludeSubdomains=true"
8 - "traefik.http.middlewares.hsts.headers.stsPreload=true"
9
10 app:
11 labels:
12 - "traefik.http.routers.app.middlewares=hsts"