#!/usr/bin/env python3 import argparse import subprocess import sys from datetime import datetime, timezone STATE_OK = 0 STATE_WARNING = 1 STATE_CRITICAL = 2 STATE_UNKNOWN = 3 def run_cmd(cmd, input_data=None, timeout=15): try: result = subprocess.run( cmd, input=input_data, text=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, timeout=timeout, check=False, ) return result.returncode, result.stdout, result.stderr except subprocess.TimeoutExpired: print("UNKNOWN - Timeout while retrieving certificate") sys.exit(STATE_UNKNOWN) except Exception as exc: print(f"UNKNOWN - Failed to run command: {exc}") sys.exit(STATE_UNKNOWN) def get_certificate_pem(host, port, servername, timeout): sni = servername if servername else host cmd = [ "openssl", "s_client", "-connect", f"{host}:{port}", "-servername", sni, "-showcerts", "-verify", "0", ] rc, stdout, stderr = run_cmd(cmd, input_data="", timeout=timeout) begin = stdout.find("-----BEGIN CERTIFICATE-----") end = stdout.find("-----END CERTIFICATE-----") if begin == -1 or end == -1: print(f"UNKNOWN - Could not retrieve peer certificate from {host}:{port}") sys.exit(STATE_UNKNOWN) end += len("-----END CERTIFICATE-----") return stdout[begin:end] def parse_certificate_dates(cert_pem, timeout): cmd = [ "openssl", "x509", "-noout", "-dates", ] rc, stdout, stderr = run_cmd(cmd, input_data=cert_pem, timeout=timeout) if rc != 0: print(f"UNKNOWN - Could not parse certificate dates: {stderr.strip()}") sys.exit(STATE_UNKNOWN) not_before = None not_after = None for line in stdout.splitlines(): if line.startswith("notBefore="): not_before = line.split("=", 1)[1].strip() elif line.startswith("notAfter="): not_after = line.split("=", 1)[1].strip() if not_after is None: print("UNKNOWN - Certificate does not contain notAfter field") sys.exit(STATE_UNKNOWN) # Example OpenSSL format: Jul 21 10:00:00 2026 GMT try: expires = datetime.strptime(not_after, "%b %d %H:%M:%S %Y %Z") expires = expires.replace(tzinfo=timezone.utc) except ValueError: print(f"UNKNOWN - Unsupported certificate date format: {not_after}") sys.exit(STATE_UNKNOWN) return not_before, not_after, expires def main(): parser = argparse.ArgumentParser( description="Check SSL/TLS certificate validity period only, without RootCA or hostname validation." ) parser.add_argument("-H", "--host", required=True, help="Target host or IP") parser.add_argument("-p", "--port", type=int, default=443, help="Target port, default: 443") parser.add_argument("--sni", help="SNI server name. Defaults to host value.") parser.add_argument("-w", "--warning", type=int, default=30, help="Warning threshold in days") parser.add_argument("-c", "--critical", type=int, default=14, help="Critical threshold in days") parser.add_argument("-t", "--timeout", type=int, default=15, help="Timeout in seconds") args = parser.parse_args() if args.critical >= args.warning: print("UNKNOWN - Critical threshold must be lower than warning threshold") sys.exit(STATE_UNKNOWN) cert_pem = get_certificate_pem(args.host, args.port, args.sni, args.timeout) not_before, not_after, expires = parse_certificate_dates(cert_pem, args.timeout) now = datetime.now(timezone.utc) seconds_left = int((expires - now).total_seconds()) days_left = seconds_left // 86400 perfdata = f"| days_left={days_left};{args.warning};{args.critical};0;" target = f"{args.host}:{args.port}" if args.sni: target += f" sni={args.sni}" if seconds_left < 0: print( f"CRITICAL - Certificate for {target} expired {-days_left} days ago, " f"notAfter={not_after} {perfdata}" ) sys.exit(STATE_CRITICAL) if days_left <= args.critical: print( f"CRITICAL - Certificate for {target} expires in {days_left} days, " f"notAfter={not_after} {perfdata}" ) sys.exit(STATE_CRITICAL) if days_left <= args.warning: print( f"WARNING - Certificate for {target} expires in {days_left} days, " f"notAfter={not_after} {perfdata}" ) sys.exit(STATE_WARNING) print( f"OK - Certificate for {target} valid for {days_left} days, " f"notAfter={not_after} {perfdata}" ) sys.exit(STATE_OK) if __name__ == "__main__": main()