Enabling HTTPS with Let's Encrypt on Docker

Schubert Tabarez
BROS
Published in
5 min readJun 11, 2018

--

The problem we face is, enabling HTTPS to our applications without raising the costs or having to install any extra dependencies directly to our host (which we would have to install along in every other host if we need to migrate). We decided to keep it simple (and cost-free!), combining the power of Docker with Let’s Encrypt CA.

Let’s Encrypt is a free, automated, and open certificate authority (CA), run for the public’s benefit. It is a service provided by the Internet Security Research Group (ISRG).

Source: https://letsencrypt.org

Getting Started

To follow this tutorial, you will need:

  • A Linux machine, with Nginx and Docker installed.
  • SSH access to that machine.
  • An application running on Docker, to which we will add the certificate.
  • A registered domain name.

Setting Nginx Configurations

First step, we will need to point the domain name to our host’s IP. In that way, we will assure our ownership over the domain.

The way to do this is to point the domain name to where the host is and placing there proper configurations to attend incoming requests.

Here is an example of pointing domain names using the DigitalOcean network functions. We do not deepen on this, as there are many tutorials on how to achieve this step by step.

Once the domain is pointing to the host. Inside the host, we go inside the folder where our site configuration is located:

cd /etc/nginx/sites-available

Once in there, we create/edit the Nginx configuration file for our application:

touch application-name

Next, we edit this file, placing the following configuration:

server {
listen 80;
server_name domain;

location ~ /.well-known {
allow all;
root /var/www/html/shared;
}

location / {
proxy_pass http://localhost:3000/;
}
}

The server’s configuration displayed above, is just an example resembling our own structure, if you already have one, it will probably differ from this one, don’t worry about it! just get sure to have the paths to your folders right ;-).

Inside our host we have Nginx installed listening on port 80. We also have a Docker container running our application. This docker container is listening on port 3000, that is the way we have for the proxy_pass configuration, to route every request that came through the port 80 for that domain and to our application container. The above also apply if you are using fastcgi_pass to route into an application (running on a container) which accepts non-http request.
Finally, we set the proper configuration to the .well-know path, to verify our domain control with Let’s Encrypt.

NOTE: we can easily scale this to have multiple applications running on multiple Docker containers, each one will need to have their own Nginx configuration.

Obtaining the Certificate

In order to get the certificate, we will connect via SSH to the host machine.

Once inside, we will run a docker image called lojzik/letsencrypt using the following command:

docker run --rm -it -v "/root/letsencrypt/log:/var/log/letsencrypt" -v "/var/www/html/shared:/var/www/" -v "/etc/letsencrypt:/etc/letsencrypt" -v "/root/letsencrypt/lib:/var/lib/letsencrypt" lojzik/letsencrypt certonly --webroot --webroot-path /var/www --email EMAIL -d domain

What we are doing here is running Certbot to get the certificate inside a Docker container built with the lojzek/letsencrypt image. Also with the -v flag, we are sharing between our host environment and the container one some folders to persist the work Certbot is doing. Without this, the certificates, will be created inside the container, but once Certbot finish executing we will lose it all. Finally, we must provide an email and, of course, the domain for which we want the certificate.

Since we are running just a command, and it will be finished after some time, docker will end up stopping the container afterwards, to keep things clean we also include the — rm flag, that tells Docker to delete the container.

NOTE: Let’s Encrypt doesn’t allow us to use wildcards. We can’t replace domain for *.domain. So we need to indicate every subdomain in the command separated by a comma, or running the command multiple times one for each subdomain we want to protect.

One more Nginx Configuration

Now that we have our certificate, we need to add one more configuration to Nginx, as we did it before.

server {
listen 443 ssl;
server_name domain;

ssl_certificate /etc/letsencrypt/live/domain/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/domain/privkey.pem;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_ciphers HIGH:!aNULL:!MD5;

location / {
proxy_pass http://localhost:3000/;
}
}

This configuration tells Nginx to listen on port 443. And indicates the path to the certificate we just got by running the previous command. We also need to add the proxy_pass configuration for the same reason we did before.

Renewal

Another particularity of Let’s Encrypt is that the certificates we obtain expires after ninety days. But is free, what else can we expect? Fortunately, this is not a problem, since we can craft a command to do the renewal, and wrapping it as a cron task, so we don’t have to remember running it.

The next command is very similar to the one used to obtain the certificate. The main difference is that now we only have to execute the renew command.

docker run --rm -v "/root/letsencrypt/log:/var/log/letsencrypt" -v "/var/www/html/shared:/var/www/" -v "/etc/letsencrypt:/etc/letsencrypt" -v "/root/letsencrypt/lib:/var/lib/letsencrypt" lojzik/letsencrypt renew

Instead of executing the previous command immediately, we will create a cron task to execute it daily. If the certificate is still valid, the Certbot will not do anything. If it expired, you will get a new one. Also we need to reload nginx to apply the changes.

0 0 * * * docker run --rm -v "/root/letsencrypt/log:/var/log/letsencrypt" -v "/var/www/html/shared:/var/www/" -v "/etc/letsencrypt:/etc/letsencrypt" -v "/root/letsencrypt/lib:/var/lib/letsencrypt" lojzik/letsencrypt renew >> /var/log/certbot.log 2>&1 && service nginx reload >> /var/log/certbot.log 2>&1

NOTE: We are saving the output to know how it goes, this is not mandatory but recommended.

Conclusion

So that’s all. If you got to this point, you got a simple, maintainable and even cost-free way to enable HTTPS for your applications, without having to install any extra software directly on your host machine (because we are using docker for this). Note that this gives you independence over what you are using to host your application. And if that changes tomorrow, the only thing you would need to do, is coming back to this post and following the exact same steps.

--

--