Skip to content

Docker Let's Encrypt SSL Certificate Renewal Fix

DodaTech Updated 2026-06-24 3 min read

In this tutorial, you'll learn about Docker Let's Encrypt SSL Certificate Renewal Fix. We cover key concepts, practical examples, and best practices.

Let's Encrypt SSL certificates expire every 90 days, and renewal fails when Certbot cannot reach port 80, the webroot path is wrong, or Docker containers block the ACME challenge. Without renewal, browsers show "NET::ERR_CERT_DATE_INVALID".

The Problem

certbot renew

Error:

Attempting to renew cert (example.com) from /etc/letsencrypt/renewal/example.com.conf failed
http-01 challenge failed for domain example.com
Error: Could not issue certificate

Or in Docker:

docker run certbot/certbot renew

Error:

Certbot failed to authenticate some domains (timed out)

Wrong Approach

# WRONG — running Certbot outside Docker while Nginx is inside
certbot certonly --webroot -d example.com
# Fails because Certbot cannot access the webroot inside the container

Right Approach

# Mount the webroot and Nginx config, share the network
docker run --rm \
  -v /docker/nginx/html:/usr/share/nginx/html \
  -v /docker/certbot/etc:/etc/letsencrypt \
  -v /docker/certbot/lib:/var/lib/letsencrypt \
  certbot/certbot certonly --webroot \
  -w /usr/share/nginx/html \
  -d example.com

Expected output:

Saving debug log to /var/log/letsencrypt/letsencrypt.log
Successfully received certificate.
Certificate is saved at: /etc/letsencrypt/live/example.com/fullchain.pem

Step-by-Step Fix

Step 1: Set up shared volumes in docker-compose

services:
  nginx:
    image: nginx
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx/conf:/etc/nginx/conf.d
      - ./nginx/html:/usr/share/nginx/html
      - ./certbot/etc:/etc/letsencrypt
      - ./certbot/lib:/var/lib/letsencrypt

  certbot:
    image: certbot/certbot
    volumes:
      - ./nginx/html:/usr/share/nginx/html
      - ./certbot/etc:/etc/letsencrypt
      - ./certbot/lib:/var/lib/letsencrypt
    entrypoint: "/bin/sh -c 'trap exit TERM; while :; do certbot renew; sleep 12h & wait $${!}; done;'"

Step 2: Obtain the initial certificate

docker compose run --rm certbot certonly --webroot \
  -w /usr/share/nginx/html -d example.com

Step 3: Configure Nginx to use the certificate

server {
    listen 443 ssl;
    server_name example.com;
    ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
}

Step 4: Test renewal manually

docker compose run --rm certbot renew --dry-run

Step 5: Automate renewal with a cron job

0 3 * * * cd /docker && docker compose run --rm certbot renew && docker compose exec nginx nginx -s reload

Prevention Tips

  • Mount the same webroot and cert volumes in both Nginx and Certbot containers
  • Always test renewal with --dry-run before relying on automation
  • Set up a cron job to run renewal and Nginx reload
  • Monitor certificate expiry with openssl x509 -checkend 86400
  • Use Certbot DNS plugins for domains behind firewalls that block port 80

Common Mistakes with letsencrypt ssl

  1. Overlapping type class instances that cause GHC to reject the program with ambiguous dispatch errors
  2. Non-exhaustive pattern matches that compile with warnings then crash at runtime
  3. Misunderstanding that String is [Char] with poor performance for large text operations

These mistakes appear frequently in real-world DOCKER code. DodaTech's contributors have identified these patterns through analysis of open-source projects and production systems.

Practice Exercise

Write a pure function that safely divides two integers using Maybe, then test it with edge cases like division by zero and negative numbers.

This exercise reinforces the concepts covered in this guide. Try implementing it before checking online solutions.

FAQ

### Why does Let's Encrypt renewal fail in Docker?

The ACME HTTP-01 challenge requires Let's Encrypt to reach your domain on port 80. If Nginx is in a Docker container, the challenge file must be accessible at the path mapped to the webroot. The most common cause is mismatched volume mounts between Nginx and Certbot.

Can I renew certificates without exposing port 80?

Yes. Use the DNS-01 challenge with a Certbot DNS plugin (e.g., certbot-dns-<a href="/web-servers-hosting/cloudflare/">cloudflare</a>, certbot-dns-route53). This creates a TXT record in DNS instead of requiring HTTP access. It works for domains behind firewalls or internal-only services.

How do I reload Nginx after certificate renewal automatically?

Add --deploy-hook "<a href="/devops/docker-compose/">docker compose</a> exec nginx nginx -s reload" to the Certbot renewal command, or run it from cron after renewal. Without a reload, Nginx continues serving the old certificate until a restart.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro