Free SSL certificates without a web server

Contents

There’s lots of instances where you need a certificate for a non-web server system. Popular examples of this include database servers, git-servers, docker-repos, etc. However, free providers like Let’s Encrypt usually validate your server by means of an HTTP lookup for a specific file and that means you need a way to serve that file but, we aren’t running a web server. Catch-22? Not necessarily…

To get around this, we have two options, both them using an awesome program called certbot. Our first option is to let certbot spin up a temporary webserver and the second is via DNS validation. I’m only covering the first option in this post.

Can certbot handle this for me?

As I mentioned, you have two options when you’re not running a web server but still need a certificate. The first is letting certbot spin-up a temporary web server on your system, respond to Let’s Encrypt queries, then destroy the server. This option is super simple and is a built-in functionality of the basic certbot package. It does have 2 caveats, however:

If you can meet both of these requirements, then we’re good to go. If you cannot meet these requirements, then DNS validation is the way to go. With DNS validation, you need to add a special TXT record in your DNS and Let’s Encrypt will validate against that. With any luck, certbot has a plugin to do that for you! You can find a list of supported providers here along with specific instructions. If you use Cloudflare like I do, I have a post about that!

Before continuing, ensure you have a proper DNS A and/or AAAA record pointing to the system you want to get a certificate for. Just because certbot will be spinning up a web server on your system doesn’t mean that it can create a DNS record for you too! Let’s Encrypt has to be able to lookup the domain name you are requesting a certificate for and follow it back to your system in order for verification to work!

Installing certbot

If you’re running something other than Debian 9 (“Stretch”) then I suggest going to certbot’s instruction page to find out exactly how to install certbot. Make sure you select “My HTTP website is running none of the above on…” then select your OS. If you’re running on Debian, then keep reading.

I'm running Debian 9 (Stretch)

The version of certbot in the default Debian repo is very old, so we need to add the backports repo to our apt so we can get the latest and greatest certbot. Let’s start by adding a new apt source list file:

cd /etc/apt/sources.list.d
touch stretch-backports.list

Now, open that stretch-backports.list file in your favourite text editor and add the following line:

deb http://deb.debian.org/debian stretch-backports main

Save that file and now update your apt cache and install certbot using our new repo definition:

apt update
apt install certbot -t stretch-backports

I'm running Debian 10 (Buster)

The latest certbot is already in the default Buster repo, so things are very easy.

apt install certbot

Get your certificate

We will be using certbot to spin-up a temporary web server in order to validate against Let’s Encrypt. So, before we continue, make sure port 80 is open on any firewalls on your system and upstream. At this point, you have to choose between a few command variants depending whether or not you have something else running on port 80, need to restart an application post-renewal (to read new certificates, for example) or just need a no-frills simple renewal. Choose-your-own-adventure style – expand the relevant section:

I have something running on port 80

Fortuantely, certbot can handle stopping and restarting whatever is running on port 80 so it can do it’s certificate renewals. You just need to provide a command to start/stop whatever is running or be able to point certbot to a shell script that can perform the start/stop. We handle calling that command/script via pre- and post hooks. For the sake of this example, I’m assuming you have a service called ‘myapp’ that’s controlled via systemd. It could just as easily be a call to a shell script. Here’s the full command and what it all means:

certbot certonly --standalone --rsa-key-size 4096 -d domain.tld,sub.domain.tld --pre-hook "systemctl stop myapp.service" --post-hook "systemctl start myapp.service" --agree-tos -m [email protected] --no-eff-email

ParameterMeaning
certonlyTell certbot to obtain a certificate and save it, but don’t try installing it anywhere. We want to do that manually.
standaloneSpin-up a webserver using port 80 to get our certificate then destory said server.
rsa-key-sizeNumber of bits for our RSA key. 2048 is the default, but modern computer power makes 4096 more reasonable.
dComma separated list of domains for which to request a certificate (i.e. Subject-Alternate-Names or SAN). Since this is probably for a standalone server providing a service to your domain, you’ll probably only be specifying one name like database.mydomain.net.
pre-hookCommand/script to run before starting renewal process. This should be the command to stop anything running on port 80. In this example, I’ve used systemd. You could also have a hook like ”/bin/sh /usr/local/bin/stop.sh”.
post-hookCommand/script to run after renewal process. This should be the command to restart whatever was running on port 80. In this example, I’ve used systemd. You could also have a hook like ”/bin/sh /usr/local/bin/start.sh”.
agree-tosAgree to the ACME/Let’s Encrypt terms of service without a prompt.
mEmail address for notifications from Let’s Encrypt, things like expiration warnings, etc.
no-eff-emailOpt-out of the Electronic Frontier Foundation mailing list… it’s a good newsletter though, so this parameter is quite optional.

