Install a free SSL Certificate with LetsEncrypt and Nginx

Datetime:2016-08-22 22:58:16          Topic: Nginx           Share

Amazon AD Banner

During the past few years, the number of sites that are migrating from HTTP to HTTPS is has been growing rapidly and we can't really blame them because there are a lot of benefits that come from having an SSL ( Secure Sockets Layer ) certificate installed, a domain verified and an encrypted connection; especially if you're the owner of a business, a company that sells and buys, a web app who handles user accounts and sensitive data that is being processed and sent within the internals of your server.

Even blogs like mine are making the switch to HTTPS for the benefits that I'll mention in a bit. There are three types of SSL certificates that you or your company can obtain; a Domain Validated Certificate is pretty straightforward, it validates your domain and certifies that your site is running a secure connection over port 443 which is the port for HTTPS connections over TSL/SSL, everyone can obtain one of these either paying a monthly or yearly fee (this is more common) or even for free. now, an Organization Validated Certificate contains more information and it's intended for organizations (including non-profit), brands, companies, and corporations; to obtain it you would need to provide some information and documentation to the certificate authority; once you get one, your organization's info (country, name, state, etc) will appear in the certificate details! By the way, to check the details of an SSL certificate on your browser, click on the icon to the left side of the URL in the address bar. Lastly, an Extended Validation Certificate gives you a green bar and the name of your organization will be displayed beside the padlock icon in the address bar, this also requires more steps and checks, documentation and time to obtain one.

Requirements before starting

I feel like it's important to let you know the tools you require to obtain an SSL certificate and who is this tutorial for exactly. This tutorial is intended for people who run their site or web app on a Virtual Private Server ( VPS ) or a cloud hosting provider; being more exact: someone who has control of the server. I'm going to be using an Ubuntu 16 server (although you can follow on the 14 version), a Terminal window to log in to my server via SSH (Secure Shell) in order to send the commands required to install the certificate, GNU Nano as the command-line editor and a domain name that has to be already set up on your hosting provider using the correct DNS records, which means that if you visit yourdomain.com , a website must be displayed. And most important, a stable version of Nginx because I'll use it to provide access to the LetsEncrypt Command-Line client to a special file that it needs to validate the domain; I'll also use Nginx to listen to connections on port 443 (HTTPS) and redirect those who enter via HTTP to the HTTPS version of my website; Nginx will also be used to indicate the location of the installed certificate and the settings to the secure connection.

I'll be using a DigitalOcean 5 dollar droplet solely to install a brand new certificate to a domain name that I purchased some months ago (horchata.me), it runs Ubuntu 16 and it's completely blank, it has nothing apart from Nginx, a /var/www/html/ folder with two files ( index.html and styles.css ) which convert to the site you see here:

The starting Nginx configuration is super simple, it's located at /etc/nginx/sites-available/default but your Nginx configuration can be whatever you already have, we won't remove much, just add a few lines and that's all). You can be running a web app on Rails, Wordpress, NodeJS, Flask, PHP, Ghost (like my blog), Drupal, Meteor or whatever you want as long as it's being served with Nginx to the user.

# My super simple config file
server {  
  listen      80;
  server_name www.horchata.me horchata.me;

  location / {
    root /var/www/html;
  }
}

The config file above is just catching connections to the HTTP port (80) and the root folder location / will serve the contents of /var/www/html , pretty simple but like I mentioned, yours can look extremely different, it just doesn't matter for now.

Installing the LetsEncrypt client

LetsEncryptis relatively recent and it's known for being a "new Certificate Authority which is free , automated, and open", according to what their homepage says. Apart from being cost-free to users who want to make the switch to HTTPS, I liked LetsEncrypt because it has several automated plugins that are used by its client (you can install it with Git on your server) to automate the process of validating the domain , generating the certificate and installing it. The validation of the domain can be manual or automated, I'll be using a plugin called Webroot (compatible with Nginx) but if you want to do it manually, all you have to do is follow the instructions that ask you to put a file with a long hashed name and its contents are also provided to you (another long hashed string), that file must be accessible through the web, more details on manual installation here .

To install the command-line client for LetsEncrypt, log-in to your server using SSH and if you don't have Git installed, install it by running the following commands:

sudo apt-get update  
sudo apt-get -y install git

Git needs to be installed because we're going to clone it to our server from the Github repository . Next, we're going to do just that, run the following commands:

