Skip to main content

Setting Up NGINX with Let's Encrypt for HTTPS in Docker Compose

·1276 words·6 mins
Sebastian Scheibe
Author
Sebastian Scheibe
Table of Contents

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.

References
#

https://letsencrypt.org/

https://letsencrypt.org/getting-started/

https://certbot.eff.org/