The list of SAN names (-d parameter) is processed in order! This means the first entry will become your certificate’s common name. If you are only specifying one domain name, then you don’t have much to worry about.

I need to restart an application/service after renewal

Certbot can run a particular command or script after renewing your certificates. This may be necessary, for example, if you’re using a program that needs to be reloaded in order to recognize the renewed certificate. This is different from the previous case where something needs to be stopped before renewal because it’s using port 80. In this case, we are just restarting something after renewal has happened.

certbot certonly --standalone --rsa-key-size 4096 -d domain.tld,sub.domain.tld --deploy-hook "systemctl restart myapp.service" --agree-tos -m [email protected] --no-eff-email

ParameterMeaning
certonlyTell certbot to obtain a certificate and save it, but don’t try installing it anywhere. We want to do that manually.
standaloneSpin-up a webserver using port 80 to get our certificate then destory said server.
rsa-key-sizeNumber of bits for our RSA key. 2048 is the default, but modern computer power makes 4096 more reasonable.
dComma separated list of domains for which to request a certificate (i.e. Subject-Alternate-Names or SAN). Since this is probably for a standalone server providing a service to your domain, you’ll probably only be specifying one name like database.mydomain.net.
deploy-hookCommand/script to run after renewal process. This should be the command to restart whatever program/service that needs to read the newly obtained certificates. In this example, I’ve used systemd. You could also have a hook like ”/bin/sh /usr/local/bin/restart.sh”.
agree-tosAgree to the ACME/Let’s Encrypt terms of service without a prompt.
mEmail address for notifications from Let’s Encrypt, things like expiration warnings, etc.
no-eff-emailOpt-out of the Electronic Frontier Foundation mailing list… it’s a good newsletter though, so this parameter is quite optional.

The list of SAN names (-d parameter) is processed in order! This means the first entry will become your certificate’s common name. If you are only specifying one domain name, then you don’t have much to worry about.

I just need a simple renewal

If you do not need to stop anything running on port 80 or reload any services/programs after the new certificate is obtained, your certbot command is as follows:

certbot certonly --standalone --rsa-key-size 4096 -d domain.tld,sub.domain.tld --agree-tos -m [email protected] --no-eff-email

What does this all mean?

ParameterMeaning
certonlyTell certbot to obtain a certificate and save it, but don’t try installing it anywhere. We want to do that manually.
standaloneSpin-up a webserver using port 80 to get our certificate then destory said server.
rsa-key-sizeNumber of bits for our RSA key. 2048 is the default, but modern computer power makes 4096 more reasonable.
dComma separated list of domains for which to request a certificate (i.e. Subject-Alternate-Names or SAN). Since this is probably for a standalone server providing a service to your domain, you’ll probably only be specifying one name like database.mydomain.net
agree-tosAgree to the ACME/Let’s Encrypt terms of service without a prompt.
mEmail address for notifications from Let’s Encrypt, things like expiration warnings, etc.
no-eff-emailOpt-out of the Electronic Frontier Foundation mailing list… it’s a good newsletter though, so this parameter is quite optional.

The list of SAN names (-d parameter) is processed in order! This means the first entry will become your certificate’s common name. If you are only specifying one domain name, then you don’t have much to worry about.

Where’s my certificate?

Your certificate and key are stored in /etc/letsencrypt/archive/your.domain.tld/. However, that will also store all old certificates (remember that Let’s Encrypt certificates expire every 90 days). Therefore, you must always reference the symlinks at /etc/letsencrypt/live/your.domain.tld in order to refer to the correct files. They are named as follows:

FileWhat is it?
cert.pemYour certificate in PEM format.
chain.pemThe certificate chain, in PEM format, for your certificate. In other words, the intermediate and root CA certificates.
fullchain.pemYour certificate and the entire certification chain, in PEM format. This is a concatenated version of cert.pem and chain.pem that is most likely what you will be using for whatever application you are securing.
privkey.pemYour private key, in PEM format.

