Setup SSL with Docker, NGINX and Lets Encrypt
Did you ever want to secure your application with the HTTPS protocol? This guide will show you how to run your applications behind a reverse proxy and secure the communication with HTTPS by using Docker, NGINX, and Lets Encrypt.
Used Technologies
- Lets Encrypt: Get free and automated SSL certificates for your applications
- NGINX: Reverse proxy to secure your web applications
- Docker: Host your applications and make them public to the web behind NGINX
With these three technologies, you can create a secure environment to publish your applications to the web. NGINX will be the entry point for users from the web to access the different applications. The SSL certificates are needed to use HTTPS as a communication protocol between your server and the clients.
Docker itself will host NGINX, your applications, and a service to generate new Lets Encrypt certificates automatically.
VPS Hosting Course
Learn everything you need to know about servers and hosting your own applications!To follow this guide, you need a domain, and you need to install docker and docker-compose for your system!
Steps
You can receive SSL certificates for any application you want with the following steps.
- Create your application with Docker
- Create a reverse proxy with NGINX
- Automate SSL certificates with Certbot
Create your application with Docker
The first step is to use docker compose to create a container for your application. I will use the simple helloworld image found here.
Need help or want to share feedback? Join my discord community!
services:
helloworld:
container_name: helloworld
image: crccheck/hello-world
ports:
- 80:8000
The tasks of the different attributes are:
- container_name: name the service to better handle it
- image: node image to start the node application
- ports: map port 8000 to 80, to access it in the browser under the servers ip(! this will be removed later)
Now you can create the container by running:
If this guide is helpful to you and you like what I do, please support me with a coffee!
docker compose up -d helloworld
After this, you can reach your application on the host machine by entering the IP of your server. Don’t make your port available if you run this on a machine on the Internet because it is unsecured. To make this secure will create a reverse proxy with NGINX and put it in front of our applications.
[convertkit form=2649016]
Create a reverse proxy with NGINX
First, we will create a configuration to pass the requests to our node application via NGINX. Therefore we will make the directory nginx containing the file nginx.conf. With this file, we configure the NGINX instance.
events {
worker_connections 1024;
}
http {
server_tokens off;
charset utf-8;
server {
listen 80 default_server;
server_name _;
location / {
proxy_pass http://helloworld:8000/;
}
}
}
The event section is needed to run NGINX. The http section is the interesting part for us. First, the server is defined to listen to all requests on port 80 and is set as a default_server for all requests to this host. After that, we say each server name is valid for requests. In a later part, you can define different domains that are used for a server.
In the location / we define that every request should be passed to our helloworld container with port 8000. It is important to say that the specified port is not the public but the private port. Therefore we will remove the port mapping in the docker-compose.yml file.
Additionally, we will create an NGINX container in the same file.
services:
helloworld:
container_name: helloworld
image: crccheck/hello-world
expose:
- 8000
nginx:
container_name: nginx
restart: unless-stopped
image: nginx
ports:
- 80:80
- 443:443
volumes:
- ./nginx/nginx.conf:/etc/nginx/nginx.conf
We will make ports 80 and 443 public because these are used for HTTP and HTTPS.
The volume is used to make the configuration available inside the container and to persist any changes.
You can create and update the container by running:
docker compose up -d
After this, you should be able to open the application by entering the URL to the host. Now we have setup a reverse proxy for our application. The next step will be to get SSL certificates and redirect all HTTP requests to HTTPS.
Automate SSL certificates with Certbot
Let’s Encrypt works with challenges to check if the domain and the host are eligible. For the challenges, we have to create a route called /.well-known/acme-challenge/ in the NGINX configuration. This will be part of the HTTP section, but we will move it there after switching to HTTPS, so the automated renewal works as expected.
server {
listen 80 default_server;
.
.
.
location ~ /.well-known/acme-challenge/ {
root /var/www/certbot;
}
}
As you can see, it points to the directory /var/www/certbot. Therefore, we need to add two volumes to make the challenges and the resulting certificates available in the NGINX container.
- ./certbot/conf:/etc/letsencrypt
- ./certbot/www:/var/www/certbot
With this done, we have to recreate the container:
docker compose up -d nginx
After configuring NGINX, we can retrieve the certificates. To do this, we will use the certbot container and run it with a set of parameters.
certbot:
image: certbot/certbot
container_name: certbot
volumes:
- ./certbot/conf:/etc/letsencrypt
- ./certbot/www:/var/www/certbot
command: certonly --webroot -w /var/www/certbot --force-renewal --email {email} -d {domain} --agree-tos
The parameters are used to automate the process without you needing to manually enter the data on every container run.
- certonly: only generate certificate without installing
- webroot: use our own webserver in this case NGINX
- w: root directory of webserver for the challenges
- force-renewal: on repeated run renew certificates
- email: your email for notifcations
- d: domain for the certificate (you can enter -d {domain} multiple times for different domains)
- agree-tos: agree the terms of service automatically (dont set it, if you want to read and understand them first)
Before requesting the certificate, we need to create an A record at our hosting provider pointing to our server. I am using Hostinger as my domain provider (find a domain here – affiliate link):
- Manage Domain
- DNS / Nameserver
- Manage DNS Records > Create A record:
- Type = A
- Name = @ – (This means it is for the main domain)
- Points to = IP of your server
- TTL = 14400
Afterward, we can request the certificates by creating the container with:
docker compose up -d certbot
By running the command docker logs certbot
you can see if everything worked out and if you received your certificate. After you receive it, you have to include the certificate in nginx.conf
.
Configure HTTPS in NGINX
In the first step, we redirect all HTTP requests to HTTPS, and in the second step, we create the HTTPS section for our application:
http {
server_tokens off;
charset utf-8;
# always redirect to https
server {
listen 80 default_server;
server_name _;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl http2;
# use the certificates
ssl_certificate /etc/letsencrypt/live/{domain}/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/{domain}/privkey.pem;
server_name {domain};
root /var/www/html;
index index.php index.html index.htm;
location / {
proxy_pass http://helloworld:8000/;
}
location ~ /.well-known/acme-challenge/ {
root /var/www/certbot;
}
}
}
Another thing you can see is that we moved the challenge route to the HTTPS section. We need to do this because all new requests of Lets Encrypt will also be redirected to HTTPS.
Automated renewal with crontab
The last step is to automatically renew the certificates before they run out. A certificate has a lifetime of 90 days, and it is recommended to update them after a timespan of 60 days. Therefore, we need to rerun our certbot container every 60 days to renew the certificates. I will accomplish this by using crontab.
A crontab can be created on linux systems by running:
crontab -e
And adding a line with the following structure:
0 5 1 */2 * /usr/bin/docker compose -f /var/docker/docker-compose.yml up certbot
The command means: Run docker-compose up -d at 5 am on the first day every 2nd month.
After implementing this guide, you should have an application behind a reverse proxy accessible via the HTTPS protocol!
Thank you for reading my guide. If you have any questions feel free to shoot me an email at mail@programonaut.com, and I will try to help you!
In case you liked this post consider subscribing to my newsletter. You will also receive a free docker-compose cheat sheet!
[convertkit form=2649016]