NGINX server with SSL certificates with Let’s Encrypt in Docker

Let’s Encypt’s Certbot in a Docker Container

  1. You issue a command to the Certbot agent
  2. Certbot informs Let’s Encrypt that you want an SSL/TLS certificate
  3. Let’s Encrypt sends the Certbot agent a unique token
  4. The Certbot agent places the token at an endpoint on your domain that looks like: http://{domain}/.well-known/acme-challenge/{token}
  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.

Obtaining the Let’s Encrypt SSL/TLS Certificate

  • Pulls the latest version of Nginx from the Docker registry
  • Exposes port 80 on the container to port 80 on the host, which means that requests to your domain on port 80 will be forwarded to nginx running in the Docker container
  • Maps the nginx configuration file that we will create in the next step to the configuration location in the Nginx container. When the container starts, it will load our custom configuration
  • Maps the Let’s Encrypt location to the default location of Nginx in the container.
  • Creates a default Docker network
# docker-compose.ymlservices:letsencrypt-nginx-container:
container_name: 'letsencrypt-nginx-container'
image: nginx:latest
ports:
- "80:80"
volumes:
- ./nginx.conf:/etc/nginx/conf.d/default.conf
networks:
- docker-network
networks:
docker-network:
driver: bridge
# nginx.conf
server {
listen 80;
listen [::]:80;
server_name {domains};
location ~ /.well-known/acme-challenge {
allow all;
root /usr/share/nginx/html;
}
root /usr/share/nginx/html;
index index.html;
}
  • Listens for requests on port 80 for URLs in the domain
  • Gives the Certbot agent access to ./well-known/acme-challenge
  • Sets the default root and file
sudo docker-compose up -d
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 {domain}
  • Run docker in interactive mode so that the output is visible in terminal
  • If the process is finished close, stop and remove the container
  • Map 4 volumes from the server to the Certbot Docker Container:
  • The Let’s Encrypt Folder where the certificates will be saved
  • Lib folder
  • Map our html and other pages in our site folder to the data folder that let’s encrypt will use for challenges.
  • Map a logging path for possible troubleshooting if needed
  • For staging, we’re not specifying an email address
  • We agree to terms of service
  • Specify the webroot path
  • Run as staging
  • Issue the certificate to be valid for the A record and the CNAME record
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
sudo rm -rf /docker-volumes/
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 {domain}
cd /docker/letsencrypt-docker-nginx/src/letsencryptsudo docker-compose down

Set up Your Production Site to Run in a Nginx Docker Container

# docker-compose.ymlversion: '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/{domain}/fullchain.pem:/etc/letsencrypt/live/{domain}/fullchain.pem
- /docker-volumes/etc/letsencrypt/live/{domain}/privkey.pem:/etc/letsencrypt/live/{domain}/privkey.pem
networks:
- docker-network
networks:
docker-network:
driver: bridge
  • Allows ports 80 and 443
  • Maps the production Nginx configuration file into the container
  • Maps the production site content into the container
  • Maps a 2048 bit Diffie–Hellman key exchange file into the container
  • Maps the public and private keys into the container
  • Sets up a docker network
# production.confserver {
listen 80;
listen [::]:80;
server_name {domain};
location / {
rewrite ^ https://$host$request_uri? permanent;
}
#for certbot challenges (renewal process)
location ~ /.well-known/acme-challenge {
allow all;
root /data/letsencrypt;
}
}
#https://ohhaithere.com
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name {domain};
server_tokens off; ssl_certificate /etc/letsencrypt/live/{domain}/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/{domain}/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://{domain}$request_uri;
}
#https://{domain}
server {
server_name {domain};
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/{domain}/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/{domain}/privkey.pem;
root /usr/share/nginx/html;
index index.html;
}
sudo openssl dhparam -out /docker/letsencrypt-docker-nginx/src/production/dh-param/dhparam-2048.pem 2048
/docker/letsencrypt-docker-nginx/src/production/production-site/
sudo docker-compose up -d

How to Renew Let’s Encrypt SSL Certificates with Certbot and Docker

location ~ /.well-known/acme-challenge {
allow all;
root /usr/share/nginx/html;
}
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

Finally — Set Up a Cron Job to Automatically Renew Let’s Encrypt SSL/TLS Certificates

sudo crontab -e
0 0 * * * 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

--

--

--

An Enthusiast Full-Stack Developer. Signal Processing Engineer. Nerd for Technology.

Love podcasts or audiobooks? Learn on the go with our new app.

Recommended from Medium

Python: data storing, SQL vs NoSQL

A Green Scanner Effect with Depth Texture

How to Use zip() in Python

Clear separation between internal and external domains the Propeller way

Implementing LaunchDarkly with no clear separation between domains

Scheduling Google Cloud Functions to Run Periodically

Are You Good with Pointers in C? Solve This

Understanding Kubernetes Autoscaling

How to Wield the Power of R

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Agustin Navcevich

Agustin Navcevich

An Enthusiast Full-Stack Developer. Signal Processing Engineer. Nerd for Technology.

More from Medium

Run Jenkins using Docker Desktop (linux containers)

Running Redis with resilience in Linux containers on Windows — Part 4

Nginx server error

MongoDB Service (Result: core-dump) in Proxmox