Test auto-renewal

Certbot will auto-renew your certificates once they have 30 days or less remaining before expiry. But, we should probably make sure that can happen without any problems, right? The first part of our test involves checking the renewal parameters file and we’ll be looking for different things depending on what command options you chose when obtaining the certificate. So, same three choice as before – expand the relevant section:

I have something running on port 80

Let’s cat our renewal file so we can inspect it:

cat /etc/letsencrypt/renewal/your.domain.tld.conf

Now, check the [renewalparams] section and make sure it looks something like this:

[renewalparams]
account=d7j398dlke93kdhf93kd93dl903jf30d
authenticator=standalone
rsa_key_size=4096
server = https://acme-...
post_hook = systemctl start myapp.service
pre_hook = systemctl stop myapp.service

Ensure the RSA key size is as you specified and that your pre- and post hooks are correct. Edit this file as needed if there are any mistakes.

I need to restart an application/service after renewal

Let’s cat our renewal file so we can inspect it:

cat /etc/letsencrypt/renewal/your.domain.tld.conf

Now, check the [renewalparams] section and make sure it looks something like this:

[renewalparams]
account=d7j398dlke93kdhf93kd93dl903jf30d
authenticator=standalone
rsa_key_size=4096
server = https://acme-...
renew_hook = systemctl restart myapp.service

Ensure the RSA key size is as you specified and that your renewal hook is correct. Edit this file as needed if there are any mistakes.

I just need a simple renewal

Let’s cat our renewal file so we can inspect it:

cat /etc/letsencrypt/renewal/your.domain.tld.conf

Now, check the [renewalparams] section and make sure it looks something like this:

[renewalparams]
account=d7j398dlke93kdhf93kd93dl903jf30d
authenticator=standalone
rsa_key_size=4096
server = https://acme-...

Ensure the RSA key size is as you specified. Edit this file as needed if there are any mistakes.

Now that we’ve confirmed our configuration, let’s test whether or not things actually execute properly:

certbot renew --dry-run

This will generate a real renewal request and re-verify your domain using the built-in webserver, but will not actually generate new certificates or count against your daily limits. It should complete successfully. If you have any pre- or post-renewal hooks, they will be executed. If you have any renewal-hooks, certbot will mention that it “skipped” them which lets you know that when this happens for real, it understands what commands it should run.

Renewal timers

The last thing we need to check is that the renewal timers are functioning correctly, otherwise certbot will not be called and your certificates will expire! By default, certbot is called twice a day (once at midnight and once at noon) to renew your certificates if necessary. This is accomplished via a cronjob or systemd timer.

cronjob

If you check out /etc/cron.d/certbot, you’ll see the following:

0 */12 * * * root test -x /usr/bin/certbot -a \\! -d /run/systemd/system && perl -e 'sleep int(rand(3600))' && certbot -q renew

This means that every 12 hours at midnight and noon, check to see if Certbot is installed and that systemd is NOT installed.  If those conditions are met, run certbot in renew mode quietly.  If your system is not using systemd, then this is how your renewals will be processed so be aware of this cronjob!

systemd

On most current Debian/Ubuntu systems, you are using systemd and thus, the cronjob above will not run.  Instead, there is a systemd service and timer installed.  Let’s verify all is good with those:

systemctl list-timers

You should see ‘certbot.timer’ listed.  Now, let’s make sure the files that define this timer and it’s accompanying service exist too:

ls -lA /lib/systemd/system/certbot*

You should see both ‘certbot.timer’ and ‘certbot.service’ listed.  Let’s check the status of these components:

systemctl status certbot.service   #should show loaded but dead
systemctl status certbot.timer   #should show loaded and active(waiting)

You should also ensure that when checking the status of the ‘certbot.timer’ the ‘Loaded:’ line lists the timer as ‘enabled’ otherwise it will not be automatically started after a reboot.  If it’s showing ‘disabled’, then run the following:

systemctl enable certbot.timer

Final thoughts

Well, that’s about it! You have free certificates stored on your system that can be used to secure whatever you need to secure and they’ll take care of renewing themselves and even restart stuff that uses them -- that’s pretty cool, right?


Thanks for reading my techie-thoughts on this issue. Have any comments or suggestions? Want to add your tips? Things you want me to cover in a future article? Comment below!