Checking SSL Certificates With Bash

I help monitor and maintain a whole bunch of websites and of course, have a variety of SSL certificates. Usually, I keep an eye on them periodically using calendar items and the odd visit to SSL Checker but it is a bit of a pain.

I know what you are thinking - why don’t I just use a Cron job to automatically renew them? Well, sometimes automations fail and I like to also add in a human element.

In this case, what I really wanted was (another!) automated task that would do it for me, perhaps by sending an email, weekly. This blog post will show you how you can do that too using some Linux commands and the Bash shell.

Steps Required

The main things that we need to do for this are to:

  • Iterate through a list of domains
  • Check the SSL status of each domain
  • Create a report
  • Email that report
  • Do it all again, a week later

The Script

To save you some time, here’s the whole script followed by a break-down of how it works.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#!/bin/bash
# Check SSL certificates for key domains
#
# Run: ./check-ssl-certs.sh

hosts=("logicalmoon.com")
port=443
tmp_file=`mktemp`
email_to="me@example.com"

for host in "${hosts[@]}"; do
date=`echo |
openssl s_client -connect $host:$port 2>/dev/null |
openssl x509 -dates -noout |
grep notAfter |
cut -d"=" -f2`
echo -e $date $host "\r\n" >> $tmp_file
sleep 1
done

mail -s "SSL Certs" $email_to < $tmp_file
rm $tmp_file
at 1pm + 7 days -f ~/bin/check-ssl-certs.sh

If you are using the script as-is, open up a file named: check-ssl-certs.sh and save the above into it. Don’t forget to set the execute bit on the file with: chmod +x check-ssl-certs.sh though or you won’t be able to execute it directly from the command line.

Initialisation

To begin, the first block of code handles all of our settings:

1
2
3
4
hosts=("logicalmoon.com")
port=443
tmp_file=`mktemp`
email_to="me@example.com"

hosts is a array of all the domains I want to check. If you want to add some more, separate them with a space, and some more quotes like this: hosts=("domain.com" domain2.com") etc.

port is the HTTPS port number and will most likely be 443 as I have set it.

tmp_file contains the path of a temporary file, which will hold our report. mktemp is great in that it will take care of finding an unused filename we can use, and outputs that filename onto the console. We capture the name by using back ticks into the variable tmp_file.

Lastly, email_to is a comma separated list of email addresses of people that should receive the report. Other than hosts you will also definitely want to change this.

The Main Body

1
2
3
for host in "${hosts[@]}"; do
...
done

This is the main loop of the script and its effect is to go through each domain, one by one, setting host equal to that particular domain.

"${hosts[@]}" is a way to expand the array like so:

1
2
$ echo "${hosts[@]}"
logicalmoon.com domain2.com etc.

Actually finding out the date which the SSL certificate is to expire happens next and for this, I drew heavily on this excellent post by Mohamed Ibrahim.

1
2
3
4
5
date=`echo |
openssl s_client -connect $host:$port 2>/dev/null |
openssl x509 -dates -noout |
grep notAfter |
cut -d"=" -f2`

Go read that post to get some of the finer details but my additions are that I am looking for the notAfter string and stripping out the date, which I add to my date field. This is better explained with some example output. Here’s the code:

1
2
3
$echo |
openssl s_client -connect $host:$port 2>/dev/null |
openssl x509 -dates -noout

And here’s what would happen if we used my domain: logicalmoon.com

1
2
3
$ echo | openssl s_client -connect logicalmoon.com:443 2>/dev/null | openssl x509 -dates -noout
notBefore=Mar 30 04:05:37 2021 GMT
notAfter=Jun 28 04:05:37 2021 GMT

The date that matters most to me is the notAfter one since that is my expiry date (err, actually, I’d better keep an eye on that!). Let’s take just that line, excluding the other.

1
2
$ echo | openssl s_client -connect logicalmoon.com:443 2>/dev/null | openssl x509 -dates -noout | grep notAfter
notAfter=Jun 28 04:05:37 2021 GMT

Now we want to use just the date portion, so we need to split the string at the equals sign = and grab the second field. That’s what cut does next:

1
2
$ echo | openssl s_client -connect logicalmoon.com:443 2>/dev/null | openssl x509 -dates -noout | grep notAfter | cut -d"=" -f2
Jun 28 04:05:37 2021 GMT

Lastly, we assign the output to my variable date as we have done before.

The remainder of the loop simply appends the results into a file, and sleeps for a second. My use of \r\n is because this will end up on a Windows machine and I want the line breaks as part of the output.

Final Steps

Almost done. Here’s the last of the script and my line by line explanation:

1
2
3
mail -s "SSL Certs" $email_to < $tmp_file
rm $tmp_file
at 1pm + 7 days -f ~/bin/check-ssl-certs.sh

The first line sends a mail with a subject of SSL Certs to the list of email addresses in $email_to using our temporary file as the body contents.

We then remove our temporary file - all good scouts clean up after themselves!

Finally, we reschedule this very same script to run at 1pm in a week’s time. This is a cheeky way of ensuring a script keeps executing on a schedule, periodically. Alternatively, I could have used Cron, but this is user level so simpler and requires fewer permissions.

The Results

Here’s an example output that I received via email; I’ve masked the real domains I am checking, but everything else is approximately as you would find if you ran this.

1
2
3
4
5
6
7
Aug 12 09:20:29 2021 GMT domain1.com 

Aug 12 08:38:54 2021 GMT domain2.com

Aug 9 23:31:34 2021 GMT domain3.com

Aug 9 23:31:34 2021 GMT domain4.com

Next Steps

To improve this further, you could:

  • Sort the dates - earliest to expire, first
  • Use Cron instead of the at queue
  • Reformat the dates - do the times really matter? I dont think so
  • Use an alternative language - how about Python?

Anyway, hope this helps someone should they need to do this.


Hi! Did you find this useful or interesting? I have an email list coming soon, but in the meantime, if you ready anything you fancy chatting about, I would love to hear from you. You can contact me here or at stephen ‘at’ logicalmoon.com