Professional Documents
Culture Documents
How To Set Up Free SSL Certificates From Let's Encrypt Using Docker and Nginx PDF
How To Set Up Free SSL Certificates From Let's Encrypt Using Docker and Nginx PDF
---
I've been using Free SSL/TLS certificates from Let's Encrypt for
about 18 months. Recently, I started combining Docker with Let's
Encrypt. The tooling that Let's Encrypt's Certbot provides is
extensive, and the whole experience of using Docker with Let's
Encrypt is fantastic.
If you've been looking for an easy to read and follow guide that
will allow you to dockerize Let's Encrypt on Ubuntu, you've come
to the right place. Even if you're not interested in running Let's
Encrypt in Docker, this post will still show you how to obtain free
SSL/TLS certificates from Let's Encrypt as the certificate acquisition
steps are the same.
Youtube Tutorial
I created a Youtube tutorial that shows how to use Docker and Let's
Encrypt to issue free SSL certificates.
Set Up Free SSL Certi,cates from Let's Encrypt using Docker and Ngi…
Add the GPG key and add the Docker repository from APT sources
This certificate consists of a private key and a public key and both
these keys are saved in a Let's Encrypt folder on your server. Your
web server, which is Nginx in this instance, loads both the private
and public keys in order to configure SSL/TLS for your site.
The public key is not secure and is sent to every browser that
makes a request to your site, while the private key is secure and is
not shared with clients. I will cover the certificate issuance process
in more detail in a subsequent post as it is an in-depth topic that
requires a separate post.
The Certbot software gets updated with new releases often. If you
install Certbot on your server, this would involve uninstalling and
re-installing Certbot every time you need to update the Certbot
agent, which makes it a perfect candidate to run in a Docker
container. If Certbot is updated, a new image will automatically
be pulled from the Docker registry the next time the agent runs in
a docker container.
By dockerizing Certbot, the process for obtaining Let's Encrypt
certificates will now only consist of 2 parts:
2. Set up a cron job that will execute another Docker run script
on a periodic basis. The script will look almost identical to
the one in the first step, give or take a parameter or two.
5. If the token at the endpoint matches the token that was sent
to the Certbot agent from the Let's Encrypt CA, the challenge
request was successful and Let's Encrypt knows that you are
in control of the domain.
This basic instance of Nginx will only ever be run for the first time
that you request a certificate from Let's Encrypt. It's a basic
instance because it doesn't even need to have a default page. It just
needs to give write permissions to the Certbot agent so that it can
place a token at an endpoint for the challenge request and that's
all.
For all Let's Encrypt renewal requests, the process will involve
the following:
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
nginx.conf
server {
listen 80;
listen [::]:80;
server_name ohhaithere.com www.ohhaithere.com;
location ~ /.well-known/acme-challenge {
allow all;
root /usr/share/nginx/html;
}
root /usr/share/nginx/html;
index 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>Oh, hai there!</h1>
<p>
This is the temporary site that will only be used for the
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.
The Site Running in the Nginx Docker Container for Generating the First
Let's Encrypt Certificate
We're almost ready to execute the Certbot command. But before we
do, you need to be aware that Let's Encrypt has rate limits. Most
notably, there's a limit of 20 issued certificates per 7 days. So if you
exceeded 20 requests and are having a problem with generating
your certificate for whatever reason, you could run into trouble.
Therefore, it's always wise to run your commands with a --staging
parameter which will allow you to test if your commands will
execute properly before running the actual commands.
After executing the above command, you should get the following
output which should indicate everything ran successfully.
Issue a new Let's Encrypt Certificate with Certbot and Docker in Staging
Mode
Map our html and other pages in our site folder to the
data folder that let's encrypt will use for challenges.
Run as staging
You can also get some additional information about certificates for
your domain by running the Certbot certificates command:
cd /docker/letsencrypt-docker-nginx/src/letsencrypt
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/ohhaithere.com
/fullchain.pem:/etc/letsencrypt/live/ohhaithere.com/fullcha
in.pem
- /docker-volumes/etc/letsencrypt/live/ohhaithere.com
/privkey.pem:/etc/letsencrypt/live/ohhaithere.com/privkey.p
em
networks:
- docker-network
networks:
docker-network:
driver: bridge
Next, create the Nginx configuration file for the production site
production.conf
server {
listen 80;
listen [::]:80;
server_name ohhaithere.com www.ohhaithere.com;
location / {
rewrite ^ https://$host$request_uri? permanent;
}
#https://ohhaithere.com
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name ohhaithere.com;
server_tokens off;
ssl_certificate /etc/letsencrypt/live/ohhaithere.com/fu
llchain.pem;
ssl_certificate_key /etc/letsencrypt/live/ohhaithere.co
m/privkey.pem;
ssl_buffer_size 8k;
ssl_dhparam /etc/ssl/certs/dhparam-2048.pem;
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.ohhaithere.com$request_uri;
}
#https://www.ohhaithere.com
server {
server_name www.ohhaithere.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_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/ohhaithere.com/fu
llchain.pem;
ssl_certificate_key /etc/letsencrypt/live/ohhaithere.co
m/privkey.pem;
root /usr/share/nginx/html;
index index.html;
}
/docker/letsencrypt-docker-nginx/src/production/production-
site/
cd /docker/letsencrypt-docker-nginx/src/production
sudo docker-compose up -d
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
All that's left to do is to set up a cron job that will execute a certbot
command to renew Let's Encrypt SSL certificates.
Set Up a Cron Job to Automatically Renew Let's
Encrypt SSL/TLS Certificates
It's a good idea to run a daily cron job that attempts to renew Let's
Encrypt SSL certificates. It doesn't matter how many times this
command is executed as nothing will happen unless your
certificate is due for renewal.
sudo crontab -e
Place the following at the end of the file, then close and save it.
production.conf
#https://www.ohhaithere.com
server {
# ....
location / {
#security headers
add_header Strict-Transport-Security "max-age=31536
000; includeSubDomains; preload";
add_header X-XSS-Protection "1; mode=block" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "DENY" always;
#CSP
add_header Content-Security-Policy "frame-src 'self
'; default-src 'self'; script-src 'self' 'unsafe-inline' ht
tps://maxcdn.bootstrapcdn.com https://ajax.googleapis.com;
img-src 'self'; style-src 'self' https://maxcdn.bootstrapcd
n.com; font-src 'self' data: https://maxcdn.bootstrapcdn.co
m; form-action 'self'; upgrade-insecure-requests;" always;
add_header Referrer-Policy "strict-origin-when-cros
s-origin" always;
}
# ....
}
Get an A Score on securityheaders.io
Closing Thoughts
There's certainly no doubt that HTTPS adoption has reached a
tipping point (https://www.troyhunt.com/https-adoption-has-
reached-the-tipping-point/). Let's Encrypt has had a huge hand to
play in this by making free SSL/TLS certificates available to
everyone, while Google has been placing pressure on site owners
to adopt HTTPS. The newest versions of Google Chrome show sites
as insecure if they do not communicate over HTTPS.
This is the guide that I wish was available when I started this
journey. It's my contribution towards lowering the barrier to entry
for HTTPS adoption. If there's any suggestions or improvements to
be made, which I'm sure there would be, let me know in the
comments and I will update accordingly.
Related Posts
Create a Self-Signed Certificate for Nginx in 5 Minutes
(https://www.humankode.com/ssl/create-a-selfsigned-certificate-
for-nginx-in-5-minutes)
Resources
Certbot User Guide (https://certbot.eff.org/docs/using.html)
SecurityHeaders.io (https://securityheaders.io/)
Twitter (https://twitter.com/thecarlo)
GitHub (https://www.github.com/thecarlo)