/ Nginx

Setting up SSL on multiple Nginx proxy servers

It's possible to run multiple sites from a single Digital Ocean droplet using Nginx proxies. This is how I can have ashryan.io, blog.ashryan.io, resizeicon.engineer and so on all running from the same droplet.

Before getting started adding SSL to these sites, I had a feeling that the Nginx proxies might complicate the configuration.

"How To Secure Nginx with Let's Encrypt on Ubuntu 14.04" by Mitchell Anicas for Digital Ocean was quite helpful, but only once I realized that the guide is showing how to deal with a single default Nginx site. In retrospect, that may be obvious in context, but it isn't ever stated explicitly, so I spent a lot of time in /etc/nginx/sites-available/default before I understood that the default file isn't really relevant to my setup.

The default file wasn't relevant for me because I already have multiple custom proxies for sites available and enabled with Nginx.

Below, I'm going to show the bare steps I ended up taking to use Let's Encrypt's certbot to add SSL to my Nginx proxy servers. For basic setup, dependencies, details and generalized how-to's, see Mitchell's guide (linked above).

If you don't have any Nginx proxies running, you might find a few hints in here for setting that up (if that's what you're looking for), but I'd recommend finding a tutorial focused on creating Nginx proxy servers; I'm not going to cover it here.

I will show the full Nginx files as they change along the way. Of course, your Nginx files may not match mine completely; I am putting them here simply as a code-complete hint.

Setting up an existing Nginx proxy server for SSL

Here are the steps I've followed for several of my Nginx proxy servers.

0. Starting point

Nginx proxies in /etc/nginx/sites-available (symbolically linked to /etc/nginx/sites-enabled) in their most simplistic form would look something like this:

server {
    listen 80;
    server_name mysite.com www.mysite.com;

    location / {
        # Port goes here
        proxy_pass http://127.0.0.1:####;
    }
}

In other words, the server listens (insecurely) on port 80, and filters traffic based on the server_name to a server (like a Node server) running at port #### on my droplet.

Each proxy server lives in its own file similar to the one above, all within /sites-available. So this example file above would live at /etc/nginx/sites-available/mysite.com.

1. Prep the Nginx block for certbot

You'll need to make sure you have a couple of things in your Nginx server block so certbot can work its magic.

server {
    listen 80;
    server_name mysite.com www.mysite.com;

    # Edit this path
    root /var/www/mysite.com/server;

    location / {
        proxy_pass http://127.0.0.1:####;
    }

    # Add this block, verbatim
    location ~ /.well-known {
        allow all;
    }
}

The root path should point to the top level entry point of your site, most likely where your index.html is sitting.

The new location block is there for certbot. Again, for a detailed explanation, see Mitchell's guide that I linked to earlier.

2. Run certbot

Use the certbot CLI to generate your certificate. Be sure to edit the --webroot-path value (same as the root from your Nginx server) and the domain names (save as the server_name from your Nginx server).

$ certbot-auto certonly -a webroot --webroot-path=/var/www/mysite.com/server/ -d mysite.com -d www.mysite.com  

Running this command, you should get a "Congratulations!" message that tells you that you now have a certificate.

If that doesn't happen, the reported errors can be downright cryptic...

As an example of a baffling error, when I was first having trouble, I noticed that HTML markup was showing in the console error message; I could see the <html> and <head> elements in the error.

What fixed this for me was double-checking my root in both the Nginx file and the certbot command. I had a mismatch between the two and where the root really was on the server. As a result of that mismatch, certbot was getting an HTML response instead of its own generated file that it uses to validate the setup.

3. Set up your Nginx SSL block

Before we set up the block, if you haven't already, create your Diffie-Hellman group:

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

Now we can set up the Nginx SSL server block:

server {
  listen 443 ssl;
  server_name mysite.com www.mysite.com;

  root /var/www/mysite.com/server;

  # Generated in step 2
  ssl_certificate /etc/letsencrypt/live/mysite.com/fullchain.pem;
  ssl_certificate_key /etc/letsencrypt/live/mysite.com/privkey.pem;

  # Configuration recommendations from Digital Ocean
  ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
  ssl_prefer_server_ciphers on;
  ssl_dhparam /etc/ssl/certs/dhparam.pem;
  ssl_ciphers 'ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:AES:CAMELLIA:DES-CBC3-SHA:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!aECDH:!EDH-DSS-DES-CBC3-SHA:!EDH-RSA-DES-CBC3-SHA:!KRB5-DES-CBC3-SHA';
  ssl_session_timeout 1d;
  ssl_session_cache shared:SSL:50m;
  ssl_stapling on;
  ssl_stapling_verify on;
  add_header Strict-Transport-Security max-age=15768000;

  location / {
        proxy_pass http://127.0.0.1:####;
  }

  location ~ /.well-known {
        allow all;
  }
}

At this point, you should be able to visit https://mysite.com and connect via SSL.

4. Redirect HTTP to HTTPS

Lastly, you can redirect all requests to connect over SSL by replacing the location / block in the port 80 server with a redirect:

server {
    listen 80;
    server_name mysite.com www.mysite.com;

    root /var/www/mysite.com/server;

    # Add this line to redirect to HTTPS
    return 301 https://$host$request_uri;

    location ~ /.well-known {
        allow all;
    }
}

Now if you try to go to http://mysite.com, you will be redirected to https://mysite.com.


I've been able to run through this workflow for a few Nginx-proxied sites on my droplet, and all are now running with SSL, including the site you are reading now.

Note that I haven't touched on a number of important subjects, including certificate renewal. These subjects and more are covered in "How To Secure Nginx with Let's Encrypt on Ubuntu 14.04" on Digital Ocean. Make sure to read that guide to ensure that you're seeing the broader picture.