DEV Community

Cover image for Let's Encrypt Server Certificate via DNS Challenge
Michael Scherr
Michael Scherr

Posted on • Updated on

Let's Encrypt Server Certificate via DNS Challenge

Have you ever wondered if it was possible to obtain an Server Certificate before you migrate a website? I always assumed the DNS had to be switched before the certificate validation could take place. I've come to find there are more options available.

The Scenario

My company is currently in the process of moving all of our client production sites to a new server. Part of that effort is ensuring all sites be placed on https.

Server Certificate Challenges

Let's Encrypt offers domain-validated certificates, meaning they have to check that the certificate request comes from a person who actually controls the domain. They do this by sending the client a unique token, and then making a web or DNS request to retrieve a key derived from that token.

There are two main options to obtain a server certificate:

  • HTTP Challenge - Posting a specified file in a specified location on a web site
  • DNS Challenge - Posting a specified DNS record in the domain name system

HTTP Challenge

This is usually handled by adding a token inside a .well-known directory in your web root. Certbot can then confirm you actually control resources on the specified domain, and will sign a certificate.

DNS Challenge

This approach requires you to add specific DNS TXT entry for each domain requested. This is useful when you haven't switched DNS yet, but want to issue a certificate in anticipation (for testing).

For more information on challenges, visit certbot's documentation.

Setup

We'll be discussing the DNS Challenge approach for the rest of the article.

In the examples below, I'll be using Apache & Ubuntu 16.04 following this guide. To find documentation for your specific web server / operating system, go to certbot's homepage.

First we need to install certbot along with all necessary dependencies.

# run as root

apt-get update \
    && apt-get install software-properties-common \
    && add-apt-repository -y universe \
    && add-apt-repository -y ppa:certbot/certbot \
    && apt-get update \
    && apt-get install -y python-certbot-apache

Configuration

I won't be going over wildcard domains here, but they are an option. Refer to their documentation.

We now need to tell certbot which domains we would like to issue a certificate for. Remember to add each subdomain individually.

Since we may have multiple vhosts per server, we decided to use the --manual & certonly flags.

# run as root

# replace with your domain
# add all relevant subdomains
certbot --manual --preferred-challenges dns certonly \
    -d yourwebsite.com \
    -d www.yourwebsite.com \          # don't forget www binding
    -d staging.yourwebsite.com \      # example subdomain
    -d staging.stage1.yourwebsite.com # example long subdomain

Challenge Prompts

Once you run the command above, it will prompt you to add a DNS TXT record for each specified domain. It will look like this:

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

[random-string-of-characters]

Before continuing, verify the record is deployed.
(This must be set up in addition to the previous challenges; do not remove,
replace, or undo the previous challenge tasks yet. Note that you might be
asked to create multiple distinct TXT records with the same name. This is
permitted by DNS standards.)

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Press Enter to Continue

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

Now here's the important part. You need to remove the base url from each record name, like so:

requested name from certbot actual name to add in DNS
_acme-challenge.yourwebsite.com _acme-challenge
_acme-challenge.www.yourwebsite.com _acme-challenge.www
_acme-challenge.staging.yourwebsite.com _acme-challenge.staging
_acme-challenge.staging.stage1.yourwebsite.com _acme-challenge.staging.stage1

Once you add the DNS TXT records, and click Continue through each challenge prompt respectively, the validation should pass.

Press Enter to Continue
Waiting for verification...
Cleaning up challenges

IMPORTANT NOTES:
 - Congratulations! Your certificate and chain have been saved at:
   /etc/letsencrypt/live/yourwebsite.com/fullchain.pem
   Your key file has been saved at:
   /etc/letsencrypt/live/yourwebsite.com/privkey.pem
   Your cert will expire on 2019-04-24. 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

Updating Your VHost

Now that we have a valid certificate, we can update our vhost:

# inside the 443 binding

SSLEngine On
SSLCertificateFile /etc/letsencrypt/live/yourwebsite.com/cert.pem
SSLCertificateKeyFile /etc/letsencrypt/live/yourwebsite.com/privkey.pem
SSLCertificateChainFile /etc/letsencrypt/live/yourwebsite.com/chain.pem

Then restart Apache:

sudo apachectl configtest

# if syntax ok
sudo apachectl restart

Bonus: Setup Auto Renewal

We'll leverage the crontab of the root user to automatically renew certificates that will expire soon.

# run as root

# edit the crontab
crontab -e

Then add the following two lines at the bottom:

# every Monday at 2:30am
30 2 * * 1 /usr/bin/certbot renew --deploy-hook "service apache2 reload" >> /var/log/letsencrypt/le-renew.log

The certbot will try and renew any certificates marked for renewal once a week. We then use the --deploy-hook to only reload apache if necessary.

Special thanks to Daniel McCarney for the updated crontab code.

cpu image

Latest comments (5)

Collapse
 
dev_uruguru profile image
Jason Boles

I assume that in your scenario, the certbot renew is running on those web hosts after they've already gone live?
Just asking because I noticed in the certbot docs that using the manual method doesn't support renew (unless you use hook scripts, via --manual-auth-hook and --manual-cleanup-hook). Other alternative is just to use the manual method again when it comes time to renew.

The scenario I'm thinking of is where the server is private but has a public DNS name, so the DNS TXT Challenge is the only option. (original cert and renewals). For automation, perhaps the certbot could run on the DNS (bind) server, and part of the cleanup/deploy hook script could push the new cert to the private server.

Collapse
 
praveenmak profile image
Praveen Mak

Excellent article.
I just have question though. I am using internal openstack cloud.
It has way to add DNS Zones and Record set.
I am not sure how to add the certbot DNS TXT record.

Any ideas?

Collapse
 
dineshrathee12 profile image
Dinesh Rathee

LetsEncrypt have revoked around 3 million certs last night due to a bug that they found. Are you impacted by this, Check out ?

DevTo
[+] dev.to/dineshrathee12/letsencrypt-...

GitHub
[+] github.com/dineshrathee12/Let-s-En...

LetsEncryptCommunity
[+] community.letsencrypt.org/t/letsen...

Collapse
 
cpu profile image
Daniel McCarney

Hi Michael,

Great post! I can suggest one small improvement if you're interested :-)

# every Monday at 2:35am
35 2 * * 1 service apache2 restart >> /var/log/letsencrypt/le-apache2-reload.log

Instead of unconditionally restarting apache2 weekly you could do two things:

  1. Using service apache2 reload instead of restart will reduce your downtime but still let Apache pick up certificates that have changed on disk.
  2. You can avoid the apache2 restart cron entry all together and use Certbot's --deploy-hook feature of the renew command.

Adding --deploy-hook "service apache2 reload" to your Certbot renew crontab will ensure Apache2 is gracefully reloaded only when a certificate is actually renewed.

Hope that helps!

Collapse
 
michaeldscherr profile image
Michael Scherr • Edited

Thanks for the suggestion, I didn't know about that flag. That definitely seems like the way to go.

I updated the post with your code example.