The short answer

Run this, replacing example.com with your domain:

echo | openssl s_client -connect example.com:443 -servername example.com 2>/dev/null \
  | openssl x509 -noout -enddate

It prints one line with the expiry date in UTC:

notAfter=Jan 14 23:59:59 2027 GMT

openssl s_client opens a TLS connection and prints the server's certificate. echo | closes the connection right after the handshake. -servername sends the hostname with SNI, so a server that hosts many sites returns the certificate for the name you asked for. OpenSSL 1.1.1 and later send the -connect hostname by default, but older versions do not, so setting it keeps the command portable. openssl x509 -noout -enddate reads the certificate and prints only its notAfter field. This is the same command SSLMonitor runs for each domain. The s_client and x509 manual pages document every option used here.

The examples on this page were tested with OpenSSL 3.0 and GNU coreutils on Linux.

Show start date, end date, subject and issuer

echo | openssl s_client -connect example.com:443 -servername example.com 2>/dev/null \
  | openssl x509 -noout -subject -issuer -dates
subject=CN = example.com
issuer=C = US, O = Let's Encrypt, CN = R12
notBefore=Oct 16 23:59:59 2026 GMT
notAfter=Jan 14 23:59:59 2027 GMT

The issuer line above is sample output. Yours shows the certificate authority that signed your certificate.

OpenSSL 3.0 and later can print dates in a format that sorts and parses cleanly:

echo | openssl s_client -connect example.com:443 -servername example.com 2>/dev/null \
  | openssl x509 -noout -enddate -dateopt iso_8601
notAfter=2027-01-14 23:59:59Z

Check whether a certificate expires within N days

-checkend takes a number of seconds. It prints a message and sets the exit code: 0 if the certificate is still valid at that time, 1 if it will have expired. 30 days is 2,592,000 seconds:

echo | openssl s_client -connect example.com:443 -servername example.com 2>/dev/null \
  | openssl x509 -noout -checkend 2592000
echo "exit code: $?"
Certificate will not expire
exit code: 0

The exit code makes it easy to use in a script or a monitoring check:

if ! echo | openssl s_client -connect example.com:443 -servername example.com 2>/dev/null \
  | openssl x509 -noout -checkend 2592000 > /dev/null; then
  echo "example.com expires within 30 days (or could not be checked)"
fi

A failed connection also returns a non-zero exit code, so the message covers both cases.

Count the days left

This is the calculation SSLMonitor uses. It needs GNU date:

end=$(echo | openssl s_client -connect example.com:443 -servername example.com 2>/dev/null \
  | openssl x509 -noout -enddate | cut -d= -f2)
echo $(( ($(date -d "$end" +%s) - $(date +%s)) / 86400 ))

The result is whole days, rounded down. A certificate that expires in 9 days and 20 hours prints 9.

List the names a certificate covers

A certificate is only valid for the names in its Subject Alternative Name extension. To list them (OpenSSL 1.1.1 or later). The names come from RFC 5280, the standard for X.509 certificates:

echo | openssl s_client -connect example.com:443 -servername example.com 2>/dev/null \
  | openssl x509 -noout -ext subjectAltName
X509v3 Subject Alternative Name:
    DNS:example.com, DNS:www.example.com

Check a certificate file on disk

To read a certificate file directly, without a network connection:

openssl x509 -in /etc/letsencrypt/live/example.com/cert.pem -noout -enddate

Comparing this date with the one your server returns tells you whether a renewed certificate has been installed and the web server reloaded. If the file is newer than what the server sends, reload the web server.

Check mail servers with STARTTLS

Mail servers often start in plain text and upgrade with STARTTLS. Add -starttls with the protocol name:

# SMTP submission (port 587)
echo | openssl s_client -connect mail.example.com:587 -starttls smtp -servername mail.example.com 2>/dev/null \
  | openssl x509 -noout -enddate

# IMAP (port 143)
echo | openssl s_client -connect mail.example.com:143 -starttls imap -servername mail.example.com 2>/dev/null \
  | openssl x509 -noout -enddate

Ports that use TLS from the first byte, such as 465 (SMTPS), 993 (IMAPS) and 995 (POP3S), work like HTTPS: use -connect host:993 without -starttls. SSLMonitor itself only checks port 443.

Read the dates with curl

curl with -v prints the certificate dates in its connection details (wording can vary between curl versions and TLS libraries):

curl -sv -o /dev/null https://example.com 2>&1 | grep -E 'start date|expire date'
*  start date: Oct 16 23:59:59 2026 GMT
*  expire date: Jan 14 23:59:59 2027 GMT

Why the command can hang or return nothing

  • Nothing printed: openssl s_client could not connect or the server did not complete a TLS handshake. Run it without 2>/dev/null to see the error, such as Connection refused or a DNS failure.
  • Wrong certificate: the server received no SNI name (for example, you connected to an IP address without -servername, or used OpenSSL older than 1.1.1), so it returned its default certificate.
  • Long wait: a firewall drops packets instead of rejecting them, so openssl waits for the TCP connect timeout. Wrap the command in timeout 10 to cap it.
  • Behind a proxy: a TLS-inspecting proxy returns its own certificate, so check from a network without one.

Run these checks every day

SSLMonitor wraps the first command on this page in a loop over your domain list, works out the days left, and emails a report when a certificate is close to expiry. The install guide takes a few minutes.