Skip to content

Instantly share code, notes, and snippets.

@cgmartin
Created January 17, 2016 18:00
Show Gist options
  • Save cgmartin/49cd0aefe836932cdc96 to your computer and use it in GitHub Desktop.
Save cgmartin/49cd0aefe836932cdc96 to your computer and use it in GitHub Desktop.
Bash SSL Certificate Expiration Check
#!/bin/bash
TARGET="mysite.example.net";
RECIPIENT="[email protected]";
DAYS=7;
echo "checking if $TARGET expires in less than $DAYS days";
expirationdate=$(date -d "$(: | openssl s_client -connect $TARGET:443 -servername $TARGET 2>/dev/null \
| openssl x509 -text \
| grep 'Not After' \
|awk '{print $4,$5,$7}')" '+%s');
in7days=$(($(date +%s) + (86400*$DAYS)));
if [ $in7days -gt $expirationdate ]; then
echo "KO - Certificate for $TARGET expires in less than $DAYS days, on $(date -d @$expirationdate '+%Y-%m-%d')" \
| mail -s "Certificate expiration warning for $TARGET" $RECIPIENT ;
else
echo "OK - Certificate expires on $expirationdate";
fi;
@opthakur
Copy link

opthakur commented Jul 15, 2020

How to add multiple targets ?
@cgmartin

@Clanwarz-zz
Copy link

@opthakur

Place all your domains in a file. Run the script in a loop, giving the loop a domain each time it runs. Maybe something like this:

#!/bin/bash

DOMAINS="/path/to/list/of/domains/list.txt"
RECIPIENT="[email protected]"
DAYS="7"

while read -r TARGET; do
  echo "checking if $TARGET expires in less than $DAYS days";
  expirationdate=$(date -d "$(: | openssl s_client -connect "$TARGET":443 -servername "$TARGET" 2>/dev/null \
                                | openssl x509 -text \
                                | grep 'Not After' \
                                |awk '{print $4,$5,$7}')" '+%s');
  in7days=$(($(date +%s) + (86400*DAYS)));
  if [ "$in7days" -gt "$expirationdate" ]; then
      echo "KO - Certificate for $TARGET expires in less than $DAYS days, on $(date -d @"$expirationdate" '+%Y-%m-%d')" \
      | mail -s "Certificate expiration warning for $TARGET" $RECIPIENT ;
  else
      echo "OK - Certificate expires on $expirationdate";
  fi;
done<"${DOMAINS}"

Define your list of domains on line 3. I added some double quotes to his original script.

Cheers

@cliftonwwyeager
Copy link

How would i scan ports 443, 465, and 993, with human readable time format instead of epoch?

@vishnuv1995
Copy link

Hi,

I have getting an issue in the script once we pass the dummy url also the mail is sending with alert, how can we include that condition also in that script .

Eg:
unable to load certificate
140359043876160:error:0909006C:PEM routines:get_name:no start line:../crypto/pem/pem_lib.c:745:Expecting: TRUSTED CERTIFICATE

Once we got the above its should go to else part no need to send mail.

@dahse89
Copy link

dahse89 commented Oct 22, 2021

i recommend to also format the date for the success case

Instead of
echo "OK - Certificate expires on $expirationdate"; # OK - Certificate expires on 1658872800

do this
echo "OK - Certificate expires on $(date -d @$expirationdate '+%Y-%m-%d')"; # OK - Certificate expires on 2022-07-27

@vadirajks
Copy link

vadirajks commented Jun 1, 2022

to fix some glitch's in Ubuntu: expirationdate=$(date -d "$(echo -n | openssl s_client -servername "$TARGET" -connect "$TARGET":"443" 2>&- | openssl x509 -enddate -noout | awk -F= '{print $2}')" +%s)

@Aabhusan
Copy link

i am using this script

#!/bin/bash
TARGET="mysite.example.net";
RECIPIENT="[email protected]";
DAYS=7;
echo "checking if $TARGET expires in less than $DAYS days";
expirationdate=$(date -d "$(: | openssl s_client -connect $TARGET:443 -servername $TARGET 2>/dev/null
| openssl x509 -text
| grep 'Not After'
|awk '{print $4,$5,$7}')" '+%s');
in7days=$(($(date +%s) + (86400*$DAYS)));
if [ $in7days -gt $expirationdate ]; then
echo "KO - Certificate for $TARGET expires in less than $DAYS days, on $(date -d @$expirationdate '+%Y-%m-%d')"
| mail -s "Certificate expiration warning for $TARGET" $RECIPIENT ;
else
echo "OK - Certificate expires on $expirationdate";
fi;

