<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Md Jewele Islam</title>
    <description>The latest articles on DEV Community by Md Jewele Islam (@md_jeweleislam).</description>
    <link>https://dev.to/md_jeweleislam</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4229034%2Fb5588bb5-6b56-4232-90d5-1b13d7591314.jpg</url>
      <title>DEV Community: Md Jewele Islam</title>
      <link>https://dev.to/md_jeweleislam</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/md_jeweleislam"/>
    <language>en</language>
    <item>
      <title>Read a TLS certificate's dates with openssl, and make sure it's the right certificate</title>
      <dc:creator>Md Jewele Islam</dc:creator>
      <pubDate>Thu, 05 Nov 2026 17:06:31 +0000</pubDate>
      <link>https://dev.to/md_jeweleislam/read-a-tls-certificates-dates-with-openssl-and-make-sure-its-the-right-certificate-hgo</link>
      <guid>https://dev.to/md_jeweleislam/read-a-tls-certificates-dates-with-openssl-and-make-sure-its-the-right-certificate-hgo</guid>
      <description>&lt;p&gt;A browser says the certificate has expired, or a monitor says it's about to. Before you touch anything, ask the server what it's really presenting. That takes one command, and most of the mistakes come from looking at the wrong certificate.&lt;/p&gt;

&lt;h2&gt;
  
  
  The command
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; | openssl s_client &lt;span class="nt"&gt;-connect&lt;/span&gt; example.com:443 &lt;span class="nt"&gt;-servername&lt;/span&gt; example.com 2&amp;gt;/dev/null &lt;span class="se"&gt;\&lt;/span&gt;
  | openssl x509 &lt;span class="nt"&gt;-noout&lt;/span&gt; &lt;span class="nt"&gt;-subject&lt;/span&gt; &lt;span class="nt"&gt;-issuer&lt;/span&gt; &lt;span class="nt"&gt;-dates&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;subject=CN=example.com
issuer=C=US, O=SSL Corporation, CN=Cloudflare TLS Issuing ECC CA 3
notBefore=Sep 26 22:49:11 2026 GMT
notAfter=Dec 25 22:56:35 2026 GMT
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The first &lt;code&gt;openssl&lt;/code&gt; connects and prints the handshake, and the second reads the certificate out of it. &lt;code&gt;echo |&lt;/code&gt; closes the connection instead of leaving it waiting for your input. &lt;code&gt;notAfter&lt;/code&gt; in the past means expired. &lt;code&gt;notBefore&lt;/code&gt; in the future means not valid yet, and the same browser error covers both.&lt;/p&gt;

&lt;h2&gt;
  
  
  Don't drop -servername
&lt;/h2&gt;

&lt;p&gt;One address can serve many sites, and &lt;code&gt;-servername&lt;/code&gt; says which one you want. I set up a throwaway nginx with three sites on one port and asked for two of them, then for none:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; | openssl s_client &lt;span class="nt"&gt;-connect&lt;/span&gt; 127.0.0.1:18443 &lt;span class="nt"&gt;-servername&lt;/span&gt; old.test 2&amp;gt;/dev/null &lt;span class="se"&gt;\&lt;/span&gt;
  | openssl x509 &lt;span class="nt"&gt;-noout&lt;/span&gt; &lt;span class="nt"&gt;-subject&lt;/span&gt; &lt;span class="nt"&gt;-enddate&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;subject=CN=old.test
notAfter=Jan  1 00:00:00 2025 GMT
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With &lt;code&gt;-servername good.test&lt;/code&gt; the same port gave:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;subject=CN=good.test
notAfter=Jan  1 00:00:00 2030 GMT
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And with &lt;code&gt;-noservername&lt;/code&gt;, I got the default site's certificate, which was neither of those:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;subject=CN=future.test
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So if you leave the name out, or get it wrong, the dates you read may belong to some other site on that server.&lt;/p&gt;

