This post is more than 3 years old.

SSL is one of the most important technologies in use on the modern web. It enables all kinds of business, collaboration, commerce, activism and communication to happen securely, and the Internet couldn't thrive without it.  Yet for the average person, alongside domain name registration and management, obtaining and renewing SSL certificates has always been one of the least accessible and convenient parts of having a website.

So I was particularly proud when a year ago my employer Automattic became a sponsor of the Let's Encrypt initiative and even more proud earlier this month when we rolled out free SSL for all domains hosted on WordPress.com, using Let's Encrypt certificates. All of the sudden a huge portion of the world's websites were using SSL to make sure communications between site owners and users are encrypted and secure - amazing!

Let's Encrypt is itself pretty amazing. A bunch of industry experts got together and decided it was time to make the process of obtaining SSL certificates free, automatic, secure, transparent, open and cooperative. This is a long way from what it looked like in the late 1990s, when just a few "certificate authority" options existed, you could expect to pay $100 or more for a certificate, and the application process was painfully slow and analog (think faxing your corporate articles of organization and a photocopy of your driver's license to a call center somewhere), and that's all before you had to mess around with recompiling or reconfiguring Apache to use SSL on your site(s). Even with Let's Encrypt and other modern options some of the concepts and steps remain too technical for many site owners to tackle, but it's getting better all the time.

I'm used to paying around $10/year for SSL certificates on a few of my personal sites, and I actually haven't minded that price point given that the rest of the process has been pretty easy for me to manage. But I recently decided to try using a Let's Encrypt SSL certificate on a site that didn't have one yet, and I'm sharing the steps involved here.

My goal was to add an LE SSL cert to the site 47374.info, which I recently wrote about updating with a new look. The site is hosted on a shared cPanel server, which means that my main avenue for accessing and managing my site is through whatever tools cPanel offers. There's a Let's Encrypt cPanel/WHM plugin that aims to let the people who own and operate cPanel servers (usually hosting companies) automatically issue LE certs for sites living there, but that option wasn't available from my host yet.

So the first thing I needed to do was be able to run the Let's Encrypt client software somewhere so I could generate and manage certificates, and I decided to try this out on my Mac OS X system. The client is written mostly in Python so I was glad to already have the pip Python package installation tool on my system as a head start, but if you don't have it already, the instructions are pretty straightforward: download get-pip.py and then run it with

$ sudo python get-pip.py

and it will walk you through the rest. You might also have to run

$ sudo pip install -U pip

to make sure you've got the latest version.

Then I had to make sure that some libraries that the letsencrypt client depends on were installed:

$ sudo pip install pbr
$ sudo pip install --no-deps stevedore
$ sudo pip install --no-deps virtualenvwrapper

Once that was done, I was ready to get the letsencrypt client working.

$ cd bin
$ git clone https://github.com/letsencrypt/letsencrypt
$ cd letsencrypt
$ ./letsencrypt-auto --help

This downloads the latest version of the letsencrypt software and confirms that it will run okay. If that last step results in an error, you may have some other dependencies to install/resolve on your system first.

From there I pretty much followed the instructions in the Let's Encrypt documentation, along with some helpful discussions in the Let's Encrypt community forums, especially this one. Here's the command I ran (might have to scroll to the right to see all of it):

$ ./letsencrypt-auto certonly -a manual --email my-cert-email@address.com --rsa-key-size 4096 -d 47374.info -d www.47374.info --debug

Let's break that down:

  • certonly says that I'm just generating certificates, not trying to do any other stuff the LE client does
  • -a manual says that I'm going to perform domain validation (which is how I prove to LE that I'm the owner of the site getting the new cert) manually
  • --email my-cert-email@address.com (with the email address replaced to something more intelligent) tells the LE system what my contact address is for this certificate
  • --rsa-key-size 4096 says I'm generating a certificate using a 4096 bit key, which means it will take the NSA's quantum computers a few extra minutes to break the encryption on SSL communication with my site
  • -d 47374.info -d www.47374.info says that I want to generate certificates for both of those domain names. Doing both is important so that if you redirect users from one to the other, they won't receive a certificate warning when their initial request comes in and has a domain that doesn't match with the certificate's domain.
  • --debug tells the client to spit out some extra information about the work it's doing

Don't worry if you don't understand all of that, but I find it helpful to review. Especially because the very next thing the letsencrypt tool might ask you to do is enter your administrator password to make some "root" level changes on your system. You should do this at your own risk, balanced against trust that the software has been developed and reviewed by many security-conscious people. You will probably also be asked to give permission to store the IP address your request is coming from in the LE database, and to agree to their terms of use.

Once you've done that, the letsencrypt tool will prompt you to "Make sure your web server displays the following content at http://example.com/.well-known/acme-challenge/1234567890 before continuing" with both a funny-looking link using one of the domains you specified in the command above and a string of random-looking characters like that it wants to be available at that link. Your task now is to create the /.well-known/acme-challenge/123467890 file within your website (replacing the 1234567890 part with whatever it's telling you to use), and to populate it with a single line of text with the string of random-looking characters. You can do this via FTP, SFTP, SSH if you have it, the cPanel "file manager" or whatever works for you.

Open the link in your browser to make sure that you can successfully visit it AND that you see the string of random-looking characters as the only content that comes back.

Then you can tell the letsencrypt client to continue, and it will try to fetch the same URL and do the same verification. If it can't reach it or doesn't find the string it expects, it will tell you about it and you'll need to figure out where the problem is. Otherwise, it will tell you it successfully validated the domain, and have you repeat the process for any variations (e.g. www) you specified.

