518

If I have the actual file and a Bash shell in Mac or Linux, how can I query the cert file for when it will expire? Not a web site, but actually the certificate file itself, assuming I have the csr, key, pem and chain files.

Ankur Loriya
  • 3,276
  • 8
  • 31
  • 58
GL2014
  • 6,016
  • 4
  • 15
  • 22

11 Answers11

957

With openssl:

openssl x509 -enddate -noout -in file.pem

The output is on the form:

notAfter=Nov  3 22:23:50 2014 GMT

Also see MikeW's answer for how to easily check whether the certificate has expired or not, or whether it will within a certain time period, without having to parse the date above.

Community
  • 1
  • 1
that other guy
  • 116,971
  • 11
  • 170
  • 194
  • 33
    You also have the `-startdate` and `-enddate` options built into the `x509` utility. They will save you the `grep`. – jww Jan 23 '14 at 09:53
  • 7
    this also works if the file is not in pem format. works fine for server.crt – look Apr 05 '19 at 18:36
225

If you just want to know whether the certificate has expired (or will do so within the next N seconds), the -checkend <seconds> option to openssl x509 will tell you:

if openssl x509 -checkend 86400 -noout -in file.pem
then
  echo "Certificate is good for another day!"
else
  echo "Certificate has expired or will do so within 24 hours!"
  echo "(or is invalid/not found)"
fi

This saves having to do date/time comparisons yourself.

openssl will return an exit code of 0 (zero) if the certificate has not expired and will not do so for the next 86400 seconds, in the example above. If the certificate will have expired or has already done so - or some other error like an invalid/nonexistent file - the return code is 1.

(Of course, it assumes the time/date is set correctly)

Be aware that older versions of openssl have a bug which means if the time specified in checkend is too large, 0 will always be returned (https://github.com/openssl/openssl/issues/6180).

Nick Robinson
  • 13
  • 1
  • 3
MikeW
  • 5,504
  • 1
  • 34
  • 29
  • 11
    To determine whether a certificate is currently expired, use a duration of zero seconds. Omit the `-noout` option to see a helpful message using a single command without extra logic. E.g., `openssl x509 -checkend 0 -in file.pem` will give the output "Certificate will expire" or "Certificate will not expire" indicating whether the certificate will expire in zero seconds. – Mr. Lance E Sloan Jan 26 '18 at 15:07
  • 1
    Providing values > 30 years (922752000) to -checkend causes the option to behave unexpectedly (returns 0 even though certificate would expire during this timeframe). – Mustermann Sep 20 '20 at 22:10
40

Here's my bash command line to list multiple certificates in order of their expiration, most recently expiring first.

for pem in /etc/ssl/certs/*.pem; do 
   printf '%s: %s\n' \
      "$(date --date="$(openssl x509 -enddate -noout -in "$pem"|cut -d= -f 2)" --iso-8601)" \
      "$pem"
done | sort

Sample output:

2015-12-16: /etc/ssl/certs/Staat_der_Nederlanden_Root_CA.pem
2016-03-22: /etc/ssl/certs/CA_Disig.pem
2016-08-14: /etc/ssl/certs/EBG_Elektronik_Sertifika_Hizmet_S.pem
Nicholas Sushkin
  • 13,050
  • 3
  • 30
  • 20
  • Very nice! This is what I was after. Now I have an overview of the certificiates that I have to renew soon. Saved it as checkcerts.sh in my home folder so I can check it regularly. Next thing would be to have a CRON job to check every month and email the certificates that need renewal. – Pete Feb 07 '17 at 12:10
  • 7
    Very usefull thanks. I use this cronjob `0 7 * * 1 /path/to/cert.sh | mail -s "certbot" my@email.com` – Matthieu Mar 04 '17 at 13:17
27

Command:

# cat {key_name} | openssl x509 -noout -enddate
Example: # cat tower.cert | openssl x509 -noout -enddate

Result:

notAfter=Dec  7 04:03:32 2023 GMT
Rahul Srivastava
  • 513
  • 1
  • 5
  • 9
16

Here's a bash function which checks all your servers, assuming you're using DNS round-robin. Note that this requires GNU date and won't work on Mac OS

function check_certs () {
  if [ -z "$1" ]
  then
    echo "domain name missing"
    exit 1
  fi
  name="$1"
  shift

  now_epoch=$( date +%s )

  dig +noall +answer $name | while read _ _ _ _ ip;
  do
    echo -n "$ip:"
    expiry_date=$( echo | openssl s_client -showcerts -servername $name -connect $ip:443 2>/dev/null | openssl x509 -inform pem -noout -enddate | cut -d "=" -f 2 )
    echo -n " $expiry_date";
    expiry_epoch=$( date -d "$expiry_date" +%s )
    expiry_days="$(( ($expiry_epoch - $now_epoch) / (3600 * 24) ))"
    echo "    $expiry_days days"
  done
}

Output example:

$ check_certs stackoverflow.com
151.101.1.69: Aug 14 12:00:00 2019 GMT    603 days
151.101.65.69: Aug 14 12:00:00 2019 GMT    603 days
151.101.129.69: Aug 14 12:00:00 2019 GMT    603 days
151.101.193.69: Aug 14 12:00:00 2019 GMT    603 days
Andrew
  • 1,027
  • 1
  • 11
  • 17
  • surprisingly osx 10.13.4 runs your shell OK ( don't judge me I am only on osx today to push an app to app store ... booting back to linux shortly ;-) – Scott Stensland May 09 '18 at 22:20
  • 1
    @ScottStensland We are judging :-P . I use Mac a lot but Linux is really much better. – Mike Q May 11 '18 at 19:22
  • 1
    Thank you very much for that code snippit! What an annoying task :), I wish there was a unixtime timestamp flag for openssl. – user1279741 Jul 10 '18 at 17:20
  • 1
    For those of you on an alpine linux container, your `expiry_date` value will need to have the timezone name removed from the end of it. Add an additional `cut` to the end of the pipe to do this: `| cut -d ' ' -f 1-4` – yurisich Feb 28 '20 at 10:13
15

Same as accepted answer, But note that it works even with .crt file and not just .pem file, just in case if you are not able to find .pem file location.

openssl x509 -enddate -noout -in e71c8ea7fa97ad6c.crt

Result:

notAfter=Mar 29 06:15:00 2020 GMT
Srihari Karanth
  • 2,067
  • 2
  • 24
  • 34
8

One line checking on true/false if cert of domain will be expired in some time later(ex. 15 days):

openssl x509 -checkend $(( 24*3600*15 )) -noout -in <(openssl s_client -showcerts -connect my.domain.com:443 </dev/null 2>/dev/null | openssl x509 -outform PEM)
if [ $? -eq 0 ]; then
  echo 'good'
else
  echo 'bad'
fi
Alexey
  • 601
  • 7
  • 17
3

Storing openssl fields into variables

As this question is tagged , I often use UNIX EPOCH to store dates, this is useful for compute time left with $EPOCHSECONDS and format output via printf '%(dateFmt)T bashism:

{ read -r certStart;read -r certEnd;}< <(date -f <(cut -d = -f 2 <(
    openssl x509 -dates -noout -in "$file")) +%s)

Then

printf '%-6s %(%a %d %b %Y, %H %Z)T\n' start $certStart end $certEnd
start  Mon 01 Nov 2004, 17 UTC
end    Mon 01 Jan 2035, 05 UTC

Sample, listing content of /etc/ssl/certs and compute days left:

for file in /etc/ssl/certs/*pem;do
    { read -r certStart;read -r certEnd;}< <(
        date -f <(cut -d = -f 2 <(
            openssl x509 -dates -noout -in "$file")) +%s)
    printf "%(%d %b %Y %T)T - %(%d %b %Y %T)T: %6d %s\n" \
        $certStart $certEnd $(( (certEnd - EPOCHSECONDS)/86400 )) ${file##*/}
done
05 May 2011 09:37:37 - 31 Dec 2030 09:37:37:   3034 ACCVRAIZ1.pem
26 Oct 2010 08:38:03 - 26 Oct 2040 08:38:03:   6620 Buypass_Class_2_Root_CA.pem
19 Jan 2010 00:00:00 - 18 Jan 2038 23:59:59:   5609 COMODO_RSA_Certification_Authority.pem
13 Nov 2012 00:00:00 - 19 Jan 2038 03:14:07:   5609 GlobalSign_ECC_Root_CA_-_R4.pem
06 Apr 2001 07:29:40 - 06 Apr 2021 07:29:40:   -522 Sonera_Class_2_Root_CA.pem
29 Jun 2004 17:39:16 - 29 Jun 2034 17:39:16:   4310 Starfield_Class_2_CA.pem
04 Feb 2016 12:32:16 - 31 Dec 2029 17:23:16:   2669 TrustCor_RootCert_CA-1.pem
01 Nov 2004 17:14:04 - 01 Jan 2035 05:37:19:   4495 XRamp_Global_CA_Root.pem
...

More complete x509 reading:

for file in /etc/ssl/certs/*pem;do
    mapfile -t x509 < <(openssl x509 -noout -dates -subject -in "$file")
    x509=("${x509[@]#*=}")
    mapfile -t dates < <(IFS=$'\n';date -f - <<<"${x509[*]::2}" +%s) 
    str="${x509[-1]}"
    declare -A Subj='([CN]="${file##*/}")'
    while [[ "$str" ]] ;do
        lhs=${str%%=*} rhs=${str#$lhs= } rhs=${rhs%% = *} rhs=${rhs%, *}
        Subj[${lhs// }]="$rhs"
        str=${str#"$lhs= $rhs"} str=${str#, }
    done
    printf "%(%d %b %Y %T)T - %(%d %b %Y %T)T: %s\n" \
        ${dates[@]} "${Subj[CN]}"
done
05 May 2011 09:37:37 - 31 Dec 2030 09:37:37:   3034 ACCVRAIZ1
26 Oct 2010 08:38:03 - 26 Oct 2040 08:38:03:   6620 Buypass Class 2 Root CA
19 Jan 2010 00:00:00 - 18 Jan 2038 23:59:59:   5609 COMODO RSA Certification Authority
13 Nov 2012 00:00:00 - 19 Jan 2038 03:14:07:   5609 GlobalSign
06 Apr 2001 07:29:40 - 06 Apr 2021 07:29:40:   -522 Sonera Class2 CA
29 Jun 2004 17:39:16 - 29 Jun 2034 17:39:16:   4310 Starfield_Class_2_CA.pem
04 Feb 2016 12:32:16 - 31 Dec 2029 17:23:16:   2669 TrustCor RootCert CA-1
01 Nov 2004 17:14:04 - 01 Jan 2035 05:37:19:   4495 XRamp Global Certification Authority
...

Note: Some certs don't have CN field in subject. For this I've initialized $Subj array by setting CN field to filename: declare -A Subj='([CN]="${file##*/}")'

Full script

Sharing here a full bash script, showing all certificates from command line arguments, which could by file, domain name or IPv4 address. Will ouput past days, days left, number of alternative domain, and all alts in one (long) line:

#!/bin/bash

showCert() {
    local x509 dates lhs rhs str alts
    mapfile -t x509 < <(
        openssl x509 -noout -dates -subject -ext subjectAltName -in "$1")
    x509=("${x509[@]#*=}")
    mapfile -t dates < <(IFS=$'\n';date -f - <<<"${x509[*]::2}" +%s)
    str="${x509[2]}"
    local -A Subj;Subj[CN]="${file##*/}"
    while [[ -n "$str" ]]; do
        lhs=${str%%=*} rhs=${str#$lhs= } rhs=${rhs%% = *} rhs=${rhs%, *}
        Subj[${lhs// }]="$rhs"
        str=${str#"$lhs= $rhs"} str=${str#, }
    done
    read -ra alts <<<"${x509[4]//,}"
    alts=("${alts[@]#*:}")
    printf "  %(%d %b %Y %H:%M)T %(%d %b %Y %H:%M)T %6d %6d %-30s %3d %s\n" \
        "${dates[@]}" $(((dates[1]-EPOCHSECONDS)/86400)) $(((EPOCHSECONDS-
          dates[0])/86400)) "${Subj[CN]}" "${#alts[@]}" "${alts[*]}" 
}
checkIsIpv4() { # throw an error if not valid IPv4
    local _iPointer _i _a _vareq=()
    for _i ;do
        case $_i in *[^0-9.]* ) return 1 ;; esac
        read -ra _a <<<"${_i//./ }"
        [ ${#_a[@]} -eq 4 ] || return 1
        for _iPointer in "${_a[@]}" ;do
            (( _iPointer == ( _iPointer & 255 ) ))  || return 2
        done
    done
}
checkIsLabel() {
    ((${#1}<4 || ${#1}>253)) && return 1
    [[ -z ${1//[a-zA-Z0-9.-]} ]] || return 2
    [[ -z ${1//.} ]] && return 3
    set -- ${1//./ }
    (($#<2 )) && return 4
    :
}
printf '  %-17s %-17s %6s %6s %-30s %2s\n' Not\ before Not\ after left \
       past Common\ Name Alt 

for arg ;do
    if [ -f "$arg" ] ;then
        showCert "$arg"
    elif checkIsLabel "$arg" || checkIsIpv4 "$arg" ;then
        showCert <(openssl s_client -ign_eof -connect "$arg:443" \
                           <<<$'HEAD / HTTP/1.0\r\n\r' 2> /dev/null)
    else
        echo "Unknown argument: '$arg'."
    fi
done

Explanation

  • function showCert create a array variable $x590 with dates, subject and alt names.
  • use mapfile and (only 1 fork to) date to convert both start and end dates.
  • create an associative array variable $Subj for parsing Subject string (3rd line: ${x509[2]})
  • split alternatives names into another array: $alts.
  • the use printf to format and print each certificats showing
    • start date,
    • end date,
    • days left,
    • days past,
    • common name,
    • number of alternative names and
    • alternative names.

Usage sample:

./certShow.sh /etc/ssl/certs/ssl-cert-snakeoil.pem www.example.com
  Not before        Not after           left   past Common Name                    Alt
  08 Sep 2021 16:49 06 Sep 2031 16:49   3277    372 hostname.local                   1 hostname.local
  14 Mar 2022 00:00 14 Mar 2023 23:59    179    186 www.example.org                  8 www.example.org example.net example.edu example.com example.org www.example.com www.example.edu www.example.net
F. Hauri - Give Up GitHub
  • 64,122
  • 17
  • 116
  • 137
2

For MAC OSX (El Capitan) This modification of Nicholas' example worked for me.

for pem in /path/to/certs/*.pem; do
    printf '%s: %s\n' \
        "$(date -jf "%b %e %H:%M:%S %Y %Z" "$(openssl x509 -enddate -noout -in "$pem"|cut -d= -f 2)" +"%Y-%m-%d")" \
    "$pem";
done | sort

Sample Output:

2014-12-19: /path/to/certs/MDM_Certificate.pem
2015-11-13: /path/to/certs/MDM_AirWatch_Certificate.pem

macOS didn't like the --date= or --iso-8601 flags on my system.

Donald.M
  • 96
  • 6
  • 1
    How would you do this if you didn't have make the .pem files, but just had `.cer` certs you just made and downloaded from the Apple Dev site? – Alex Zavatone May 16 '17 at 21:41
1

If (for some reason) you want to use a GUI application in Linux, use gcr-viewer (in most distributions it is installed by the package gcr (otherwise in package gcr-viewer))

gcr-viewer file.pem
# or
gcr-viewer file.crt
Attila123
  • 932
  • 8
  • 8
0

I have made a bash script related to the same to check if the certificate is expired or not. You can use the same if required.

Script

https://github.com/zeeshanjamal16/usefulScripts/blob/master/sslCertificateExpireCheck.sh

ReadMe

https://github.com/zeeshanjamal16/usefulScripts/blob/master/README.md

Zeeshan Jamal
  • 21
  • 1
  • 7