<?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: Jesica Kearney</title>
    <description>The latest articles on DEV Community by Jesica Kearney (@jesskearney).</description>
    <link>https://dev.to/jesskearney</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%2F2867225%2F21d18fcf-424a-4fe0-a0ef-dddc2d1d05a5.jpg</url>
      <title>DEV Community: Jesica Kearney</title>
      <link>https://dev.to/jesskearney</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/jesskearney"/>
    <language>en</language>
    <item>
      <title>What actually happens when a TLS certificate expires (and how I built a Bash guardrail for it)</title>
      <dc:creator>Jesica Kearney</dc:creator>
      <pubDate>Sat, 12 Sep 2026 17:51:20 +0000</pubDate>
      <link>https://dev.to/jesskearney/what-actually-happens-when-a-tls-certificate-expires-and-how-i-built-a-bash-guardrail-for-it-4m35</link>
      <guid>https://dev.to/jesskearney/what-actually-happens-when-a-tls-certificate-expires-and-how-i-built-a-bash-guardrail-for-it-4m35</guid>
      <description>&lt;p&gt;Every few months, somewhere, a website goes down for the most avoidable reason in infrastructure: a TLS certificate expired and nobody was watching. Browsers throw a full-screen red warning, API clients refuse to connect, and an on-call engineer gets paged for something a calendar reminder could have prevented.&lt;/p&gt;

&lt;p&gt;I've dealt with enough certificate issues in production to want a lightweight guardrail I fully control — so I built one in Bash, rather than reaching for a managed service that hides the mechanics. It's a small tool, but it's a good excuse to walk through what a certificate actually is and why expiry is only half the problem.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A certificate is a public identity document&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A website's certificate is public by design. When a client connects over HTTPS, the server presents its certificate to anyone who asks — before any encryption begins — because the certificate is how the server proves it is who it claims to be.&lt;/p&gt;

&lt;p&gt;You can see this yourself. This one command opens a TLS connection and reads the certificate's expiry date:&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;-servername&lt;/span&gt; github.com &lt;span class="nt"&gt;-connect&lt;/span&gt; github.com:443 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;-enddate&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No login, no API key. The certificate carries only public metadata: the domain it was issued for, the issuing authority, the public key, and the validity window. The private key never leaves the server. That split is the whole basis of TLS — anyone can verify the server's identity, but only the server can prove it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The handshake, step by step&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Checking a certificate is just performing the opening moves of a normal HTTPS connection:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;TCP connect&lt;/strong&gt; to the host on port 443, exactly as a browser would.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ClientHello + SNI.&lt;/strong&gt; The client signals it wants to speak TLS, and the 
&lt;em&gt;-servername&lt;/em&gt; flag sets SNI (Server Name Indication) — because many sites share one IP address, SNI tells the server which site's certificate to present. Omit it and you can be handed the wrong certificate entirely.&lt;/li&gt;
&lt;li&gt;The server presents its certificate in the clear.&lt;/li&gt;
&lt;li&gt;Parse the &lt;em&gt;notAfter&lt;/em&gt; date and compute days remaining.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;A browser would continue from here into an encrypted session; the check just reads the date and disconnects.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Expiry is only one of the checks&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Expiry is the obvious failure mode, but it's only one of several independent checks a browser runs. A certificate can be perfectly in-date and still be rejected — because it was issued for the wrong hostname.&lt;/p&gt;

&lt;p&gt;The clearest example is a wildcard-scope mismatch. A &lt;em&gt;*.badssl.com&lt;/em&gt; certificate is valid, but wildcards match only one level, so it does not cover &lt;em&gt;wrong.host.badssl.com.&lt;/em&gt; Other common causes in the wild:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Shared IP / wrong default cert&lt;/strong&gt; — a missing SNI default hands you another site's valid certificate.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reused certificate&lt;/strong&gt; — a cert deployed onto the wrong host by mistake.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CDN / load-balancer gaps&lt;/strong&gt; — a new subdomain pointed at infrastructure whose certificate doesn't include it yet.
In every case the browser hard-blocks the connection, because a valid certificate belonging to someone else is exactly what a man-in-the-middle attack looks like. Expiry checks miss all of these, so hostname matching has to be a separate check:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bash
openssl s_client &lt;span class="nt"&gt;-servername&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$domain&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-connect&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$domain&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;:443 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-verify_hostname&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$domain&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This returns &lt;strong&gt;&lt;em&gt;Verify return code: 0 (ok)&lt;/em&gt;&lt;/strong&gt; on a match, or &lt;strong&gt;&lt;em&gt;62 (hostname mismatch)&lt;/em&gt;&lt;/strong&gt; when the certificate is real but wrong for that host.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Turning a script into infrastructure&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A script that prints text is a toy. What makes it usable by real systems is exit codes. This one follows the Nagios convention:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Code    Meaning
0   All certificates OK
1   Warning window, or a hostname mismatch
2   Critical, expired, or unreachable
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That single design choice is what lets a scheduler act on the result without parsing any text. Wired to a &lt;em&gt;GitHub Actions cron&lt;/em&gt;, it needs no extra tooling: because the script exits non-zero on trouble, a failing certificate turns the workflow run red — and that red status is the alert.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why build what you could buy&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Some companies offer certificate monitoring as a checkbox, so building it by hand isn't about saving money. The value is owning every layer under that checkbox — &lt;strong&gt;the handshake, SNI, the public/private key split, why hostname verification is separate from expiry, and how exit codes turn a script into something a monitoring system can consume&lt;/strong&gt;. When the managed version misbehaves, that's the difference between debugging it and filing a support ticket.&lt;/p&gt;

&lt;p&gt;The full tool — including a &lt;strong&gt;companion process/inode inspector&lt;/strong&gt; — is on GitHub:[&lt;a href="https://github.com/JayKearney/linux-toolbox" rel="noopener noreferrer"&gt;https://github.com/JayKearney/linux-toolbox&lt;/a&gt;]. Feedback and questions welcome.&lt;/p&gt;

</description>
      <category>devops</category>
      <category>bash</category>
      <category>security</category>
      <category>kubernetes</category>
    </item>
  </channel>
</rss>