i am getting this error:

checking if expires in less than 7 days
date: invalid date 'Jul 4 2023'
./checkssl2.sh: line 11: [: 1658487210: unary operator expected
OK - Certificate expires on

getting invalid date ?
can anyone help me @vadirajks @dahse89 @cgmartin

@vadirajks
Copy link

@Aabhusan : checkout below

#!/bin/bash
TARGET="example.net";
RECIPIENT="[email protected]";
DAYS=7;
echo "checking if $TARGET expires in less than $DAYS days";
expirationdate=$(date -d "$(echo -n | openssl s_client -servername "$TARGET" -connect "$TARGET":"443" 2>&- | openssl x509 -enddate -noout | awk -F= '{print $2}')" +%s)
in7days=$(($(date +%s) + (86400*$DAYS)));
if [ $in7days -gt $expirationdate ]; then
echo "KO - Certificate for (date -d @$expirationdate '+%Y-%m-%d')" | mailx -s "Certificate expiration warning for $TARGET" $RECIPIENT ;
else
echo "OK - Certificate expires on $expirationdate";
fi;

@SrikumarM
Copy link

SrikumarM commented Aug 25, 2022

@opthakur

Place all your domains in a file. Run the script in a loop, giving the loop a domain each time it runs. Maybe something like this:

#!/bin/bash

DOMAINS="/path/to/list/of/domains/list.txt"
RECIPIENT="[email protected]"
DAYS="7"

while read -r TARGET; do
  echo "checking if $TARGET expires in less than $DAYS days";
  expirationdate=$(date -d "$(: | openssl s_client -connect "$TARGET":443 -servername "$TARGET" 2>/dev/null \
                                | openssl x509 -text \
                                | grep 'Not After' \
                                |awk '{print $4,$5,$7}')" '+%s');
  in7days=$(($(date +%s) + (86400*DAYS)));
  if [ "$in7days" -gt "$expirationdate" ]; then
      echo "KO - Certificate for $TARGET expires in less than $DAYS days, on $(date -d @"$expirationdate" '+%Y-%m-%d')" \
      | mail -s "Certificate expiration warning for $TARGET" $RECIPIENT ;
  else
      echo "OK - Certificate expires on $expirationdate";
  fi;
done<"${DOMAINS}"

Define your list of domains on line 3. I added some double quotes to his original script.

Cheers

Hi,

I'm a new user of Linux can you please explain this "$(: ". the exact usage of this command,

without this line, "while read" exited after the first-line executed

thanks

@saudiqbal
Copy link

This is how I got it working

#!/bin/bash

file=$(cat /path/to/list/of/domains/list.txt)
RECIPIENT="[email protected]"
DAYS="3"

for line in $file; do
  echo "checking if $line expires in less than $DAYS days";
  expirationdate=$(date -d "$(: | openssl s_client -connect "$line":443 -servername "$line" 2>/dev/null \
                                | openssl x509 -text \
                                | grep 'Not After' \
                                |awk '{print $4,$5,$7}')" '+%s');
  indays=$(($(date +%s) + (86400*DAYS)));
  if [ "$indays" -gt "$expirationdate" ]; then
  echo "expiring soon"
      echo "KO - Certificate for $line expires in less than $DAYS days, on $(date -d @"$expirationdate" '+%Y-%m-%d')" \
      | mail -s "Certificate expiration warning for $line" $RECIPIENT ;
  else
      echo "OK - Certificate expires on $(date -d @"$expirationdate" '+%Y-%m-%d')";
  fi;
done

exit 0

@markg85
Copy link

markg85 commented Mar 17, 2025

Amazing script! Just what i needed as base to build on :)
I changed it quite a bit to be an alternative to LetsEncrypt expiry mails. Here's my version:

#!/bin/bash

# Configuration
DOMAIN_LIST_FILE="/home/mark/domain_ssl_ttl_check.txt"
EXPIRY_DAYS=(14 10 7 5 3 2 1)
NOTIFICATION_SCRIPT="/home/mark/notify.sh"

# Function to get certificate expiry timestamp
get_expiry_timestamp() {
  local domain="$1"
  date -d "$(: | openssl s_client -connect "$domain":443 -servername "$domain" 2>/dev/null \
                                  | openssl x509 -text \
                                  | grep 'Not After' \
                                  | awk '{print $4,$5,$7}')" '+%s' 2>/dev/null
}

