Skip to content

Instantly share code, notes, and snippets.

@sergiks
Last active October 18, 2024 05:56
Show Gist options
  • Save sergiks/4c1ccddc097e61e6fe5e45c53072a944 to your computer and use it in GitHub Desktop.
Save sergiks/4c1ccddc097e61e6fe5e45c53072a944 to your computer and use it in GitHub Desktop.
Let's Encrypt wildcard certificates in docker

NGINX and Certbot example with CloudFlare API in Docker

Sample config files to demonstrate seup that creates and updates free SSL certificates from Let's Encrypt given that the domains are maintained at CloudFlare service.

How it works

Certbot verifies domains ownership by accessing CloudFlare API that adds temporary TXT DNS records. To enable it You must provide your CloudFlare API token. More details in documentation for dns-cloudflare Certbot plugin.

Certbot saves created certificates in Docker volume certbot_etc. Pay attention to output of the certbot run - it mentions path to the created certificates.

Steps to reproduce

  1. Setup docker, docker-compose, domains, nginx – make your website work via plain HTTP.

  2. docker-compose run certbot to create certificates. It will wait for 60 seconds in the middle. Note the output of the command – it will contain actual paths to certificates.

  3. Update nginx.conf to use the right paths to certificates.

  4. ssl-dhparams.pem is like a cryptographic "salt" - required by some of algorithms. Copy that file from somewhere or generate one with command: openssl dhparam -out ssl-dhparams.pem 2048 - that will take some minutes to generate.

    Copy the file into certbot_etc volume by command similar to: docker cp ./ssl-dhparams.pem my_app_nginx_1:/etc/letsencrypt/ssl-dhparams.pem supposing the running NGINX container name is "my_app_nginx_1" - check with docker ps

  5. Test if NGINX config is OK: docker-compose exec nginx nginx -t

  6. Make NGINX reload the updated config: docker-compose exec nginx nginx -s reload

# Cloudflare API credentials used by Certbot
# How to generate API token:
# https://developers.cloudflare.com/api/tokens/create
dns_cloudflare_api_token = XXXXXXXXXXXXXXXXXXXX
### Old insecure way. Not recommended.
# dns_cloudflare_email = [email protected]
# dns_cloudflare_api_key = XXXXXXXXXXXXXXXXXXXX
version: '3'
services:
certbot:
image: certbot/dns-cloudflare
volumes:
- certbot_etc:/etc/letsencrypt
- ./cloudflare.ini:/root/cloudflare.ini
command: >-
certonly --dns-cloudflare
--dns-cloudflare-credentials /root/cloudflare.ini
--dns-cloudflare-propagation-seconds 15
--email [email protected]
--agree-tos --no-eff-email
--force-renewal
-d domain1.com
-d *.domain1.com
-d domain2.com
-d *.domain2.com
nginx:
image: nginx:alpine
ports:
- "80:80"
- "443:443"
restart: "always"
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "10"
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf
- certbot_etc:/etc/letsencrypt
volumes:
certbot_etc:
worker_processes 1;
events {
worker_connections 512;
}
http {
server {
listen 80;
root /var/www/;
index index.html;
### SSL LetsEncrypt
listen 443 ssl http2;
listen [::]:443 ssl http2;
ssl_certificate /etc/letsencrypt/live/YOUR_DOMAIN/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/YOUR_DOMAIN/privkey.pem;
### Create the dhparam file:
### openssl dhparam -out ssl-dhparams.pem 2048
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
ssl_session_timeout 1d;
ssl_session_cache shared:MozSSL:1m; # about 4000 sessions
ssl_session_tickets off;
# intermediate configuration
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384;
ssl_prefer_server_ciphers off;
# HSTS (ngx_http_headers_module is required) (63072000 seconds)
add_header Strict-Transport-Security "max-age=63072000" always;
}
}
@red-avtovo
Copy link

@sergiks thank you very much for the manual.
I have one question though: why did you mount nginx.conf to certbot?

@sergiks
Copy link
Author

sergiks commented Oct 8, 2021

why did you mount nginx.conf to certbot?

@red-avtovo You are welcome. This gist is quite outdated by now.
CloudFlare has introduced API Tokens to replace the less secure global API Key. It is better to create a token using Zone:DNS:Edit template.
NGINX config file is not required at all in the certbot container. Thanks for spotting! Will remove.

@andreigavril27
Copy link

@sergiks will the certificate get always renewed when the container starts in the docker compose file? I've looked into the official doc of the container and there are no details about it.

@sergiks
Copy link
Author

sergiks commented Mar 16, 2022

@andreigavril27 I think it will, as there is the --force-renewal flag present. The documentation for this option says:

If a certificate already exists for the requested renew it now, regardless of whether it near expiry. (Often --keep-until-expiring is more appropriate). Also implies --expand. (default: False)

@tyrellm04
Copy link

tyrellm04 commented Jun 26, 2024

Hello, it would seem I'm missing something somewhere along the way. I have created the API key that has read and edit permissions on DNS, I have run the CURL to confirm that the key authenticates correctly.
image

However, when I run the certbot container with the following config:
image

and then I check the certbot logs I see the following error (Image keeps failing to attach)
`Saving debug log to /var/log/letsencrypt/letsencrypt.log
Account registered.
Requesting a certificate for DOMAIN1 and SUBDOMAIN1
Unsafe permissions on credentials configuration file: /root/cloudflare.ini
Waiting 15 seconds for DNS changes to propagate

Certbot failed to authenticate some domains (authenticator: dns-cloudflare). The Certificate Authority reported these problems:
Domain: DOMAIN1
Type: dns
Detail: DNS problem: NXDOMAIN looking up TXT for _acme-challenge.DOMAIN - check that a DNS record exists for this domain

Hint: The Certificate Authority failed to verify the DNS TXT records created by --dns-cloudflare. Ensure the above domains are hosted by this DNS provider, or try increasing --dns-cloudflare-propagation-seconds (currently 15 seconds).

Some challenges have failed.
Ask for help or search for solutions at https://community.letsencrypt.org. See the logfile /var/log/letsencrypt/letsencrypt.log or re-run Certbot with -v for more details.`

I blurred the domain, but yes, it's referencing it in a .env file and it's pulling the correct domain that's managed by cloudflare.

Unless I'm missing something, I thought the certbot container using the API key that has read and edit permissions to DNS was supposed to validate with CF and then make the TXT record for the challenge success?

This is what the API Key perms look like in CF (Yes, I've verified the domain in the error matches exactly with the domain in the below CF screenshot).
image

Can someone please point me in the right direction as to what detail I'm missing?

[Edit] - I missed a blur!

Update:

After removing the '-' after "Command: >" (Line 9 in the example YAML) seems to have corrected the issue. Time to make sure I have the certs landing where the webserver conf is looking.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment