Renewing LetsEncrypt Certificate When Using Cloudflare

I recently suffered a little down time on my site due to my letsencrypt certificate expiring and having some difficulty renewing because I'm using CloudFlare's Content Delivery Network in front of my site.

CloudFlare Main Page

I'm posting so that those of you who have similar issues won't have to Google as much as I did to find an answer that works.

I'm running the Ghost server that hosts this blog on an Ubuntu/nginx server running on DigitalOcean.

I was getting an error similar to the following when I tried to browse to my site this morning:

LetsEncrypt Certificate Error on CloudFlare

My CloudFlare certificate was fine, but the LetsEncrypt certificate for my server had expired, so CloudFlare threw a warning.

I let my certificate renewal slip because, when I tried to renew using LetsEncrypt's certbot, I was getting errors related to my use of CloudFlare and I didn't take the time to troubleshoot it when I should have.

EFF CertBot

Here's how it's supposed to work with CloudFlare and TLS:

  1. The user browses to infosec.theos-blog.com.
  2. The user is directed to CloudFlare.
  3. CloudFlare provides its certificate to the user.
  4. CloudFlare then creates its own secure connection to my server using my server's certificate.

Step 4 was broken as my certificate was expired.

LetsEncrypt certificates are free, and normally easy to renew, but they expire every 90 days. You can set them to auto-renew, so this shouldn't be an issue.

The way LetsEncrypt normally verifies that you own the server you're requesting the certificate for is through checking that your servers IP Address is the one that DNS points to.

Server IP, DNS IP Mismatch

That being the case, when using LetsEncrypt's default renewal method, with my server behind CloudFlare, verification fails.

Which names would you like to activate HTTPS for?
-------------------------------------------------------------------------------
1: infosec.theos-blog.com
-------------------------------------------------------------------------------
Select the appropriate numbers separated by commas and/or spaces, or leave input blank to select all options shown (Enter 'c' to cancel): 1 Cert is due for renewal, auto-renewing...
Renewing an existing certificate
Performing the following challenges:
tls-sni-01 challenge for infosec.theos-blog.com
Waiting for verification...
Cleaning up challenges
Failed authorization procedure. infosec.theos-blog.com (tls-sni-01): urn:acme:error:tls :: The server experienced a TLS error during domain verification :: remote error: tls: handshake failure

IMPORTANT NOTES:
 - The following errors were reported by the server:

   Domain: infosec.theos-blog.com
   Type:   tls
   Detail: remote error: tls: handshake failure

   To fix these errors, please make sure that your domain name was entered correctly and the DNS A/AAAA record(s) for that domain contain(s) the right IP address. Additionally, please check that you have an up-to-date TLS configuration that allows the server to communicate with the Certbot client.

The error occurs because the TLS handshake is happening at CloudFlare's IP, not my server's IP.

There's a LetsEncrypt plugin called certbot_dns_cloudflare, but I didn't have luck with it, or put the time into figuring it out.

I found another way. I used the following option:

--preferred-challenges="dns"

The entire command looked like this:

certbot certonly --manual -d theos-blog.com -d infosec.theos-blog.com --preferred-challenges="dns"

When you run that command, you'll get output similar to the following:

Please deploy a DNS TXT record under the name
_acme-challenge.theos-blog.com with the following value:

<random looking string 1>

Once this is deployed,
-------------------------------------------------------------------------------
Press Enter to Continue

-------------------------------------------------------------------------------
Please deploy a DNS TXT record under the name
_acme-challenge.infosec.theos-blog.com with the following value:

<random looking string 2>

Once this is deployed,
-------------------------------------------------------------------------------
Press Enter to Continue

Navigate to your CloudFlare admin page and select DNS from the top menu.

CloudFlare DNS

In the dropdown under DNS Records, choose TXT. Enter the name provided under Name, and the random looking string under Value, then click on Add Record. It should look similar to the following:

Acme-challenge DNS text entry

Back on your server, hit Enter to allow the process to continue.

If all goes well, you'll get output like below:

Waiting for verification...
Cleaning up challenges

IMPORTANT NOTES:
 - Congratulations! Your certificate and chain have been saved at
   /etc/letsencrypt/live/infosec.theos-blog.com/fullchain.pem. Your cert will expire on 2017-11-08. To obtain a new or tweaked version of this certificate in the future, simply run certbot again. To non-interactively renew *all* of your certificates, run "certbot renew"
 - 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

Now, restart your server, nginx in my case:

service nginx restart

Refresh your page in your browser, and you're up and running!

The reason this works is, instead of just checking where DNS is pointing for verification, certbot checks for the strings you added to DNS to verify that you own your domain. When it finds the strings, verification passes, and your cert is renewed.

If you know of a different, hopefully easier way to resolve this issue, please post a comment below.

comments powered by Disqus