January 20, 2016

A few days ago we showed you how to add an SSL certificate – one that you can purchase – to your newly created Amazon AWS Instances. As I was researching SSL certificates, I came across an interesting initiative: Let’s Encrypt.  It’s an organization dedicated to serving up free SSL certfiicates so you can encrypt your site data.

According to their website, they have six key points to their reasoning:

  • Free: Anyone who owns a domain name can use Let’s Encrypt to obtain a trusted certificate at zero cost.
  • Automatic: Software running on a web server can interact with Let’s Encrypt to painlessly obtain a certificate, securely configure it for use, and automatically take care of renewal.
  • Secure: Let’s Encrypt will serve as a platform for advancing TLS security best practices, both on the CA side and by helping site operators properly secure their servers.
  • Transparent: All certificates issued or revoked will be publicly recorded and available for anyone to inspect.
  • Open: The automatic issuance and renewal protocol will be published as an open standard that others can adopt.
  • Cooperative: Much like the underlying Internet protocols themselves, Let’s Encrypt is a joint effort to benefit the community,

Whereas some people are turned off by the cost of an SSL Certificate from a trusted authority, this becomes a much better option.

Note: There are a couple of other tutorials on setting up the “letsencrypt” package, but I ran into a few snags post-setup that I want to address, specifically on the WordPress front.

Step 0: Prerequisites

We’re setting this up on Ubuntu, so we’ll need:

  • A user with sudo privileges. If you’ve been following the tutorials, you have this.
  • Your domain registrar login information.
  • Your domain pointed to the Amazon AWS Elastic IP with an A record. Letsencrypt validates the domain ownership via the A record, so make sure that the IP address is set up properly in your domain registrar.
  • You’ll need to stop the server for a few minutes to allow letsencrypt to run a web server on port 80. Make any necessary precautions.
  • There is a second, ‘webroot’ option that we can tap into for automatic renewals, so once the initial setup process is done we won’t have to shut down the server again.

Step 1: Installing the Let’s Encrypt Installation Scripts

Right now, it’s not available via the package managers – I have a suspiscion that will change soon. But we can definitely clone it via Git.

Update your packages…

$ sudo apt-get update

…and install both git and bc.

$ sudo apt-get -y install git bc

BC, by the way, is an “arbitrary precision language calculator”. Neat!

Clone the letsencrypt package into /opt:

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

Now that it’s installed and we have our dependencies, we can start the installation package.

Step 2: Using letsencrypt and Obtaining a Certificate

Stop your Nginx server…

$ sudo service nginx stop

…and check to see if port 80 is open and in use.

$ netstat -na | grep ':80.*LISTEN'

If you don’t seen any output… congrats! You’re ready to go!

$ cd /opt/letsencrypt
$ ./letsencrypt-auto certonly --standalone

You’ll see a few things initialize, and you’ll be asked for a few bits of information. If this is your first time setting up, you’ll be asked for your email address. You’ll use this to receive notices and any recovery options, so make sure it’s valid and hit <OK>.

encrypt-email

Then a terms of service screen. Read the terms and click <Agree>.

encrypt-tos

Now, enter the domain names you want to secure. If you have multiple subdomains (www vs non-www) enter them both. I usually enter the root top level domain (non-www) first, but that’s my preference:

encrypt-tld

Let the command finish running. You should see a large wall of text with a few important pieces of information:

  • Location of the saved certificate chain and keys:
    /etc/letsencrypt/live/yourdomain.com
  • Expiration date of the certificate

You’ll notice that the certificate expires quickly – 90 days. We’ll automate the renewal process later in the tutorial.

You’ll also notice, if you list the location above…

$ sudo ls /etc/letsencrypt/live/yourdomain.com

That there are four files:

  • cert.pem – your domain’s certificate
  • chain.pem – the Let’s Encrypt chain certificate
  • fullchain.pem – a concatenated (combined) file of cert.pem and chain.pem
  • privkey.pem – your certificate’s private key

You’ll need fullchain.pem and privkey.pem, so mentally note where those are:

/etc/letsencrypt/live/yourdomain.com/fullchain.pem
/etc/letsencrypt/live/yourdomain.com/privkey.pem

Without those, we can’t set up the next part: the Nginx configuration.

Step 3: Setting up Nginx

This is where the tutorial diverges from other similar tutorials. Other tutorials have you redirect the non-SSL traffic into the SSL traffic, but we actually need to keep the non-SSL and SSL traffic open with the same configuration, and use plugins to force SSL on the WordPress site. Otherwise, plugins such as Jetpack will cease to function correctly. 

We’re going to change the server configuration slightly from when we set it up initially. We’re still going to use all of the same variables from before, but we’re adding our elements to define the SSL certificates:

# Define the microcache path.
fastcgi_cache_path /etc/nginx/cache levels=1:2 keys_zone=microcache:100m inactive=60m;

server {
 listen [::]:80;
 listen 80;
 listen 443 ssl;

 root /sites/yourdomain.com/htdocs;
 index index.php index.html index.htm;

 server_name yourdomain.com www.yourdomain.com;

 ssl_certificate /etc/letsencrypt/live/yourdomain.com/fullchain.pem;
 ssl_certificate_key /etc/letsencrypt/live/yourdomain.com/privkey.pem;
 ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
 ssl_prefer_server_ciphers on;
 ssl_ciphers 'EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH';

 # Include the basic h5bp config set
 include h5bp/basic.conf;
 
 location / {
 index index.php;
 try_files $uri $uri/ /index.php?$args;
 }
 
 location ~ \.php$ {
 fastcgi_cache microcache;
 fastcgi_cache_key $scheme$host$request_method$request_uri;
 fastcgi_cache_valid 200 304 10m;
 fastcgi_cache_use_stale updating;
 fastcgi_max_temp_file_size 1M;
 fastcgi_pass 127.0.0.1:9000;
 fastcgi_index index.php;
 fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
 include fastcgi_params;
 
 # Local variables to track whether to serve a microcached page or not.
 set $no_cache_set 0;
 set $no_cache_get 0;
 
 # If a request comes in with a X-Nginx-Cache-Purge: 1 header, do not grab from cache
 # But note that we will still store to cache
 # We use this to proactively update items in the cache!
 if ( $http_x_nginx_cache_purge ) {
 set $no_cache_get 1;
 }
 
 # If the user has a user logged-in cookie, circumvent the microcache.
 if ( $http_cookie ~* "comment_author_|wordpress_(?!test_cookie)|wp-postpass_" ) {
 set $no_cache_set 1;
 set $no_cache_get 1;
 }
 
 # fastcgi_no_cache means "Do not store this proxy response in the cache"
 fastcgi_no_cache $no_cache_set;
 
 # fastcgi_cache_bypass means "Do not look in the cache for this request"
 fastcgi_cache_bypass $no_cache_get;
 }
}

The variables in red are new.  Walking through them, we end up doing the following:

  • Adding a ‘listen’ reference to allow traffic to flow through port 443, the secure SSL port.
  • setting the paths for the certificate and certificate key – fullchain.pem and privkey.pem – that we generated earlier.
  • Setting an additional layer of security by specifying what ciphers and protocols we want to allow. The list above is a very modern list and will disallow outdated or insecure protocols from attempting to use the secure site.

Save and exit, and restart your webserver:

$ sudo service nginx start

Technically, we’re finished! You can visit https://yourdomain.com or https://www.yourdomain.com and see the secure site in its full glory!

Step 4: Post-Setup for WordPress

You may notice that the lock isn’t green (it may be greyed out). We still need to set WordPress to specifically use the HTTPS traffic over the non-HTTPS traffic. We can do that with two steps:

  1. Change the site_url and blog_url to https://yourdomain.com. You can do this either in the WordPress Settings > General screen, or by editing your wp-config.php file and adding these variables:
    define('WP_HOME','https://yourdomain.com');
    define('WP_SITEURL','https://yourdomain.com');
  2. Using the WordPress Force SSL to redirect all other traffic (images, links, etc) from non-HTTPS to HTTPS.

Note: Other tutorials, as I mentioned above, require you to redirect the non-SSL traffic in, but I found that this caused problems with plugins that require port 80 to be free and clear – like Jetpack. I wasn’t getting any site stats for a few days and had to dive in to figure out why. Turns out it was being rejected at the redirection point. Opening port 80, and forcing the redirection at the WordPress level, did the trick.

Step 5: Auto-Renewals of SSL Certificates

The Let’s Encrypt certificates expire after 90 days. Let’s set up a server-side cron job (a script that runs automatically) to make sure we don’t have to worry about it again.

Instead of the standalone script we ran earlier, we’re going to set up a webroot script to run automagically in the background. Let’s Encrypt will use a special folder in your web root (/.well-known) to place its files, so let’s make sure that the server can access anything in those folders:

$ sudo nano /etc/nginx/sites-enabled/yourdomain.com

In the “server” block, add the following:

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

Save and exit. This will open up that location’s files for letsencrypt, and drop in a hidden file that the scripts will use for validation. This way, we can keep our web server running – and not have to stop it to renew the certificates.

You may want to figure out what your web root folder is. If you only have one site, it’ll be at /sites/yourdomain.com/htdocs (if you’ve been following my tutorials – otherwise, it’ll be different for each user).

Run this script, and make sure the variables are filled in with your information:

