Expiry is one signal rather than a full health check

An expired certificate can interrupt both visitors and internal service connections. Occasional manual inspection becomes unreliable when an organization operates several domains and test environments. Define the host list, renewal owner and warning threshold before building automation. This tutorial reads expiry through a verified TLS connection. A successful handshake is useful evidence, but it is not a complete security assessment or proof that every application route is functioning.

The checker uses Python's default secure context to validate the trust chain and hostname. It does not disable verification simply to retrieve a convenient expiry date after a trust failure. A DNS hostname and port are separate inputs; a complete web URL is rejected. This keeps the interface explicit, but does not make the function suitable for a public arbitrary-target checking service. Use an authorized administrative inventory instead of accepting untrusted network destinations.

Verify hostnames and calculate against UTC

Time calculations use an aware UTC clock. Remaining days can be fractional or negative; rounding too early can conceal an urgent renewal window. Each target records its hostname, port and warning threshold. Connection waits are bounded so that one unavailable endpoint does not indefinitely block the remaining inventory. These limits describe the checker, while DNS availability, outbound firewall rules and the local trust store still influence the observed result.

Save the four files together and run python -m unittest test_cert_check.py. These offline tests exercise UTC boundaries, expired values and invalid inputs. Replace the demonstration entries in hosts.json with domains you administer, then run python run_checks.py hosts.json. The second step requires actual network access and can fail because of firewall policy or trust configuration. Keep that distinction clear when reporting a successful unit test versus an operational certificate inspection.

The command emits one machine-readable record per host. Expiry warnings and connection errors produce a failing exit status. A trust-validation error and an unreachable network endpoint both need attention, but they have different causes and owners. Sites behind a content distribution network can expose one certificate to visitors while using another between the distribution network and origin. Inspect those paths separately rather than assuming that the public certificate represents both connections.

Complete executable files

The four files are complete. The article provides prerequisites, command order and verification; code is neither shortened nor hidden.

1. Complete cert_check.py

Save this entire file beside the others using the exact filename.

import socket
import ssl
from datetime import datetime, timezone


def days_remaining(not_after, now=None):
    now = now or datetime.now(timezone.utc)
    if now.tzinfo is None:
        raise ValueError('Use a timezone-aware clock')
    expires = datetime.fromtimestamp(ssl.cert_time_to_seconds(not_after), timezone.utc)
    return (expires - now).total_seconds() / 86400


def check(host, port=443):
    if not host or '/' in host or ':' in host or not 1 <= port <= 65535:
        raise ValueError('Supply a DNS hostname and separate TCP port')
    context = ssl.create_default_context()
    with socket.create_connection((host, port), timeout=5) as connection:
        with context.wrap_socket(connection, server_hostname=host) as secured:
            certificate = secured.getpeercert()
            return {'host': host, 'port': port, 'daysRemaining': round(days_remaining(certificate['notAfter']), 2),
                    'expires': certificate['notAfter'], 'protocol': secured.version()}

The execution section explains this file’s command, expected outcome and limits.

2. Complete hosts.json

Save this entire file beside the others using the exact filename.

[
  {"host": "example.com", "port": 443, "warnDays": 21}
]

The execution section explains this file’s command, expected outcome and limits.

3. Complete run_checks.py

Save this entire file beside the others using the exact filename.

import json
from pathlib import Path
import sys
from cert_check import check

if __name__ == '__main__':
    hosts = json.loads(Path(sys.argv[1]).read_text(encoding='utf-8'))
    failed = False
    for target in hosts:
        try:
            result = check(target['host'], target.get('port', 443))
            result['warning'] = result['daysRemaining'] <= target.get('warnDays', 21)
            failed |= result['warning']
        except (OSError, ValueError) as error:
            result = {'host': target['host'], 'error': type(error).__name__}
            failed = True
        print(json.dumps(result))
    raise SystemExit(1 if failed else 0)

The execution section explains this file’s command, expected outcome and limits.

4. Complete test_cert_check.py

Save this entire file beside the others using the exact filename.

import unittest
from datetime import datetime, timezone
from cert_check import check, days_remaining


class CertificateTests(unittest.TestCase):
    def test_utc_boundary(self):
        now = datetime(2026, 10, 27, tzinfo=timezone.utc)
        self.assertEqual(days_remaining('Oct 28 00:00:00 2026 GMT', now), 1)
        self.assertEqual(days_remaining('Oct 26 12:00:00 2026 GMT', now), -0.5)

    def test_reject_ambiguous_clock_and_url(self):
        with self.assertRaises(ValueError):
            days_remaining('Oct 28 00:00:00 2026 GMT', datetime(2026, 10, 27))
        with self.assertRaises(ValueError):
            check('https://example.com/')


if __name__ == '__main__':
    unittest.main()

The execution section explains this file’s command, expected outcome and limits.

Run a managed host list and handle warnings

Connect operational warnings to a named owner and a tested renewal process. Review the machine clock, key access and service reload after a new certificate is installed. A currently valid date does not prove that the next automatic renewal will succeed. The useful outcome is an early warning that reaches someone able to act, followed by a fresh verified connection through the same path used by real visitors.

Implementation checklist

  • Keep trust-chain and hostname verification enabled.
  • Use timezone-aware calculations.
  • Inspect distribution and origin paths separately.
  • Assign renewal ownership and test the process.

References

Editorial guidance: Liyan Knowledge.