&lt;h2&gt;
  
  
  A check you can script
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;-checkend&lt;/code&gt; takes a number of seconds and exits non-zero if the certificate will have expired by then. 2592000 is 30 days.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; | openssl s_client &lt;span class="nt"&gt;-connect&lt;/span&gt; example.com:443 &lt;span class="nt"&gt;-servername&lt;/span&gt; example.com 2&amp;gt;/dev/null &lt;span class="se"&gt;\&lt;/span&gt;
  | openssl x509 &lt;span class="nt"&gt;-noout&lt;/span&gt; &lt;span class="nt"&gt;-checkend&lt;/span&gt; 2592000
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"exit=&lt;/span&gt;&lt;span class="nv"&gt;$?&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Certificate will not expire
exit=0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Widened to 90 days (7776000), the same certificate says:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Certificate will expire
exit=1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There's a catch. &lt;strong&gt;It only looks at the end date.&lt;/strong&gt; My test certificate that doesn't start until 2030 said this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;subject=CN=future.test
notBefore=Jan  1 00:00:00 2030 GMT
notAfter=Jan  1 00:00:00 2031 GMT
Certificate will not expire
exit=0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A browser would refuse that certificate. So a passing &lt;code&gt;-checkend&lt;/code&gt; doesn't prove it's valid today. Also, if the connection fails, &lt;code&gt;openssl x509&lt;/code&gt; finds nothing to read and exits 1 with &lt;code&gt;Could not find certificate from &amp;lt;stdin&amp;gt;&lt;/code&gt;. So in a script, exit 1 can mean "expiring" or "couldn't connect". Read the message as well as the status.&lt;/p&gt;

&lt;h2&gt;
  
  
  Does it cover the name you're using?
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; | openssl s_client &lt;span class="nt"&gt;-connect&lt;/span&gt; example.com:443 &lt;span class="nt"&gt;-servername&lt;/span&gt; example.com 2&amp;gt;/dev/null &lt;span class="se"&gt;\&lt;/span&gt;
  | openssl x509 &lt;span class="nt"&gt;-noout&lt;/span&gt; &lt;span class="nt"&gt;-ext&lt;/span&gt; subjectAltName
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;X509v3 Subject Alternative Name: 
    DNS:example.com, DNS:*.example.com
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The dates can be fine and the name still wrong. That's a different browser error.&lt;/p&gt;

&lt;h2&gt;
  
  
  Renewed, but still the old date?
&lt;/h2&gt;

&lt;p&gt;If your renewal tool shows a new date and this command doesn't, check two things.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The server hasn't loaded the new file.&lt;/strong&gt; I overwrote the certificate files on that test nginx, and the command kept showing the old one:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;subject=CN=old.test
notAfter=Jan  1 00:00:00 2025 GMT
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After &lt;code&gt;nginx -s reload&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;subject=CN=good.test
notAfter=Jan  1 00:00:00 2030 GMT
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;You're reaching a different machine.&lt;/strong&gt; If a CDN sits in front, you're reading the CDN's certificate. To read your own server's, connect to its IP and keep your domain as the name:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; | openssl s_client &lt;span class="nt"&gt;-connect&lt;/span&gt; YOUR.SERVER.IP:443 &lt;span class="nt"&gt;-servername&lt;/span&gt; yourdomain.com 2&amp;gt;/dev/null &lt;span class="se"&gt;\&lt;/span&gt;
  | openssl x509 &lt;span class="nt"&gt;-noout&lt;/span&gt; &lt;span class="nt"&gt;-dates&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I only tested that form against a local nginx at 127.0.0.1. &lt;strong&gt;Untested against a real CDN and origin.&lt;/strong&gt; A firewall that lets only your CDN in will block this from outside, so you'd run it on the server itself.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I ran it on
&lt;/h2&gt;

&lt;p&gt;OpenSSL 3.6.3 on macOS, on 3 November 2026, and again on 5 November 2026 with the same results. The local tests used nginx 1.30.6 in Docker, with self-signed certificates made for the purpose. The example.com output is from those days, and its dates will have changed by the time you read this. &lt;strong&gt;Untested:&lt;/strong&gt; other OpenSSL versions and LibreSSL.&lt;/p&gt;




&lt;p&gt;An AI agent wrote this post and ran every command above on 3 and 5 November 2026; I chose the topic and approved it for posting. I run SiteHealthRadar. A longer version covering hosting panels, Cloudflare and Certbot is at &lt;a href="https://sitehealthradar.com/guides/err-cert-date-invalid/" rel="noopener noreferrer"&gt;https://sitehealthradar.com/guides/err-cert-date-invalid/&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>openssl</category>
      <category>tls</category>
      <category>nginx</category>
      <category>linux</category>
    </item>
  </channel>
</rss>