$ cd /opt/letsencrypt
./letsencrypt-auto certonly -a webroot --agree-tos --renew-by-default --webroot-path=/sites/yourdomain.com/htdocs -d yourdomain.com -d www.yourdomain.com

This goes through the prompts we filled in earlier, renews the certificate, and passes it through to the server (in the same locations you set up earlier).

Restart Nginx…

$ sudo service nginx reload

…to have it use the newly created certificates.

Now we can create a cron job to automate this process.

Instead of having this long string entered in each time, let’s set up a configuration file we can refer to if we have problems or questions. A sample file is already provided, so we can start there to save time:

$ sudo cp /opt/letsencrypt/examples/cli.ini /usr/local/etc/le-renew-webroot.ini

Edit the file with your editor of choice:

$ sudo nano /usr/local/etc/le-renew-webroot.ini

There’s a lot of commented-out (and useless) stuff in this file. So, let’s cut it down and fill in only the essentials: email address, domains, and the webroot authentication stuff.  You should have something similar to this:

# Use a 4096 bit RSA key instead of 2048
rsa-key-size = 4096
# Registered Email
email = you@yourdomain.com
# Domain(s) to Secure
domains = yourdomain.com, www.yourdomain.com
# Webroot Authentication and Path
authenticator = webroot
webroot-path = /sites/yourdomain.com/htdocs

There’s all the information we used earlier, in a nice and compact format. Let’s test it to see if the certificate renews this way:

$ cd opt/letsencrypt
$ ./letsencrypt-auto certonly -a webroot --renew-by-default --config /usr/local/etc/le-renew-webroot.ini

Notice how we set the –config flag to read the file we just created.  If all goes well, reload nginx again – just in case!

Finally we can set up this script with a cron job.

There’s a Gist that has a nice packaged script we can run. Do your due dilligence and look it over before you install it.

$ sudo curl -L -o /usr/local/sbin/le-renew-webroot https://gist.githubusercontent.com/thisismitch/e1b603165523df66d5cc/raw/fbffbf358e96110d5566f13677d9bd5f4f65794c/le-renew-webroot
$ sudo chmod +x /usr/local/sbin/le-renew-webroot

This script can be executed anywhere. If you want, you can use it now to see how many days are left until your certificate needs to be renewed:

$ sudo le-renew-webroot
Checking expiration date for example.com...
The certificate is up to date, no need for renewal (89 days left).

Then, the cron job itself:

$ sudo crontab -e

Head to the bottom of the file and insert this all onto one line:

30 2 * * 1 /usr/local/sbin/le-renew-webroot >> /var/log/le-renewal.log

There’ll be a log file created at /var/log/le-renewal.log, so if anything goes awry you’ll have a reference as to how to fix it.

And that’s it!  You’re now running an amazing free encryption from Let’s Encrypt, letting it renew automatically, and allowing WordPress’ finicky internal features (like Jetpack) still authenticate.

  • D4vid

    Hi Mitch,

    Thanks for the great tutorial.

    However, would you have a comparable method for adding the LE ssl certificate on a Apache server on EC2 with the same objective to install wordpress on it?

    Thanks a lot

    • Mitch Canter

      Hey D4vid,

      Great question! Installation for this certificate is 99% the same – the only difference is where you make the SSL Certificate Declarations. Instead of editing the file located in:

      /etc/nginx/sites-available

      You’re going to find the VirtualHosts file, usually located in either:

      /etc/httpd

      Or:

      /etc/apache2

      DigiCert has a great tutorial – start at Step 2!

      • D4vid

        Thank you very much!

      • D4vid

        Hi again,

        I cannot find where it differs, I’m really not sure how to deal with the differences between nginx and apache.
        Being still in “learning mode”, could you be more specific?

        Thanks very much 🙂

  • gordonillan

    When is “a few days ago?” This page is undated.

    • Ocramot

      I can see “January 20, 2016” on top of the page, right below his picture, and next to the “N comments”

  • I had some issues running
    ./letsencrypt-auto certonly –standalone
    I was getting “E: There are problems and -y was used without –force-yes”
    Tol resolve this I had to install the packages manually.

  • Alexander Torrado

    Is this page signed with letsencrypt certificate? Because my chrome browser shows your https:// as UNSAFE

    • Mitch Canter

      Good catch! I was out of town when the Let’s Encrypt auto-renew didn’t fire for some reason. That’s been dealt with.

      ~mitch

    • Orppa

      You could just check what your browser says about the issuer. That happens on typical browser by clicking on the lock icon. Also at the same, you could check why it says unsafe, which is not the most critical of reasons, even though it should be addressed too.

    • yes it is, but that is due to mixed content.
      For example the image encrypt-email.png is loaded via http (and is redirected to https)

      Plugins like velvet blues update url will replace most of the occurences of http and convert them to https.