Docker, Nginx and Let's Encrypt for a secure website

6 min read

Let me show you how I use Docker, Nginx and Let’s encrypt to host my websites and serve secure https content.

I use Docker to host a lot of my websites, and with Let’s Encrypt now issuing free SSL certificates, there is no excuse for not having one.

Things to note

My test server is running on Ubuntu 16.04. Though the process should be similar on most other servers.

You should have pointed your domain nameservers or A records to the server.

I’ll use example.com for this tutorial, you should replace that with your domain name.

I’ll use 12.34.56.78 as the IP address of the server. You should replace this with your own IP

Step 1: Install Docker

We will now install our beloved Docker using the command.

sudo apt install docker.io

For more ways to install docker, visit the official docker installation page

Step 2: Set up our site with Docker

So, we’ll create a directory for our site, this will hold the code that we’ll mount put into the docker container.

sudo mkdir -p /var/www/example.com

Next, we’ll pull the container.

docker pull creativitykills/nginx-php-server:1.1.1

This is a container with nginx and php installed. The web root of the container is /var/www/public so we’ll have to mount our site there.

Next, we’ll start up our docker container.

docker run -d -p 1234:80 \
 --name example_site \
 -v /var/www/example.com:/var/www/public \
 creativitykills/nginx-php-server:1.1.1

With that command,

  1. We ran the container creativitykills/nginx-php-server:1.1.1
  2. We told docker to run this container in detached mode [ -d ]
  3. We mapped the port 1234 on our server to port 80 of the container [ -p 1234:80]
  4. We named the container example_site [ –name example_site ]
  5. We mounted our sites folder to the web root of the container. [-v /var/www/example.com:/var/www/public]

Lastly, we’ll add an index.html file in /var/www/example.com/index.html

<!-- /var/www/example.com/index.html -->
<!DOCTYPE html>
<html>
<head>
 <title>Example Site</title>
</head>
<body>
 <h2>This is my example site</h2>
</body>
</html>

If you visit 12.34.56.78:1234 now, you should see the following.

example siteexample site

Step 2: Point our domain to our docker container with Nginx

We’ll install Nginx on our server to use as a reverse proxy for our Docker containers. We can install it using the following command.

sudo apt install nginx

If you visit example.com now, you should see the default nginx page.

ngins-default-pageNginx default page

To point our domain to our docker container, we have to set up some custom nginx configuration.

Add an example.com.conf file to /etc/nginx/sites-available.

touch /etc/nginx/sites-available/example.com.conf

use your favorite editor to add this to example.com.conf

server {
 listen 80;
 listen [::]:80;
 server_name example.com www.example.com;

 location / {
   proxy_pass http://0.0.0.0:1234;
   proxy_set_header Host $host;
   proxy_set_header X-Real-IP $remote_addr;
   proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
   proxy_set_header X-Forwarded-Proto $scheme;
 }

}

Next, add example.com.conf to the sites-available list using this command.

ln -s /etc/nginx/sites-available/example.com.conf /etc/nginx/sites-enabled/example.com.conf

Now, reload nginx so that our new configuration takes effect

/etc/init.d/nginx reload

If you visit example.com now, you should see the following.

example siteexample site

 Step 4: Obtain an SSL certificate from Let’s Encrypt

For issuing and renewing certificates, we’ll use the letsencrypt package.

Simply install it by running the following command:

sudo apt install letsencrypt

Next, generate a Diffie-Hellman group. Using this command

sudo openssl dhparam -out /etc/ssl/certs/dhparam.pem 2048

Now, we’ll adjust the nginx configuration to create the certificate.

Edit /etc/nginx/sites-available/example.com.conf and make it this.

server {
 listen 80;
 listen [::]:80;
 server_name example.com www.example.com;

 location "/.well-known/acme-challenge" {
   default_type "text/plain";
   root /var/www/example.com;
   allow all;
 }

 location / {
   proxy_pass http://0.0.0.0:1234;
   proxy_set_header Host $host;
   proxy_set_header X-Real-IP $remote_addr;
   proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
   proxy_set_header X-Forwarded-Proto $scheme;
 }

}

