Install a free SSL Certificate with LetsEncrypt and Nginx
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 at the end of the tutorial. 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.
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 (
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 (default is a file, not a folder) 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.
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. The
server_name part handles which domains and subdomains to listen to.
LetsEncrypt is 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:
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:
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.
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:
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:
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.
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:
We’ve just started with the preparations but now it’s actually time to install the certificate with LetsEncrypt. Run the following command to navigate to the root directory of the LetsEncrypt command-line tool:
Now, run this command to execute the command-line tool:
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:
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:
It’s also advised that you create a Diffie-Helman group to increase security on 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:
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
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:
Then, let’s add a recommended SSL/TLS configuration and point to our Diffie-Hellman group:
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.
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:
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:
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:
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:
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 my case
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.
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:
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:
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:
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 Nginx configuration. When I moved from DigitalOcean to an EC2 instance on AWS, I had to choose between moving my certificate 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.
Another thing you have to do is head up to your Google Webmasters account (also known as Search Console) and make sure you have four sites added to prevent the possibility of a duplicated content penalty:
The version with HTTP and no WWW
The version with HTTPS and no WWW
The version with HTTP and WWW
The version with HTTP and WWW
Another thing you have to do is replace every internal link to your HTTP version to the HTTPS version. If you have control over some of the links that point to your website, change them to the HTTP version as well (I’m talking about social media links, facebook pages you administer, etc). You may also want to update your Google Analytics account.