Here is my setup. It involves the LE secrets living in a docker volume that is shared between nginx and certbot, and nginx proxying the renewal requests to certbot, so you do not have to stop nginx while certbot does its validation.
nginx setup
proxy LE verififcation to certbot backend
Requests on port 80 to letsencrypt validation are forwarded to certbot, anything else gets redirecte to https.
(In case you are wondering why I define the proxy pass backend as a variable, see this SO answer)
server {
listen 80 default_server;
listen [::]:80 default_server;
server_name _;
location /.well-known/acme-challenge {
resolver 127.0.0.11 valid=30s;
set $upstream letsencrypt;
proxy_pass http://$upstream:80;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header X-Forwarded-Proto https;
}
location / {
return 301 https://$host$request_uri;
}
}
SSL setup
pretty much standard stuff here:
server {
listen 443 ssl;
server_name ${DOMAINNAME};
ssl_certificate /etc/letsencrypt/live/${DOMAINNAME}/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/${DOMAINNAME}/privkey.pem;
ssl_session_timeout 5m;
ssl_protocols TLSv1.2;
ssl_ciphers 'EECDH+AESGCM: EDH+AESGCM:AES256+EECDH:AES256+EDH';
ssl_prefer_server_ciphers on;
ssl_session_cache shared:SSL:10m;
ssl_dhparam dhparam.pem;
... your lcoation block goes here ...
}
docker-compose magic
certbot
Have a special "docker-compose-LE.yml" to single run certbot:
version: '3.4'
services:
letsencrypt:
image: certbot/certbot:latest
command: sh -c "certbot certonly --standalone -d ${DOMAINNAME} --text --agree-tos --email [email protected] --server https://acme-v02.api.letsencrypt.org/directory --rsa-key-size 4096 --verbose --keep-until-expiring --preferred-challenges=http"
entrypoint: ""
volumes:
- "letsencrypt:/etc/letsencrypt"
environment:
- TERM=xterm
volumes:
letsencrypt:
name: letsencrypt_keys
By running "docker-compose -f docker-compose-LE.yml up" you will create and validate a certificate. You can use the same command to renew the certificate, certbot is that smart. You may run this command as often as you like (daily), because it will only renew your certificate when it is about to expire.
See "caveat" below before running this command the first time.
nginx
in docker-compose.yml mount the certificates from a volume. That volume has already been created by letsencrypt, so declare it as external.
services:
nginx:
image: nginx:1.18
restart: always
volumes:
- letsencrypt:/etc/letsencrypt:ro
volumes:
letsencrypt:
external:
name: letsencrypt_keys
caveat
This method causes a chicken-egg-problem when creating the certifivate the first time: Without a cert file nginx won't start and can't proxy the LE validation. No nginx means no certificate, and no certificate means no nginx.
To get around this you have to do the very first call of certbot without nginx and using certbots internal http server exposed. So the first time you run certbot add these lines to docker-compose-LE.yml:
letsencrypt:
ports:
- "80:80"
cert renewal
Simply run these two command in a daily cronjob:
docker-compose -f docker-compose-LE.yml up
Will check the certificate and start renewal process once it is due. The now running nginx will proxy the certification validation to certbot.
docker-compose exec nginx nginx -s reload
Once the certificate is updated inplace inside the docker volume certbot and nginx are sharing, simply send a SIGHUP to nginx so it reloads the cert files without interrupting service.
resolver 127.0.0.11
is allowing nginx to find the letsencrypt container even though its a separate docker-compose. And it will be able to transfer to letsencrypt port 80 even though we aren't exposing LE:80 externally after first run. So for the brief period letsencrypt is up during periodic renewal phase, nginx redirect will pass to letsencrypt during the challenge period. – Gynous