sudo git clone https://github.com/letsencrypt/letsencrypt /opt/letsencrypt

This command is telling Git to clone the contents of the remote repository into a folder inside our server called opt and a subfolder letsencrypt will be created which is where the LetsEncrypt client will now reside.

Using the Webroot plugin

Time to get our certificate! I forgot to mention, though; these certificates have a validity time of 3 months from the time they were issued or renewed. That's why at the end of the tutorial we're going to set up a cronjob (a task that runs every x amount of time) to auto-renew (thanks to LetsEncrypt's client capabilities) the certificates without us having to step in every 3 months.

Just as the manual domain validation process, Webroot also looks into a secret folder in your website's path called .well-known . Webroot will insert that folder, along with an acme-challenge subfolder and a strangely-named extensionless file. Since it has to be accessible via your website's path, you need to make it available with Nginx by adding the following location block inside your server block . So, let's edit our Nginx configuration file (mine's called default ) with Nano:

sudo nano /etc/nginx/sites-available/default

If you have never used Nano before, just move your keyboard's arrow keys and place the caret below the listen related statements, right where your location blocks are. Next, insert the following code and save the file ( Cmd/Ctrl + O ) and close Nano with Cmd/Ctrl + X :

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

In the end, the file must look like this: (note how there's a www version and a non-www version in the server_name , that's because I'll issue a certificate for both variants of my domain. Replacing of course, horchata.me with your domain .

server {  
  listen 80;
  server_name www.horchata.me horchata.me;

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

  location / {
    root /var/www/html;
  }
}

Now, all you need to do is test the configuration file for errors by running the sudo nginx -t (if something goes wrong, double check your config file) command and then restart Nginx:

sudo service nginx restart

Let's install the certificate now

We've just started with the preparations but now it's actually time to install the certificate with LetsEncrypt, please take something important in mind, from now on, every instance of " horchata.me " you see, replace it with your own domain name and TLD . Run the following command to navigate to the root directory of the LetsEncrypt command-line tool:

cd /opt/letsencrypt

Now, run this command to execute the command-line tool:

./letsencrypt-auto certonly -a webroot --webroot-path=/var/www -d horchata.me -d www.horchata.me

This command tells LetsEncrypt to issue an SSL certificate to the domains listed in every -d (domain or subdomain) flag, just noting that you can issue subdomains but you can't use a wildcard (*) character, it's not supported by LetsEncrypt. The --webroot-path flag tells Webroot where to install and find the .well-known directory in order to validate the ownership of your domain.

When you run it, it will perform a repository information update, it will install Letsencrypt's dependencies (Python and more tools) and you'll be given a prompt like this:

Type in your email address and hit ENTER , another blue screen will pop out asking you to read the Terms of Service, after that, press ENTER again and if everything went well, you'll be received with the following message:

IMPORTANT NOTES:  
 - Congratulations! Your certificate and chain have been saved at
   /etc/letsencrypt/live/horchata.me/fullchain.pem. Your cert will
   expire on 2016-11-19. To obtain a new or tweaked version of this
   certificate in the future, simply run letsencrypt-auto again. To
   non-interactively renew *all* of your certificates, run
   "letsencrypt-auto renew"
 - If you lose your account credentials, you can recover through
   e-mails sent to hello@codetuts.tech.
 - Your account credentials have been saved in your Certbot
   configuration directory at /etc/letsencrypt. You should make a
   secure backup of this folder now. This configuration directory will
   also contain certificates and private keys obtained by Certbot so
   making regular backups of this folder is ideal.
 - 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

If you navigate now to /etc/letsencrypt/live/ and list the folders and files that are in there with the ls -l command, you'll see a folder with your domain name (without the www) that contains symlinks to 4 files:

  • cert.pem : The certificate file that we just obtained.
  • chain.pem : The chain certificate, if you wish to know what the differences are, check this out.
  • fullchain.pem : The combination of the two files above that Nginx will serve to browsers who connect to your website.
  • privkey.pem : The certificate's private key file.

You can verify that those files exist by listing them with the following command:

sudo ls -l /etc/letsencrypt/live/yourdomain

It's also adviced that you create a Diffie-Helman group to increase security in your server, this group will also be linked inside your Nginx configuration later on and will be located at /etc/ssl/certs/dhparam.pem , to generate one, run:

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

This will take a few minutes, displaying some punctuation characters flowing in the meantime, it's kind of soothing tbh (watching it).

The Diffie–Hellman problem (DHP) is a mathematical problem first proposed by Whitfield Diffie and Martin Hellman in the context of cryptography. The motivation for this problem is that many security systems use mathematical operations that are fast to compute, but hard to reverse. For example, they enable encrypting a message, but reversing the encryption is difficult. If solving the DHP were easy, these systems would be easily broken. - Wikipedia

Configuring Nginx for SSL/TLS and HTTPS

We're almost done, this is the time of truth! Summarizing all we need, we're gonna create another server block that redirects any traffic from port 80 (HTTPS) to its HTTPS equivalent with a 301 permanent redirection status code. Then, we modify the original server block to listen to port 443 (HTTPS) instead of port 80 and insert some SSL/TLS configuration statements.

First, inside the current server block (the one that is currently sending visitors to your web app or static website) add the following lines below the port listening statements:

ssl_certificate /etc/letsencrypt/live/horchata.me/fullchain.pem;  
ssl_certificate_key /etc/letsencrypt/live/horchata.me/privkey.pem;

Then, let's add a recommended SSL/TLS configuration and point to our Diffie-Hellman group:

ssl_protocols TLSv1 TLSv1.1 TLSv1.2;  
ssl_prefer_server_ciphers on;  
ssl_ciphers "EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH";  
ssl_ecdh_curve secp384r1;  
ssl_session_cache shared:SSL:10m;  
ssl_session_tickets off;  
ssl_stapling on;  
ssl_stapling_verify on;  
resolver 8.8.8.8 8.8.4.4 valid=300s;  
resolver_timeout 5s;  
add_header Strict-Transport-Security "max-age=63072000; includeSubdomains";  
ssl_dhparam /etc/ssl/certs/dhparam.pem;

# Optionally, you can enable these protection statements:
# add_header X-Frame-Options DENY;
# add_header X-Content-Type-Options nosniff;

In resolver , we're using Google's DNS resolver in case the server's IP changes, Nginx by default only resolved the DNS on startup so, adding a resolver statement helps as a workaround to this problem which is especially present in load-balancers.

Listening on port 443

By now, all you have to do is do some port listening, once you've added the aforementioned SSL configuration to your server block, replace the part where you listen to port 80 (with or without IPV6 support) with the following line:

# If you're using spdy, replace http2 with spdy
listen 443 ssl http2;  
# Optionally, add support for IPV6
# listen [::]:443 ssl http2 www.horchata.me horchata.me;

Now, when users visit you over https:// , you'll serve your web app or website to them using a secure protocol and the listed configuration. But what happens now if someone access to your site with HTTP (let's say an old link somewhere points to your HTTP version)? Your users will get a nice message saying "The connection to the site was refused by the server" or something along those lines; that's because the server does exist and the DNS resolved to your server's IP address, but Nginx isn't taking port 80 (HTTP) in consideration. To solve that, and avoid future headaches, redirect all of your users that visit you through HTTP to the HTTPS version of said URL. Create another server block as follows:

server {  
  listen 80;
  # Optinally, add support for IPV6
  # listen [::]:80;

  server_name horchata.me www.horchata.me;
  return 301 https://$server_name$request_uri;
}

This is telling Nginx to permanently redirect any request to the HTTP version of the requested URL (hence the $request_uri variable use) to its HTTPS equivalent. In the end, the full configuration file will look like this:

server {  
  listen 80;
  server_name www.horchata.me horchata.me;
  return 301 https://$server_name$request_uri;
}

server {  
  listen 443 ssl http2;
  server_name www.horchata.me horchata.me;
  ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
  ssl_prefer_server_ciphers on;
  ssl_ciphers "EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH";
  ssl_ecdh_curve secp384r1;
  ssl_session_cache shared:SSL:10m;
  ssl_session_tickets off;
  ssl_stapling on;
  ssl_stapling_verify on;
  resolver 8.8.8.8 8.8.4.4 valid=300s;
  resolver_timeout 5s;
  add_header Strict-Transport-Security "max-age=63072000; includeSubdomains";
  ssl_certificate /etc/letsencrypt/live/horchata.me/fullchain.pem;
  ssl_certificate_key /etc/letsencrypt/live/horchata.me/privkey.pem;
  ssl_dhparam /etc/ssl/certs/dhparam.pem;

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

  location / {
    root /var/www/html/;
  }
}

Testing the configuration

Once you arrived at this part, save the file and test Nginx's configuration with the test command ( sudo nginx -t ), it should give you the following output:

nginx: the configuration file /etc/nginx/nginx.conf syntax is ok  
nginx: configuration file /etc/nginx/nginx.conf test is successful

This is exciting, once you restart Nginx with sudo service nginx restart , you can head up to your browser and type in yourdomain.tld , or in this case horchata.me :

Congratulations! Your site is now using the HTTPS protocol, the connection is encrypted and you can start obtaining the benefits of HTTPS:

  • You will get more referral information on Google Analytics, when a user goes from a secure connection to a site that has HTTP, the referral information isn't passed, but with HTTPS, you'll see more information about where you're users are coming from.

  • A more secure connection , though it doesn't eliminate the need to add more layers of security to your website and prevent the exploitation of common vulnerabilities such as XSS attacks, SQL injections, being "iframed", social engineering, session hijacking and other techniques.

  • Google will rank your site better, it's been known for a while that Google gives a ranking boost to sites that run over HTTPS.

  • Your site will gain more trust from your users after they see the green padlock on their address bars; this has been also a debate on how easy is to get a certificate now to create Phishing sites , making them look more legitimate now.

  • Possibility to enable HTTP2 and Spdy , both protocols serve your website faster, only on HTTP2 or Spdy enabled browsers, if the browser doesn't support them, it will fall back to SSL/TSL.

Auto-renewing the certificate

Sadly, these certificates only last 90 days, once they expire, your visitors will get a nasty message saying that "someone might steal their information", but more specifically, it will say that the certificate expired down below. To prevent that for happening, we can use LetsEncrypt's client to auto-renew the certificate every 2 months or so. We'll do this by setting up a cronjob which will run two simple commands every 60 days.

If you're still logged into your server via SSH (log in if not), type in the following command:

sudo crontab -e

It will ask you to specify the editor that you want to use to edit the file, type the number of your editor of choice (I always use Nano for small tasks like this) and add these lines at the very end of the file:

30 2 * * 1 /opt/letsencrypt/letsencrypt-auto renew >> /var/log/le-renew.log  
35 2 * * 1 /etc/init.d/nginx reload

Questions and issues I had

Codetut's certificates (for the blog and the CDN I'm running through S3 + Cloudfront using my AWS free tier and student credit) were installed just a couple of weeks ago (check the post's date) so I can assure you that my experience with cryptography, sysadmin and SSL certificates is/was null , void, zero, it went to /dev/null . I'm starting to get a better understanding on these topics but I can, for now, use my "noobness" to apologize for the lack of explanation in this post, I encourage you to do a small research on every foreign concept that you see in order to get a broad view of what you're doing.

Since I'm running this blog via Ghost , I had to change the config.js file to switch the canonical URL to https://codetuts.tech but, once I restarted the Ghost service, Chrome nicely let me know that blog had "too many redirections" or a " redirection chain !" I was scared to death, didn't know what to do until I read somewhere that I had to add the following lines to my Nginx configuration file:

proxy_set_header  X-Real-IP          $remote_addr;  
proxy_set_header  X-Forwarded-For    $proxy_add_x_forwarded_for;  
proxy_set_header  Host               $http_host;  
proxy_set_header  X-Forwarded-Proto  $scheme;

Especially the 2nd and 4th one, X-Forwarded-Proto saved my life and prevented the redirection chain, the $scheme variable stands for the protocol (HTTP or HTTPS). Oddly enough, if I added these inside the main location block (in the server block) where my proxy_pass statement for the upstream variable (that pointed to my localhost), all my assets disappeared making the blog look like it had no CSS stylesheet. But once I moved the statements outside the server block, everything worked like a charm.

All of that is in the past now, I'm using this Nginx configuration file now for my Ghost blog (here at Codetuts).

I also had the following question: What would happen if I wanted to move to a different server? Can I use a different certificate before this one expires? Yes , you can use a different certificate, just replace the paths on your Ngix configuration. When I moved from DigitalOcean to an EC2 instance on AWS, I had to choose between moving my certficate files to the new server or issue new ones with LetsEncrypt, I opted for issuing a new cert and it worked like a charm; though I'm kinda sure some users will have to delete the browser's cache for that to work.





About List