Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Let's encrypt SSL certificates using certbot in docker

Directories on host machine:

  • /data/certbot/letsencrypt

  • /data/certbot/www

  • Nginx server in docker container

docker run -d --name nginx \
    ...
    -v /data/certbot/letsencrypt:/etc/letsencrypt
    -v /data/certbot/www:/var/www/certbot
    nginx
    

config file for your site

server {
  listen 80;
  server_name mysite.com;

  location /.well-known/acme-challenge/ {
    root /var/www/certbot;
  }

  location / {
      return 301 https://$host$request_uri;
  }
}


server {
  listen 443 ssl;
  server_name mysite.com;

  access_log /var/log/nginx/access.log combined_ssl;

  ssl_certificate /etc/letsencrypt/live/mysite.com/fullchain.pem;
  ssl_certificate_key /etc/letsencrypt/live/mysite.com/privkey.pem;

  #include /data/letsencrypt/options-ssl-nginx.conf;
  #ssl_dhparam /data/letsencrypt/ssl-dhparams.pem;

  location / {
      set $upstream "site_upstream";

      proxy_set_header X-Real-IP $remote_addr;
      proxy_set_header Host $http_host;
      proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

      proxy_set_header X-Real-Port $server_port;
      proxy_set_header X-Real-Scheme $scheme;
      proxy_set_header X-NginX-Proxy true;
      proxy_set_header X-Forwarded-Proto $scheme;
      proxy_set_header X-Forwarded-Ssl on;

      expires off;

      proxy_pass http://$upstream;
  }
}

upstream site_upstream{
    server 51.1.0.20:80;
}

  • run certbot
docker run --rm --name temp_certbot \
    -v /data/certbot/letsencrypt:/etc/letsencrypt \
    -v /data/certbot/www:/tmp/letsencrypt \
    -v /data/servers-data/certbot/log:/var/log \
    certbot/certbot:v1.8.0 \
    certonly --webroot --agree-tos --renew-by-default \
    --preferred-challenges http-01 --server https://acme-v02.api.letsencrypt.org/directory \
    --text --email mxdevit@gmail.com \
    -w /tmp/letsencrypt -d mysite.com

it will create new certificates in /data/certbot/letsencrypt/live/mysite.com/.

restart nginx

docker restart nginx

Script to manage SSL certificates

/data/certbot/ssl_update.sh

  • generates a self-signed certificate if certificate doesn't exist
  • renew certificates with Let's Encrypt if certificate expires or about to expire

see the script below.

inspired by https://github.com/vdhpieter/docker-letsencrypt-webroot.

  • update certificates

ssl_mysite.sh

#!/bin/bash

export CERT_DIR_PATH="/data/certbot/letsencrypt";
export WEBROOT_PATH="/data/certbot/www";
export LE_RENEW_HOOK="docker restart nginx"; # <--- change to your nginx server docker container name
export DOMAINS="mysite.com";
export EMAIL="myemail@gmail.com";
export EXP_LIMIT="30";
export CHECK_FREQ="30";
export CHICKENEGG="1";
export STAGING="0";

bash /data/certbot/ssl_update.sh
                  