Reload nginx again for our configuration changes to take effect.

/etc/init.d/nginx reload

Obtain the certificates from let’s encrypt.

letsencrypt certonly -a webroot \
--webroot-path=/var/www/example.com \
-d example.com -d www.example.com

You will be asked to enter your email and agree to the terms and conditions.

After that, you should have your ssl certificates installed successfully.

Step 5: Serve your site over HTTPS

We edit our example.com.conf once more to serve https content. Change it to this.

server {
 listen 80;
 listen [::]:80;
 server_name example.com www.example.com;

 location "/.well-known/acme-challenge" {
   default_type "text/plain";
   root /var/www/example.com;
   allow all;
 }

 location / {
   proxy_pass http://0.0.0.0:1234;
   proxy_set_header Host $host;
   proxy_set_header X-Real-IP $remote_addr;
   proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
   proxy_set_header X-Forwarded-Proto $scheme;
 }

}

server {
 listen 443 ssl;
 server_name example.com www.example.com;
 
 ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
 ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
 ssl_session_cache shared:SSL:10m;
 ssl_session_timeout 5m;
 ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
 ssl_prefer_server_ciphers on;
 ssl_dhparam /etc/ssl/certs/dhparam.pem;
 ssl_ciphers 'EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH';
 ssl_stapling on;
 ssl_stapling_verify on;
 add_header Strict-Transport-Security max-age=15768000;
 
 location "/.well-known/acme-challenge" {
   default_type "text/plain";
   root /var/www/example.com;
   allow all;
 }

 location / {
   proxy_pass http://0.0.0.0:1234;
   proxy_set_header Host $host;
   proxy_set_header X-Real-IP $remote_addr;
   proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
   proxy_set_header X-Forwarded-Proto $scheme;
 }
}

Reload nginx one more time!

/etc/init.d/nginx reload

You should now be able to access https://example.com

Step 6: Setup Auto Renewal for your SSL certificates

Let’s encrypt certificates expire after 90 days. The command for renewal is

letsencrypt renew

If you run it now, it’ll tell you that your certificates are not due for renewal.

What we’ll do is to set up a weekly cronjob that tries to renew the certificates, and then reloads nginx.

First, open the crontab as root

sudo crontab -e

Next add the following lines to the crontab.

30 2 * * 1 letsencrypt renew
35 2 * * 1 /etc/init.d/nginx reload

Step 7: Redirect all http requests to https (Optional)

On the quest to make the web safer, it is better to serve all requests over https except you have a good reason not to. To redirect all http traffic to https, modify example.com.conf to this.

server {
 listen 80;
 listen [::]:80;
 server_name example.com www.example.com;

 location "/.well-known/acme-challenge" {
   default_type "text/plain";
   root /var/www/example.com;
   allow all;
 }

 return 301 https://example.com$request_uri;
}

server {
 listen 443 ssl;
 server_name example.com www.example.com;
 
 ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
 ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
 ssl_session_cache shared:SSL:10m;
 ssl_session_timeout 5m;
 ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
 ssl_prefer_server_ciphers on;
 ssl_dhparam /etc/ssl/certs/dhparam.pem;
 ssl_ciphers 'EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH';
 ssl_stapling on;
 ssl_stapling_verify on;
 add_header Strict-Transport-Security max-age=15768000;
 
 location "/.well-known/acme-challenge" {
   default_type "text/plain";
   root /var/www/example.com;
   allow all;
 }

 location / {
   proxy_pass http://0.0.0.0:1234;
   proxy_set_header Host $host;
   proxy_set_header X-Real-IP $remote_addr;
   proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
   proxy_set_header X-Forwarded-Proto $scheme;
 }
}

Now, reload nginx.

/etc/init.d/nginx reload

 

That’s it!

Powered By Swish

Comments