Using Certbot in Docker Containers for HTTP-01 Challenge (Preparing for Zero-Downtime Renewal)

2025-06-07

Introduction

After building a reverse proxy in a Docker container, the next step is to secure the connection between the outside and the reverse proxy using SSL via Let's Encrypt.

Created a minimal reverse proxy using Docker. (Memo)
 

There are several methods to issue and renew certificates with Let's Encrypt. The simplest one is the "Standalone" method, which temporarily stops running services. Certbot launches its own web server to perform the ACME challenge, requiring ports 80 and 443 and thus causing conflicts with running services.

To achieve a zero-downtime renewal, we use methods like HTTP-01 or DNS-01 challenges. This article uses the common HTTP-01 challenge via the Webroot method.

Certbot places the ACME challenge files into the webroot directory. Then Let's Encrypt tries to access http://<YOUR_DOMAIN>/.well-known/acme-challenge/<TOKEN>. If a valid response is received, the certificate is issued.

https://letsencrypt.org/en/docs/challenge-types/

Important Note

Due to editor limitations, slashes in some paths are shown as full-width.
Please replace /etc with standard slashes /etc when copying the commands.

Using Certbot for HTTP-01 Challenge

This article covers up to the initial certificate issuance.
The structure is shown below:

Architecture Diagram

Simplified here, but we will create reverse-proxy.yml and webapp01.yml using docker-compose, and launch each container. External requests to ports 80/443 are received by the reverse proxy, which forwards them to the web service in webapp01 based on the requested domain.

SSL is only applied between the external client and the reverse proxy.
Volumes are bind-mounted for the ACME challenge directory, certificate storage, and symbolic link paths.

Let’s walk through the setup steps.

Installing Certbot

Certbot is a tool for obtaining SSL certificates and supports Let's Encrypt.

Install the Certbot package:

sudo apt install certbot

Since we are using the Webroot method, additional plugins for web servers like Apache or Nginx are not needed.
Example: sudo apt install python3-certbot-nginx (not required)

Add to docker-compose.yml for Authentication

First, create the directory for the ACME challenge.

Assume the project directory is /home/docker.
Each docker-compose project has its own folder.

Since SSL is applied to the reverse proxy server, we configure it accordingly.

・/home/docker/reverse-proxy/docker-compose.yml

services:
 reverse-proxy:
  image: nginx:alpine
  ports:
   - "80:80"
  volumes:
   - ./conf.d:/etc/nginx/conf.d
   - ./certbot/www:/var/www/certbot
   - ./certbot/conf:/etc/letsencrypt
   - ./certbot/conf/archive:/etc/letsencrypt/archive

The network and some configs are omitted for brevity.

Bind-mounted volumes:

  • ./conf.d/etc/nginx/conf.d: Nginx config files
  • ./certbot/www/var/www/certbot: ACME challenge files
  • ./certbot/conf/etc/letsencrypt: symbolic links for certificates
  • ./certbot/conf/archive/etc/letsencrypt/archive: certificate storage

Create Directory for ACME Challenge

Next, create the directory:
/home/docker/reverse-proxy/certbot/www/.well-known/acme-challenge/

Configure Nginx

Place the reverse proxy config file (e.g., reverse-proxy.conf) in /home/docker/reverse-proxy/conf.d:

server {
 listen 80;
 server_name example(domain);
 location /.well-known/acme-challenge/ {
   root /var/www/certbot;
 }
 location / {
   # other settings
 }
}

Verify with --dry-run

Let’s Encrypt limits the number of real requests, so verify first with --dry-run:

sudo certbot certonly --webroot -w /home/docker/reverse-proxy/certbot/www -d example(domain) --dry-run

Issue the Certificate

Use the following command to issue the certificate (example domain):

sudo certbot certonly --webroot -w /home/docker/reverse-proxy/certbot/www/ -d example(domain)

Make sure there’s no error and the certificate is stored at the specified location.

Conclusion

It took quite a bit of time to get this working.
When running the issuance command, I received a 404 error. Although I could access test .html files placed under /.well-known/acme-challenge/, the error persisted.
I tried tweaking autoindex on;, default_type text/plain;, and permissions settings.

In the end, the problem was that I specified the container's directory in the Certbot command instead of the host’s directory. Certbot runs on the host, so it needs to reference the host’s path.

Reference

https://letsencrypt.org/en/docs/challenge-types/

Related Articles

How to enable HTTPS with HTTP-01 Challenge using Docker Compose Reverse Proxy
Automating SSL updates for custom domains on Sakura VPS using Docker Compose Reverse Proxy