#!/bin/bash
if [[ -z $DOMAINS ]]; then
echo "No domains set, please fill -e 'DOMAINS=example.com www.example.com'"
exit 1
fi
if [[ -z $EMAIL ]]; then
echo "No email set, please fill -e 'EMAIL=your@email.tld'"
exit 1
fi
if [[ -z $CERT_DIR_PATH ]]; then
echo "No cert dir path set, please fill -e 'CERT_DIR_PATH=/etc/letsencrypt'"
exit 1
fi
if [[ -z $WEBROOT_PATH ]]; then
echo "No webroot path set, please fill -e 'WEBROOT_PATH=/tmp/letsencrypt'"
exit 1
fi
if [[ $STAGING -eq 1 ]]; then
echo "Using the staging environment"
ADDITIONAL="--staging"
fi
DARRAYS=(${DOMAINS})
EMAIL_ADDRESS=${EMAIL}
LE_DOMAINS=("${DARRAYS[*]/#/-d }")
exp_limit="${EXP_LIMIT:-30}"
check_freq="${CHECK_FREQ:-30}"
le_hook()
{
command=$(echo $LE_RENEW_HOOK)
echo "[INFO] Run: $command"
eval $command
}
le_fixpermissions() {
echo "[INFO] Fixing permissions"
chown -R ${CHOWN:-root:root} ${CERT_DIR_PATH}
find ${CERT_DIR_PATH} -type d -exec chmod 755 {} \;
find ${CERT_DIR_PATH} -type f -exec chmod ${CHMOD:-644} {} \;
}
le_renew() {
docker run --rm --name temp_certbot \
-v "${CERT_DIR_PATH}:/etc/letsencrypt" \
-v "${WEBROOT_PATH}:/tmp/letsencrypt" \
-v "/data/servers-data/certbot/log:/var/log" \
certbot/certbot:v1.8.0 certonly --webroot --agree-tos --renew-by-default \
--preferred-challenges http-01 \
--server https://acme-v02.api.letsencrypt.org/directory --text ${ADDITIONAL} \
--email ${EMAIL_ADDRESS} -w /tmp/letsencrypt ${LE_DOMAINS}
le_fixpermissions
le_hook
}
le_check() {
cert_file="$CERT_DIR_PATH/live/$DARRAYS/fullchain.pem";
echo "START check";
echo "file: $cert_file";
if [[ -e $cert_file ]]; then
exp=$(date -d "`openssl x509 -in $cert_file -text -noout|grep "Not After"|cut -c 25-`" +%s)
datenow=$(date -d "now" +%s)
days_exp=$[ ( $exp - $datenow ) / 86400 ]
echo "Checking expiration date for $DARRAYS..."
if [ "$days_exp" -gt "$exp_limit" ] ; then
echo "The certificate is up to date, no need for renewal ($days_exp days left)."
else
echo "The certificate for $DARRAYS is about to expire soon. Starting webroot renewal script..."
le_renew
echo "Renewal process finished for domain $DARRAYS"
fi
echo "Checking domains for $DARRAYS..."
domains=($(openssl x509 -in $cert_file -text -noout | grep -oP '(?<=DNS:)[^,]*'))
new_domains=($(
for domain in ${DARRAYS[@]}; do
[[ " ${domains[@]} " =~ " ${domain} " ]] || echo $domain
done
))
if [ -z "$new_domains" ] ; then
echo "The certificate have no changes, no need for renewal"
else
echo "The list of domains for $DARRAYS certificate has been changed. Starting webroot renewal script..."
le_renew
echo "Renewal process finished for domain $DARRAYS"
fi
else
echo "[INFO] certificate file not found for domain $DARRAYS. Starting webroot initial certificate request script..."
if [[ $CHICKENEGG -eq 1 ]]; then
echo "Making a temporary self signed certificate to prevent chicken and egg problems"
mkdir -p $CERT_DIR_PATH/live/$DARRAYS
openssl req -x509 -newkey rsa:4096 -sha256 -nodes -keyout "$CERT_DIR_PATH/live/$DARRAYS/privkey.pem" -out "${cert_file}" -subj "/CN=example.com" -days 1
fi
le_renew
echo "Certificate request process finished for domain $DARRAYS"
fi
}
echo "--- start. $(date)";
le_check $1
@kdaveid
Copy link

kdaveid commented Jan 26, 2022

Certbot creates challenge files at /var/www/ not at /var/www/certbot/ anymore.

So the full path that nginx will have to serve is at /var/www/.well-known/acme-challenge/ ...

@vatavale
Copy link

vatavale commented May 21, 2022

Certbot creates challenge files at /var/www/ not at /var/www/certbot/ anymore.

@kdaveid Thank you for this!

@steklopod
Copy link

Certbot creates challenge files at /var/www/ not at /var/www/certbot/ anymore.

So the full path that nginx will have to serve is at /var/www/.well-known/acme-challenge/ ...

Disinformation. root /var/www/certbot; is valid

@timkolloch
Copy link

But if I configure my nginx like that it does not start because the ssl certificates are missing.
If I run the certbot container I get following error:

requests.exceptions.ConnectionError: HTTPSConnectionPool(host='acme-v02.api.letsencrypt.org', port=443): Max retries exceeded with url: /directory (Caused by NewConnectionError('<urllib3.connection.HTTPSConnection object at 0x7fb877dfb3a0>: Failed to establish a new connection: [Errno -3] Try again'))

Any idea how I can solve that?

@cidudp
Copy link

cidudp commented Apr 24, 2023

port 443: Connection refused

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