Install Docker on Ubuntu
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -
sudo add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable"
Update the Ubuntu package database
sudo apt-get update
install Docker
sudo apt-get install -y docker-ce
Install Docker Compose on Ubuntu
sudo curl -L https://github.com/docker/compose/releases/download/1.18.0/docker-compose-`uname -s`-`uname -m` -o /usr/local/bin/docker-compose
sudo chmod +x /usr/local/bin/docker-compose
A Better Solution: Run Let’s Encypt’s Certbot in a Docker Container
On your server, create a new Directory:
sudo mkdir -p /docker/letsencrypt-docker-nginx/src/letsencrypt/letsencrypt-site
Then, create a new docker-compose.yml file
sudo nano /docker/letsencrypt-docker-nginx/src/letsencrypt/docker-compose.yml
docker-compose.yml
version: '3.1'
services:
letsencrypt-nginx-container:
container_name: 'letsencrypt-nginx-container'
image: nginx:latest
ports:
- "80:80"
volumes:
- ./nginx.conf:/etc/nginx/conf.d/default.conf
- ./letsencrypt-site:/usr/share/nginx/html
networks:
- docker-network
networks:
docker-network:
driver: bridge
Then, create a configuration file for nginx
sudo nano /docker/letsencrypt-docker-nginx/src/letsencrypt/nginx.conf
nginx.conf
server {
listen 80;
listen [::]:80;
server_name xxx.com www.xxx.com;
location ~ /.well-known/acme-challenge {
allow all;
root /usr/share/nginx/html;
}
root /usr/share/nginx/html;
index index.html;
}
Next, create an index.html file
sudo nano /docker/letsencrypt-docker-nginx/src/letsencrypt/letsencrypt-site/index.html
index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>Let's Encrypt First Time Cert Issue Site</title>
</head>
<body>
<h1>Hello!</h1>
<p>
This is the temporary site that will only be used for the very first time SSL certificates are issued by Let's Encrypt's
certbot.
</p>
</body>
</html>
Before running the Certbot command, spin up a Nginx container in Docker to ensure the temporary Nginx site is up and running
cd /docker/letsencrypt-docker-nginx/src/letsencrypt
sudo docker-compose up -d
Then, open up a browser and visit the domain to ensure that the Docker container is up and running and accessible. As stated earlier, it’s not necessary to have a default index.html page for this container, but it makes testing the container a lot easier, so I always create one.
Run the staging command for issuing a new certificate:
sudo docker run -it --rm \
-v /docker-volumes/etc/letsencrypt:/etc/letsencrypt \
-v /docker-volumes/var/lib/letsencrypt:/var/lib/letsencrypt \
-v /docker/letsencrypt-docker-nginx/src/letsencrypt/letsencrypt-site:/data/letsencrypt \
-v "/docker-volumes/var/log/letsencrypt:/var/log/letsencrypt" \
certbot/certbot \
certonly --webroot \
--register-unsafely-without-email --agree-tos \
--webroot-path=/data/letsencrypt \
--staging \
-d xxx.com -d www.xxx.com
After executing the above command, you should get the following output which should indicate everything ran successfully.
You can also get some additional information about certificates for your domain by running the Certbot certificates command:
sudo docker run --rm -it --name certbot \
-v /docker-volumes/etc/letsencrypt:/etc/letsencrypt \
-v /docker-volumes/var/lib/letsencrypt:/var/lib/letsencrypt \
-v /docker/letsencrypt-docker-nginx/src/letsencrypt/letsencrypt-site:/data/letsencrypt \
certbot/certbot \
--staging \
certificates
If the staging command executed successfully, execute the command to return a live certificate
First, clean up staging artifacts:
sudo rm -rf /docker-volumes/
And then request a production certificate: (note that it’s a good idea to supply your email address so that Let’s Encrypt can send expiry notifications)
sudo docker run -it --rm \
-v /docker-volumes/etc/letsencrypt:/etc/letsencrypt \
-v /docker-volumes/var/lib/letsencrypt:/var/lib/letsencrypt \
-v /docker/letsencrypt-docker-nginx/src/letsencrypt/letsencrypt-site:/data/letsencrypt \
-v "/docker-volumes/var/log/letsencrypt:/var/log/letsencrypt" \
certbot/certbot \
certonly --webroot \
--email youremail@domain.com --agree-tos --no-eff-email \
--webroot-path=/data/letsencrypt \
-d xxx.com -d www.xxx.com
If everything ran successfully, run a docker-compose down command to stop the temporary Nginx site
cd /docker/letsencrypt-docker-nginx/src/letsencrypt
sudo docker-compose down
Set up Your Production Site to Run in a Nginx Docker Container
sudo mkdir -p /docker/letsencrypt-docker-nginx/src/production/production-site
sudo mkdir -p /docker/letsencrypt-docker-nginx/src/production/dh-param
docker-compose.yml
version: '3.1'
services:
production-nginx-container:
container_name: 'production-nginx-container'
image: nginx:latest
ports:
- "80:80"
- "443:443"
volumes:
- ./production.conf:/etc/nginx/conf.d/default.conf
- ./production-site:/usr/share/nginx/html
- ./dh-param/dhparam-2048.pem:/etc/ssl/certs/dhparam-2048.pem
- /docker-volumes/etc/letsencrypt/live/xxx.com/fullchain.pem:/etc/letsencrypt/live/xxx.com/fullchain.pem
- /docker-volumes/etc/letsencrypt/live/xxx.com/privkey.pem:/etc/letsencrypt/live/xxx.com/privkey.pem
networks:
- docker-network
networks:
docker-network:
driver: bridge
Next, create the Nginx configuration file for the production site
sudo nano /docker/letsencrypt-docker-nginx/src/production/production.conf
production.conf
server {
listen 80;
listen [::]:80;
server_name xxx.com www.xxx.com;
location / {
rewrite ^ https://$host$request_uri? permanent;
}
#for certbot challenges (renewal process)
location ~ /.well-known/acme-challenge {
allow all;
root /data/letsencrypt;
}
}
#https://xxx.com
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name ohhaithere.com;
server_tokens off;
ssl_certificate /etc/letsencrypt/live/xxx.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/xxx.com/privkey.pem;
ssl_buffer_size 8k;
ssl_dhparam /etc/ssl/certs/dhparam-2048.pem;
ssl_protocols TLSv1.2 TLSv1.1 TLSv1;
ssl_prefer_server_ciphers on;
ssl_ciphers ECDH+AESGCM:ECDH+AES256:ECDH+AES128:DH+3DES:!ADH:!AECDH:!MD5;
ssl_ecdh_curve secp384r1;
ssl_session_tickets off;
# OCSP stapling
ssl_stapling on;
ssl_stapling_verify on;
resolver 8.8.8.8;
return 301 https://www.xxx.com$request_uri;
}
#https://www.xxx.com
server {
server_name www.xxx.com;
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_tokens off;
ssl on;
ssl_buffer_size 8k;
ssl_dhparam /etc/ssl/certs/dhparam-2048.pem;
ssl_protocols TLSv1.2 TLSv1.1 TLSv1;
ssl_prefer_server_ciphers on;
ssl_ciphers ECDH+AESGCM:ECDH+AES256:ECDH+AES128:DH+3DES:!ADH:!AECDH:!MD5;
ssl_ecdh_curve secp384r1;
ssl_session_tickets off;
# OCSP stapling
ssl_stapling on;
ssl_stapling_verify on;
resolver 8.8.8.8 8.8.4.4;
ssl_certificate /etc/letsencrypt/live/xxx.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/xxx.com/privkey.pem;
root /usr/share/nginx/html;
index index.html;
}
Generate a 2048 bit DH Param file
sudo openssl dhparam -out /docker/letsencrypt-docker-nginx/src/production/dh-param/dhparam-2048.pem 2048
Copy your site content into the mapped directory:
/docker/letsencrypt-docker-nginx/src/production/production-site/
Spin up the production site in a Docker container:
cd /docker/letsencrypt-docker-nginx/src/production
sudo docker-compose up -d
How to Renew Let’s Encrypt SSL Certificates with Certbot and Docker
Earlier, we placed the following section in the production Nginx configuration file:
location ~ /.well-known/acme-challenge {
allow all;
root /usr/share/nginx/html;
}
The production site’s docker-compose file then maps a volume into the Nginx container that can be used for challenge requests:
production-nginx-container:
container_name: 'production-nginx-container'
image: nginx:latest
ports:
- "80:80"
- "443:443"
volumes:
#other mapped volumes...
#for certbot challenges
- /docker-volumes/data/letsencrypt:/data/letsencrypt
networks:
- docker-network
To add a crontab, run the following commands:
sudo crontab -e
Place the following at the end of the file, then close and save it.
0 23 * * * docker run --rm -it --name certbot -v "/docker-volumes/etc/letsencrypt:/etc/letsencrypt" -v "/docker-volumes/var/lib/letsencrypt:/var/lib/letsencrypt" -v "/docker-volumes/data/letsencrypt:/data/letsencrypt" -v "/docker-volumes/var/log/letsencrypt:/var/log/letsencrypt" certbot/certbot renew --webroot -w /data/letsencrypt --quiet && docker kill --signal=HUP production-nginx-container