When the script is done, you'll probably have a new directory on your OS X system, /etc/letsencrypt/archive/, and this is where your freshly issued SSL certificates now live. You might have to "sudo su" to get to that directory, depending on how permissions are set up:

$ sudo su
$ cd /etc/letsencrypt/archive/

Now you have three files of importance:

  • cert.pem: The SSL certificate
  • chain.pem: The Certificate Authority Bundle (CABUNDLE)
  • privkey.pem: The certificate's private key

It's very important that you keep the private key file secure. Don't email it around, don't put it anywhere public. I suspect this is why the letsencrypt directory is created on your system in a directory only accessible to privileged users.

Now comes installing the certificate onto your site via cPanel. In my case, that started by logging in to cPanel and clicking on the "SSL/TLS" icon that appeared in the "Security" section.

Then I went to the "Private Key" manager and scrolled down to "Upload a New Private Key." I pasted the contents of privkey.pem into the indicated box, and entered a description of "47374.info and www.47374.info" and then clicked "Save." I got confirmation from cPanel that my private key had been uploaded.

Then I went back to the "SSL/TLS" main screen and selected the "Certificate" manager option. Similar to the private key process, I scrolled to "Upload a New Certificate" and pasted in the contents of cert.pem there, entered a description, and clicked "Save."

Finally I went back to the "SSL/TLS" main screen and selected the "Install and Manage SSL for your site" option. I scrolled to "Install an SSL Website" and selected the domain I'd already uploaded the key and certificate for. When I did this, an "Autofill by Domain" button appeared that, when clicked, filled in the certificate and private key information for me. All that remained was to scroll to the "CABUNDLE" field and paste in the contents of the chain.pem file. I clicked "Install Certificate" and after some processing, my SSL certificate became active on my site. Yay!

By default this meant that two different versions of my website were accessible - an SSL-secured version and a regular, non-SSL version. I visited the SSL version and worked to resolve any "mixed-content" issues: finding resources loaded by my site over insecure HTTP connections, and changing them to use HTTPS or removing them. This is important to fix because most browsers will not label your site as "secure" (usually via the little lock icon in the URL bar) if there are mixed-content issues remaining.

Once all of those were addressed, I was ready to redirect all non-SSL visitors to the SSL version of my site. I did this with the following lines in my site's .htaccess file, but you can add something similar via the cPanel redirect manager if that's more comfortable for you.

# non-SSL URLs
RewriteCond %{HTTPS} !=on
RewriteCond %{HTTP_HOST} ^47374.info [NC,OR]
RewriteCond %{HTTP_HOST} ^www.47374.info [NC]
RewriteRule ^(.*)$ https://47374.info/$1 [L,R=301,L]

This directs visits to an insecure version of my site at either the "www" or "non-www" URL to the "non-www" secure, HTTPS URL. Yay!

All in all, this process took me about half an hour because I was doing it for the first time and moving a little slowly to make sure I had everything right. Let's Encrypt certificates expire after 90 days, so I can imagine how someone maintaining a bunch of sites would eventually want to automate this, and there are tools emerging to do that. I can also see the appeal of paying $10 to not have to worry about this four times per year, but on principle I will probably donate that money to the Let's Encrypt folks to help them make SSL available to even more people.

I hope the above process is obsolete soon. If most cPanel hosts install the LE plugin to automate it for their users, there will soon be little excuse for any new site to be created without SSL from the start, and for any existing site not to have it within a year or sooner. It's a great turning point for security on the web, and I'm really thankful to all of the people who made it happen.

If you have any questions about or suggestions for the notes above, please let me know - I'm always glad to share faster/better ways to do these kinds of things.

 

Photo by Peter Stevens

2 thoughts on “Let's Encrypt SSL certificates on cPanel hosted sites

  1. Hi Chris, great post and clear. For some reason I'm getting errors on one of the first steps:
    ./letsencrypt-auto certonly -a manual --email my-cert-[email]@[domain] --rsa-key-size 4096 -d [domain] -d http://www.[domain] --debug

    gives the following errors:
    Bootstrapping dependencies via Mac OS X...
    Using Homebrew to install dependencies...
    /System/Library/Frameworks/Ruby.framework/Versions/2.0/usr/lib/ruby/2.0.0/rubygems/core_ext/kernel_require.rb:55:in `require': cannot load such file -- mach (LoadError)
    from /System/Library/Frameworks/Ruby.framework/Versions/2.0/usr/lib/ruby/2.0.0/rubygems/core_ext/kernel_require.rb:55:in `require'
    from /usr/local/Library/Homebrew/extend/pathname.rb:2:in `'
    from /System/Library/Frameworks/Ruby.framework/Versions/2.0/usr/lib/ruby/2.0.0/rubygems/core_ext/kernel_require.rb:55:in `require'
    from /System/Library/Frameworks/Ruby.framework/Versions/2.0/usr/lib/ruby/2.0.0/rubygems/core_ext/kernel_require.rb:55:in `require'
    from /usr/local/Library/Homebrew/global.rb:3:in `'
    from /System/Library/Frameworks/Ruby.framework/Versions/2.0/usr/lib/ruby/2.0.0/rubygems/core_ext/kernel_require.rb:55:in `require'
    from /System/Library/Frameworks/Ruby.framework/Versions/2.0/usr/lib/ruby/2.0.0/rubygems/core_ext/kernel_require.rb:55:in `require'
    from /usr/local/Library/brew.rb:15:in `'

    Any ideas?

Leave a Reply

Your email address will not be published. Required fields are marked *