Rack of Ethernet switches.

How to Use Let's Encrypt TLS Certificates on GoDaddy Shared Hosting

The Goal

I wanted to update a set of web sites that are hosted on GoDaddy. The sites had been there for a while, hosted on "Economy Linux" platforms that had been the standard when the sites were created. In the past year or two GoDaddy had moved forward to their newer "Economy Linux with cPanel" platform.

The newer platform was based on a significantly newer distribution, meaning security improvements and possibly performance improvements. What's more, the price had dropped, no doubt due to the rise of Google Cloud Platform. If I was deploying those sites today, I would go with the Google Cloud Platform.

This happened in September 2017, when Google was warning that Chrome would start displaying "Insecure" on plain old HTTP pages within a few weeks, and a lack of HTTPS would start to hurt you in search result rankings some time in the coming months.

The business owner of one of the sites had purchased a TLS certificate from GoDaddy for about US$ 60/year. On the other sites I went with free Let's Encrypt TLS certificates.

GoDaddy wants to sell certificates to make up for reduced hosting income, so while they don't block the use of Let's Encrypt certificates, they certainly don't help you to do it. But it's not at all hard to do.

Step 1: Upgrade

The old platform was really old. It had a file /etc/*-release which revealed that it was based on CentOS 5.5!

Its version of OpenSSH was so old that it still supported nothing but the DSA (or ssh-dss) public-key algorithm for host keys. OpenSSH 7.0 and later disabled DSA host keys. That meant that you no longer could connect from reasonably current platforms without adding a stanza to the bottom of your /etc/ssh/ssh_config file:

$ tail /etc/ssh/ssh_config

## GoDaddy only supports the ssh-dss (DSA) public key algorithm
## for host keys.  OpenSSH 7.0 and greater disables it.  For
## details see:  http://www.openssh.com/legacy.html
Host example1.com example2.com example3.com example4.com
   HostkeyAlgorithms ssh-dss 

The newer GoDaddy platform, current as of September 2017, is CloudLinux OS. It's really stripped down, with neither os-release nor lsb-release in /etc. CloudLinux OS is fully compatible with CentOS/RHEL packages.

It is also really tightened down. Your home directory is the only subdirectory of /home. You can only see your own processes in /proc and with ps and top. The /dev/ directory has very few devices other than the pseudo-ttys. No disk devices appear! It uses CageFS, making it similar to a container.

The OpenSSH service is version 5.3, two major releases behind the current, but it's new enough to interoperate with an OpenSSH 7.5 client.

$ ssh -vv myusername@example.com uname -a
[...  output deleted   ...]
[... here are versions ...]
debug1: Local version string SSH-2.0-OpenSSH_7.5
debug1: Remote protocol version 2.0, remote software version OpenSSH_5.3
[...        output deleted        ...]
[... here are server capabilities ...]
debug2: peer server KEXINIT proposal
debug2: KEX algorithms: diffie-hellman-group-exchange-sha256,diffie-hellman-group-exchange-sha1,diffie-hellman-group14-sha1,diffie-hellman-group1-sha1
debug2: host key algorithms: ssh-rsa,ssh-dss
[... and cipher/MAC offers both ways ...]
debug2: ciphers ctos: aes128-ctr,aes192-ctr,aes256-ctr,arcfour256,arcfour128,aes128-cbc,3des-cbc,blowfish-cbc,cast128-cbc,aes192-cbc,aes256-cbc,arcfour,rijndael-cbc@lysator.liu.se
debug2: ciphers stoc: aes128-ctr,aes192-ctr,aes256-ctr,arcfour256,arcfour128,aes128-cbc,3des-cbc,blowfish-cbc,cast128-cbc,aes192-cbc,aes256-cbc,arcfour,rijndael-cbc@lysator.liu.se
debug2: MACs ctos: hmac-md5,hmac-sha1,umac-64@openssh.com,hmac-sha2-256,hmac-sha2-512,hmac-ripemd160,hmac-ripemd160@openssh.com,hmac-sha1-96,hmac-md5-96
debug2: MACs stoc: hmac-md5,hmac-sha1,umac-64@openssh.com,hmac-sha2-256,hmac-sha2-512,hmac-ripemd160,hmac-ripemd160@openssh.com,hmac-sha1-96,hmac-md5-96
[...       output deleted        ...]
[... here's what they agree upon ...]
debug1: kex: algorithm: diffie-hellman-group-exchange-sha256
debug1: kex: host key algorithm: ssh-rsa
debug1: kex: server->client cipher: aes128-ctr MAC: umac-64@openssh.com compression: none
debug1: kex: client->server cipher: aes128-ctr MAC: umac-64@openssh.com compression: none
[... output deleted ...]

I had the usual excellent live help over the phone. That's good, because GoDaddy's graphical panels are mysterious.

A roundabout sequence of clicks, copy, paste, and more clicks can get an authorized public key uploaded. That is, the public key matching the private key of a user identity.

Surprisingly, you still can't log in directly even though you just uploaded a key that was installed in ~/.ssh/authorized_keys (and, also, authorized_keys2 for some reason). You have to go back to the cPanel web interface and do some clicking that leads to authorizing that new key identity.

You can't see what's going on, but I'm guessing that you have to get the newly configured user identity listed as an AllowUsers entry in the sshd_config file. CageFS hides the details, in /etc/ssh you only see the filese ssh_config and moduli.

Once you can load your private keys into your SSH agent on your client and then make seamless key-based SSH connections, you're ready to move on.

Credit Where Credit Is Due

I created this page so I will have notes on how to do this even if other sites change or disappear.

The deployment steps in the following are based on those on the great TryingToBeAwesome.com site. Some steps on that page came from other contributors. And finally, all this is built on top of work by the authors of the acme.sh tool. That's a shell script implementing the ACME client protocol. ACME or the Automatic Certificate Management Environment is a protocol for automating interactions with certificate authorities.

There is no need for Python or Go or the official Let's Encrypt client, as acme.sh is a pure shell script. Nor is there any need for root access. This gives us an ACME tool for the shared hosting environment.

Step 2: Install acme.sh

Log in to your GoDaddy shared hosting system and run the following to download and then install the acme.sh tool. The following is a slightly more cautious sequence than some recommendations, which download it with curl while piping it directly into a shell:

$ wget https://get.acme.sh
$ more get.acme.sh
[... are you OK with this? ...]
$ ./get.acme.sh
$ mv get.acme.sh ~/.acme.sh
$ exit 

Yes, the last step above is to log out, because we need to make a fresh login to get the environment set up correctly. Two lines have been added to ~/.bashrc, referencing a two-line file that sets an environment variable and defines an alias:

$ cat ~/.bashrc
# .bashrc

# Source global definitions
if [ -f /etc/bashrc ]; then
        . /etc/bashrc
fi

# User specific aliases and functions
. "/home/myusername/.acme.sh/acme.sh.env"
$ cat ~/.acme.sh/acme.sh.env
export LE_WORKING_DIR="/home/myusername/.acme.sh"
alias acme.sh="/home/myusername/.acme.sh/acme.sh

Step 3: Get a GoDaddy API Key and Secret String

On your local desktop, start a browser and load https://developer.godaddy.com/keys/.

For some unexplained reason you must first generate a test key/secret pair. It's a single button click. Then you can generate a production key and secret.

I used vim within my SSH session to edit ~/.acme.sh/keys and store both pairs.

$ cat ~/.acme.sh/keys
Test key:
Key:    qsLLqQyqhu6Lx1q85ctZbNYT90daRvR25
Secret: Bdx4ap8gi0kK8jOosHD1zf

Production key:
Key:    AHoQgGr+qi1qModOIKZTktXzGak6C1MV0K
Secret: mkNmVBf/6ZaWkMx0Bi5sX2

Of course those aren't the actual values! They're the result of using dd to send several bytes from /dev/random into the base64 command to provide appropriately sized examples.

Step 4: Issue a Certificate

Set two environment variables and then you're ready to issue a certificate. The --help option explains the pieces and suggests other options. The certificate will be good for the domain itself plus www.example.com.

$ export GD_Key=AHoQgGr+qi1qModOIKZTktXzGak6C1MV0K
$ export GD_Secret=mkNmVBf/6ZaWkMx0Bi5sX2
$ acme.sh --help
[... 94 lines of output ...]
$ acme.sh --issue \
	--domain example.com \
	--domain www.example.com \
	--webroot ~/www --dns dns_gd 
$ ls ~/.acme.sh/example.com
./      fullchain.cer     example.com.csr
../     example.com.cer   example.com.csr.conf
ca.cer  example.com.conf  example.com.key

There will be a lot of narrative output, including a 120-second countdown for DNS updates to take effect. A subdirectory of ~/.acme.sh named for the domain will appear, and the key and certificate along with the intermediate CA certificate, full chain certificates, certificate signing request, and CSR configuration will be stored there.

Step 5: Upload the Certificate and Private Key to GoDaddy

We can call the cPanel API from our command line. First, edit cpanel_uapi.sh to uncomment the DEPLOY_CPANEL_USER line and set the variable to your numeric GoDaddycustomer ID.

$ head ~/.acme.sh/deploy/cpanel_uapi.sh
#!/bin/bash
# Here is the script to deploy the cert to your cpanel using the cpanel API.
# Uses command line uapi.  --user option is needed only if run as root.
# Returns 0 when success.
# Written by Santeri Kannisto <santeri.kannisto@2globalnomads.info>
# Public domain, 2017

#export DEPLOY_CPANEL_USER=myusername
export DEPLOY_CPANEL_USER=12345678 

Now you can deploy the certificate and key:

$ acme.sh --deploy --domain example.com --deploy-hook cpanel_uapi 

You should see a message that the certificate was deployed.

Your site should now be available via HTTPS. Test it!

Step 6: Verify Continuing Renewal

The deployment has set up a nightly cron job, scheduled for a random minute between 0000 and 0100. Mine got 0032:

$ crontab -l
32 0 * * * "/home/myusername/.acme.sh"/acme.sh --cron --home "/home/myusername/.acme.sh" > /dev/null 

You can run the cron job manually:

$ acme.sh --cron --home ~/.acme.sh
===Starting cron===
Renew: 'example.com'
Skip, Next renewal time is: Sat Nov 25 16:34:49 UTC 2017
Add '--force' to force to renew.
Skipped example.com
===End cron=== 

You will see that your certificate is good for 90 days, and it will be renewed when 30 days remain. As the command suggests, you can add --force to make it happen. You will see that it generates and installs the private key and certificate and then deploys them.

Step 7: Set Up Redirection

I want to redirect all requests to the "non-www" hostname, and redirect all HTTP requests to HTTPS. That is, all of these URLs:
https://www.example.com/some/path
http://www.example.com/some/path
http://example.com/some/path
should be redirected to:
https://example.com/some/path

Add the following block to the end of ~/www/.htaccess. Don't repeat "RewriteEngine on" if it's already in the file:

$ tail ~/www/.htaccess

# Remove "www." and redirect to https
RewriteEngine on
RewriteCond %{HTTP_HOST} ^www\.(.*)$ [NC]
RewriteRule ^(.*)$ https://%1/$1 [R=301,L]
RewriteCond %{HTTPS} off
RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]

Also change "http" to "https" throughout the rest of your .htaccess and sitemap.txt files.

Test everything and make sure that it's working.

Congratulations, you're done! For now...

Step 8: Stay Up To Date

Some time in April 2018, GoDaddy changed their API. All at once, acme.sh failed and the certificate started to count down to expiration.

Update your acme.sh code.

$ ~/.acme.sh/acme.sh --upgrade 

Upgrading to acme.sh v2.7.9 solved the April 2018 problem.

$ ~/.acme.sh/acme.sh --version
v2.7.9 

Further Exploration

You can examine certificate details with the openssl command:

$ openssl x509 -in ~/.acme.sh/example.com/example.com.cer -text -noout | less
Certificate:
    Data:
        Version: 3 (0x2)
        Serial Number:
            03:f5:93:67:0e:ca:45:ad:b4:15:4f:3c:94:05:60:31:11:f1
    Signature Algorithm: sha256WithRSAEncryption
        Issuer: C=US, O=Let's Encrypt, CN=Let's Encrypt Authority X3
        Validity
            Not Before: Sep 26 15:35:00 2017 GMT
            Not After : Dec 25 15:35:00 2017 GMT
        Subject: CN=example.com
        Subject Public Key Info:
            Public Key Algorithm: rsaEncryption
                Public-Key: (2048 bit)
                Modulus:
                    00:e9:d1:fd:e4:23:3b:f3:45:41:cc:bd:35:47:1e:
                    2b:de:3d:da:b9:da:32:75:37:a3:b4:d3:a0:30:0f:
                    ca:cf:c4:3c:36:c3:86:be:56:b6:d2:4a:c7:34:cf:
                    69:05:ac:c7:31:2d:4c:d1:fb:05:35:73:a0:80:9d:
                    08:79:6d:59:3b:84:25:68:b6:ae:27:30:d7:21:1d:
                    60:3b:30:68:3e:56:10:1e:4e:1a:12:11:a2:20:65:
                    3a:12:b3:ce:e1:88:52:28:11:8f:36:6c:e9:4e:77:
                    67:a0:cc:f3:7d:0e:1d:30:51:74:b4:2d:f7:de:23:
                    7e:73:9d:91:36:49:6e:5f:8d:96:c6:77:6e:df:5e:
                    b4:19:ef:8e:16:d0:30:c6:66:03:24:bb:b9:89:5f:
                    c1:13:95:e1:8b:73:e6:b1:b2:77:0b:8c:e9:15:2d:
                    ea:43:26:bb:f7:81:0f:a9:48:03:c4:bd:be:f7:cc:
                    89:44:94:28:de:f5:52:2c:03:3e:23:1e:fd:c4:25:
                    e7:00:8e:39:2d:5a:2a:d8:7a:6a:39:b9:dd:e5:c6:
                    35:f6:2a:bf:3f:49:43:47:9a:18:d9:14:68:33:98:
                    b5:4f:8e:50:89:b7:fd:14:14:5d:a1:cc:66:09:62:
                    04:6e:57:86:2e:a8:81:64:61:34:65:ad:58:98:2b:
                    a8:87
                Exponent: 65537 (0x10001)
        X509v3 extensions:
            X509v3 Key Usage: critical
                Digital Signature, Key Encipherment
            X509v3 Extended Key Usage: 
                TLS Web Server Authentication, TLS Web Client Authentication
            X509v3 Basic Constraints: critical
                CA:FALSE
            X509v3 Subject Key Identifier: 
                57:87:BF:61:35:65:92:0D:81:51:2D:CF:33:73:49:12:94:F4:20:72
            X509v3 Authority Key Identifier: 
                keyid:A8:4A:6A:63:04:7D:DD:BA:E6:D1:39:B7:A6:45:65:EF:F3:A8:EC:A1

            Authority Information Access: 
                OCSP - URI:http://ocsp.int-x3.letsencrypt.org
                CA Issuers - URI:http://cert.int-x3.letsencrypt.org/

            X509v3 Subject Alternative Name: 
                DNS:example.com, DNS:www.example.com
            X509v3 Certificate Policies: 
                Policy: 2.23.140.1.2.1
                Policy: 1.3.6.1.4.1.44947.1.1.1
                  CPS: http://cps.letsencrypt.org
                  User Notice:
                    Explicit Text: This Certificate may only be relied upon by Relying Parties and only in accordance with the Certificate Policy found at https://letsencrypt.org/repository/

    Signature Algorithm: sha256WithRSAEncryption
         30:5b:4d:fb:db:de:d8:92:5e:53:14:ec:9b:f9:e6:b9:6f:e5:
         4d:02:70:62:88:c7:d5:fa:ae:1e:8d:eb:d0:ed:a5:d7:ae:85:
         d9:af:02:6a:80:34:ae:f1:12:a1:3d:c7:0e:42:29:3e:13:cc:
         38:d5:39:87:1b:a2:a6:d4:18:41:d7:c8:bc:ff:76:10:00:6c:
         f6:9e:19:be:1a:f5:de:37:82:85:d2:c2:7f:7c:bf:df:1d:17:
         37:f9:0d:20:ba:63:cb:d0:4f:08:76:b0:ec:2c:5c:1f:c3:5f:
         af:cf:3b:e7:a0:ce:4c:b3:de:89:ec:e0:83:7c:bb:e1:b6:48:
         3d:a7:8f:24:93:1e:27:72:08:fa:b9:66:70:f2:1f:64:7a:14:
         39:ac:22:62:b6:25:0c:be:03:f8:c4:6d:b8:ff:c2:6d:de:04:
         81:02:b4:a9:97:3f:c9:bc:95:f2:b6:a0:2e:d2:fc:96:e4:4a:
         1e:41:3e:64:11:03:a3:c2:83:6f:34:a1:9e:0a:20:6c:06:13:
         6c:df:2c:8d:e0:14:20:3f:25:f4:22:18:c6:45:72:6f:88:b7:
         85:eb:76:66:29:be:7e:68:d3:66:b5:da:c6:8d:88:1f:c9:4c:
         db:42:6e:e5:64:d9:c4:72:c5:97:48:a7:3e:52:33:e5:ad:c6:
         7e:9f:a2:0b

You might also try this:

$ openssl s_client -connect example.com:443 -servername example.com