# Function to calculate days until expiry
days_until_expiry() {
  local expiry_timestamp="$1"
  local current_timestamp=$(date +%s)
  local seconds_diff=$((expiry_timestamp - current_timestamp))
  echo $((seconds_diff / 86400))
}

# Main loop
while IFS= read -r base_domain; do
  echo "Checking certificate expiry for: $base_domain"

  expiry_timestamp=$(get_expiry_timestamp "$base_domain")
  if [ -z "$expiry_timestamp" ]; then
    echo "Error: Could not retrieve certificate information for $base_domain. Skipping."
    continue
  fi

  days_left=$(days_until_expiry "$expiry_timestamp")
  expiry_date=$(date -d @"$expiry_timestamp" '+%d-%m-%Y')
  echo "Certificate for $base_domain expires in $days_left days, on $expiry_date."

  triggered=false # Flag to track if the notification script was triggered

  for trigger_days in "${EXPIRY_DAYS[@]}"; do
    if [ "$days_left" -eq "$trigger_days" ] && [ -x "$NOTIFICATION_SCRIPT" ]; then
      title="Certificate Expiry Warning"
      content="The SSL certificate for '$base_domain' and '*.${base_domain}' will expire in $days_left days, on $expiry_date."
      echo "Triggering notification script for $base_domain - $days_left days remaining"
      "$NOTIFICATION_SCRIPT" "$title" "$content"
      triggered=true
      break
    elif [ "$days_left" -eq "$trigger_days" ] && [ ! -x "$NOTIFICATION_SCRIPT" ]; then
      echo "Warning: Notification script '$NOTIFICATION_SCRIPT' is not executable."
      triggered=true
      break
    fi
  done

  if [ "$triggered" = false ]; then
    echo "No immediate notification for $base_domain. Expires on $expiry_date."
  fi

done < "$DOMAIN_LIST_FILE"

exit 0

Yes, i used AI to make this better :)

A couple things to note:

  • First of, day-month-year. (it used to be year-month-day, yuck)
  • DOMAIN_LIST_FILE is a one line per domain file
  • in my case i'm exclusively using wildcard domains so the message i get says foo.bar and *.foo.bar, this might not be applicable to others.
  • EXPIRY_DAYS, a list of specific expiry days to handle. This is for the notification (or mail in the old version). I didn't want to maintain a temp file so i solved it by just having a fixed list of days where you can receive a notification.
  • NOTIFICATION_SCRIPT, the first argument is the title, second is the expiry message. In my case i'm using home assistant and thus have the "luxury" of using it's notification API to send me an actual notification.

The script can probably be simplified a little but this works really nice!
Run this as a daily cronjob and you're all set for domain expiry notifications.

Here is the notify.sh script, change where needed.

#! /bin/bash
curl -X POST \
-H "Authorization: Bearer [TOKEN]"
-H "Content-Type: application/json" \
-d "{ \
    \"title\": \"$1\", \
    \"message\": \"$2\" \
    }" \
[HA]/api/services/notify/[DEVICE]

I had to change things on the links i found so i guess those resources have changed compared to how home assistant works now. Therefore not posting links :)

@saudiqbal
Copy link

  • First of, day-month-year. (it used to be year-month-day, yuck)

You said year-month-day, yuck, it has to be the most dumbest thing thing in this script. Year-month-day is the standard in programming.

@markg85
Copy link

markg85 commented Mar 18, 2025

  • First of, day-month-year. (it used to be year-month-day, yuck)

You said year-month-day, yuck, it has to be the most dumbest thing thing in this script. Year-month-day is the standard in programming.

Lol ๐Ÿ˜† then you change it to what you like.
There's a couple reasons why i made that change.

  1. We - western people - read from left to right. The most important part of the date in this context is the day and potentially the month. Having that readable from left to right makes a lot of sense (to me!).
  2. Same geographic argument, different reason. dd-mm-yyyy is the standard here, i'm used to that and can't stand the many differences.

As an aside, if year-month-day is a standard (didn't know that and I've been a programmer for decades by now), then i'll happily ignore that. I'll quote you for my reason: "it has to be the most dumbest thing" ๐Ÿ˜‰

@zeilsteen2
Copy link

Working great, thanks very much for the script!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment