Introduction #
In this guide, you’ll learn how to set up a secure HTTPS connection for your application using NGINX, Let’s Encrypt’s Certbot, and Docker Compose.
What is a secure connection (HTTPS)? #
HTTPS (HyperText Transfer Protocol Secure) is an extension of HTTP that uses encryption (SSL/TLS) to secure data exchanged between a web server and browser, ensuring privacy and data integrity.
What is Let’s Encrypt? #
Let’s Encrypt is a free and automated Certificate Authority that runs to serve the public’s benefit. It provides the service to generate certificates for SSL/TLS.
See more info at https://letsencrypt.org/
Setup #
To use NGINX, certbot (Let’s Encrypt) and Docker Compose, we need to create a couple of files. Though at first, there are some requirements.
Prerequisites #
In the following, it is assumed that you have Docker and Docker-Compose installed.
Ensure that Docker and Docker Compose are installed by running the following commands:
Your version might be different.
~# docker --version
Docker version 28.0.1, build 068a01e
~# docker compose version
Docker Compose version v2.33.1
Further on, it would be great if you know a bit about NGINX and how certificates work.
Docker Compose #
In this setup, the APP definition in the Docker Compose is optional and should be replaced with your own application reference.
Please create a file docker-compose.yml
and add the following content:
services:
app:
image: python:3
restart: always
command: ["python3", "-m", "http.server", "8080"]
networks:
- internal_network
webserver:
image: nginx:latest
ports:
- 80:80
- 443:443
restart: always
volumes:
- ./nginx/conf/:/etc/nginx/conf.d/:ro
- ./certbot/www/:/var/www/certbot/:ro
- ./certbot/conf/:/etc/letsencrypt/:ro
networks:
- internal_network
certbot:
image: certbot/certbot:latest
volumes:
- ./certbot/www/:/var/www/certbot/:rw
- ./certbot/conf/:/etc/letsencrypt/:rw
networks:
- internal_network
certbot-renew:
image: certbot/certbot:latest
volumes:
- ./certbot/www/:/var/www/certbot/:rw
- ./certbot/conf/:/etc/letsencrypt/:rw
entrypoint: ["/bin/sh", "-c"]
command: ["while true; do certbot renew --webroot --webroot-path /var/www/certbot/ --quiet && sleep 12h; done"]
networks:
- internal_network
networks:
internal_network:
driver: bridge
NGINX Config #
Next, let’s create the nginx config.
mkdir -p ./nginx/conf
Then let’s add the config for NGINX via:
nano ./nginx/conf/app.conf
Replace sample.ecostack.dev
with your own domain name.
server {
listen 80;
listen [::]:80;
server_name sample.ecostack.dev www.sample.ecostack.dev;
server_tokens off;
location /.well-known/acme-challenge/ {
root /var/www/certbot;
}
location / {
return 301 https://sample.ecostack.dev$request_uri;
}
}
# Uncomment the lines below after obtaining your SSL certificate.
#server {
# listen 443 default_server ssl http2;
# listen [::]:443 ssl http2;
# server_name sample.ecostack.dev;
# ssl_certificate /etc/letsencrypt/live/sample.ecostack.dev/fullchain.pem;
# ssl_certificate_key /etc/letsencrypt/live/sample.ecostack.dev/privkey.pem;
# location / {
# proxy_set_header Host $host;
# proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
# proxy_set_header X-Forwarded-Proto $scheme;
# proxy_pass http://app:8080;
# }
#}
Make sure to start the web server and app:
docker compose up webserver app -d
Get certificate via certbot #
Run the following command to simulate the certificate request (dry-run) to verify everything is set up correctly:
docker compose run --rm certbot certonly --webroot --webroot-path /var/www/certbot/ --dry-run -d sample.ecostack.dev
You should see this:
[+] Creating 1/1
✔ Network glitch-exp_default Created 0.1s
Saving debug log to /var/log/letsencrypt/letsencrypt.log
Enter email address or hit Enter to skip.
(Enter 'c' to cancel): [email protected]
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Please read the Terms of Service at:
https://letsencrypt.org/documents/LE-SA-v1.5-February-24-2025.pdf
You must agree in order to register with the ACME server. Do you agree?
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
(Y)es/(N)o: Y
Account registered.
Simulating a certificate request for sample.ecostack.dev
The dry run was successful.
This means, everything is working.
Let’s obtain the real certificate then:
docker compose run --rm certbot certonly --webroot --webroot-path /var/www/certbot/ -d sample.ecostack.dev
You should see this:
Saving debug log to /var/log/letsencrypt/letsencrypt.log
Enter email address or hit Enter to skip.
(Enter 'c' to cancel): [email protected]
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Please read the Terms of Service at:
https://letsencrypt.org/documents/LE-SA-v1.5-February-24-2025.pdf
You must agree in order to register with the ACME server. Do you agree?
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
(Y)es/(N)o: Y
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Would you be willing, once your first certificate is successfully issued, to
share your email address with the Electronic Frontier Foundation, a founding
partner of the Let's Encrypt project and the non-profit organization that
develops Certbot? We'd like to send you email about our work encrypting the web,
EFF news, campaigns, and ways to support digital freedom.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
(Y)es/(N)o: Y
Account registered.
Requesting a certificate for sample.ecostack.dev
Successfully received certificate.
Certificate is saved at: /etc/letsencrypt/live/sample.ecostack.dev/fullchain.pem
Key is saved at: /etc/letsencrypt/live/sample.ecostack.dev/privkey.pem
This certificate expires on 2025-06-28.
These files will be updated when the certificate renews.
NEXT STEPS:
- The certificate will need to be renewed before it expires. Certbot can automatically renew the certificate in the background, but you may need to take steps to enable that functionality. See https://certbot.org/renewal-setup for instructions.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
If you like Certbot, please consider supporting our work by:
* Donating to ISRG / Let's Encrypt: https://letsencrypt.org/donate
* Donating to EFF: https://eff.org/donate-le
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Update NGINX config #
Now, it is time to enable the HTTPS variant of your NGINX config:
nano ./nginx/conf/app.conf
Uncomment the HTTPS part at the bottom.
server {
listen 80;
listen [::]:80;
server_name sample.ecostack.dev www.sample.ecostack.dev;
server_tokens off;
location /.well-known/acme-challenge/ {
root /var/www/certbot;
}
location / {
return 301 https://sample.ecostack.dev$request_uri;
}
}
# Uncomment the lines below after obtaining your SSL certificate.
server {
listen 443 default_server ssl http2;
listen [::]:443 ssl http2;
server_name sample.ecostack.dev;
ssl_certificate /etc/letsencrypt/live/sample.ecostack.dev/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/sample.ecostack.dev/privkey.pem;
location / {
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_pass http://app:8080;
}
}
Restart the webserver:
docker compose down webserver && docker compose up -d webserver
Try HTTPS connection #
Try accessing your domain over HTTPS in a browser. You should see your app or a default NGINX page if you used the Python app example.
Start auto renewal #
Let’s Encrypt certificates expire after 90 days. To keep your site secure, Certbot can automatically renew the certificates for you. Simply run the command below to set up automatic renewal:
docker compose up -d certbot-renew
Conclusion #
Congratulations! You’ve successfully set up a secure HTTPS connection for your app using NGINX, Let’s Encrypt, and Docker Compose. Your connection is now encrypted, improving security for your users.