Security

TLS inspection rewrites every certificate your probes see

August 12, 20269 min readCertPulse Engineering

Your expiry dashboard is all green. Ninety-one days left on the payment API cert, 240 on the marketing site, nothing near the 30-day threshold. Then a customer files a ticket because their integration is failing certificate validation, and it turns out the cert your monitoring has been cheerfully reporting on for four months is one that nobody outside your network has ever seen.

If your probes run inside a corporate perimeter that does TLS inspection, this isn't an edge case. It's what the wiring produces, and it fails the worst way anything can fail: quietly.

What interception does to the handshake

An SSL interception proxy (Zscaler, Netskope, Palo Alto decryption policies, Fortinet deep inspection, Cisco Secure Web Appliance, or a Squid box somebody stood up in 2019 and never told anyone about) doesn't forward your TLS connection. It terminates it.

The proxy reads the ClientHello, pulls the SNI, and opens its own connection to the real destination. It validates that upstream cert against its own trust store, with a strictness that varies wildly between products and configs. Then it mints a brand new leaf for the name you asked for, signs it with a CA key sitting on the appliance, and finishes the handshake with your client using that.

Your client accepts it, because someone pushed the corporate root into the OS trust store years ago via Group Policy or an MDM profile. Clean handshake, as far as the client can tell. As far as the protocol is concerned, it's a textbook man-in-the-middle you're paying a license fee for.

The cert you get back has almost nothing in common with the real one. Same subject name, probably the same SANs, and that's the end of the list. Different issuer, different public key, different serial, usually a different signature algorithm. And a validity window frequently measured in hours: plenty of proxies mint per-connection or per-session certs with a notBefore of about now and a notAfter a few hours out, because why issue anything longer when you own the CA and the cache?

That last detail is what eats your monitoring alive.

Why this poisons certificate monitoring

An expiry check does four things. Connect, grab the leaf, parse notAfter, subtract today. Alert if the number lands under some threshold.

Now run that through a proxy minting fresh 12-hour certs on every cache miss. Reported expiry is always a few hours out. Depending on how the threshold is written, the check either screams constantly and gets muted inside a week or, more common and much worse, gets quietly filed under "that's just how that one behaves." Either way the real certificate's expiry is invisible to you now. It can lapse and your dashboard will keep showing a valid, freshly issued cert. The proxy is generous like that.

You don't get a wrong number. You get a permanent false negative, and false negatives don't page anyone.

This is getting worse, not better. The CA/Browser Forum's SC-081v3 timeline is dragging maximum lifetimes down: 200 days today, 100 in March 2027, 47 in March 2029. Renewals per year across your estate are about to climb hard, and every renewal is another chance for automation to fail without saying anything. Monitoring that structurally cannot see the real certificate will be wrong more often, at higher volume, with less time to catch it.

The tells

You can spot interception from the certificate itself. Roughly in order of how much I'd trust each signal:

No embedded SCTs. The strongest one by a distance. Publicly trusted certs carry Signed Certificate Timestamps as an X.509 extension (RFC 6962) because Chrome and Safari require CT compliance for anything chaining to a public root. Certs chaining to a locally installed root are exempt from CT policy, which is the whole reason interception works in browsers at all, so proxies have no reason to embed SCTs and basically never do. A leaf with zero SCTs claiming to be a public web server cert did not come from a public CA.

A validity window no public CA would issue. Under a day is an artifact, full stop. Nobody sells a two-hour certificate. Compare notBefore to notAfter and flag anything implausibly short.

An issuer CN outside your expected set. Easy when the appliance is honest about itself: issuers with "Zscaler" or "netskope" in them, or the hostname of a firewall. Harder when someone configured the forward-trust CA with a subject that mimics a real CA, which people absolutely do to cut help desk volume. Don't trust the string alone.

Key algorithm or size mismatch. You deployed ECDSA P-256 and the probe reports RSA-2048. Most proxies mint everything with one key type, and RSA-2048 is the default they ship with. Cheap to check, hard to fake.

Serial number entropy. The Baseline Requirements want at least 64 bits of CSPRNG output in the serial. Proxy serials are often small, sequential, or derived from the upstream cert. A four-byte serial is not a public CA serial.

Root not in CCADB. Walk the chain to the root and check it against the public CA certificate database. In your OS store but in no public root program? There's your interception CA.

Missing AIA OCSP URLs used to live on this list, and you'll still see it cited. Drop it. After SC-063 made OCSP optional and Let's Encrypt turned off its responders, piles of legitimate certs have no OCSP URL at all. Missing OCSP proves nothing now. Same goes for a missing stapled response: proxies never staple, but neither do plenty of real servers.

The check that settles it is the public key fingerprint. Take the SPKI hash of the leaf your probe saw and look for it among the certs logged in CT for that domain. A real public cert is in the logs by definition. A proxy-minted one never will be.

What else breaks

Once inspection is on by default, things break in places that have nothing to do with monitoring.

Runtimes that ignore the OS trust store generate more "works on my laptop, fails in the pipeline" tickets than everything else here combined. Pushing your corporate root into the Windows or Linux system store does nothing for a large chunk of what actually runs in your environment. Python's requests reads certifi, not the system store, unless you set REQUESTS_CA_BUNDLE or SSL_CERT_FILE; you'll see "certificate verify failed: unable to get local issuer certificate." Java keeps its own cacerts keystore and wants the root imported with keytool, and the symptom is that PKIX path building failure every Java dev can recite from memory. Node wants NODE_EXTRA_CA_CERTS pointed at a PEM. Go binaries in scratch containers have no trust store at all, because nobody copied a ca-certificates bundle into the image, so every outbound call comes back "x509: certificate signed by unknown authority." None of that is a bug. It's four different opinions about where trust lives, and TLS inspection surfaces all four in the same afternoon.

Pinning is next. Mobile SDKs that pin (payment processors, some auth providers, a lot of vendor SDKs) hard fail against an intercepted connection, which is exactly what pinning is for. There's no fix on your end. Those destinations get bypassed or they don't work.

mTLS breaks too, and it confuses people longer than it should. The proxy terminates the client's connection and originates its own, and it doesn't hold the client's private key. It can't present a certificate it doesn't have. Every mutual-TLS flow in your environment needs an explicit do-not-decrypt entry, and the failure usually surfaces as a generic connection reset, which sends someone hunting through network gear for a day.

The bypass list is infrastructure

Every org with an interception proxy has a bypass list, and every one I've seen follows the same arc. It starts at three domains: the payment gateway, one bank, one SDK endpoint that pins. Two years later it's a hundred and forty entries and nobody owns it. A good chunk point at hosts decommissioned before the current platform team was hired. Several are wildcards that quietly exempt an entire cloud provider from inspection, which your security team would probably like to hear about.

It's a firewall ruleset. Treat it like one: a list of exceptions to your own controls that only grows unless somebody prunes it.

So give every entry an owner, a reason code, and a review date. The reason codes that matter are pinning, mTLS, regulatory (data that legally shouldn't be decrypted), and the honest one, "it broke and nobody could figure out why." That last bucket is where the good archaeology is. Review quarterly, resolve DNS on every entry, delete what doesn't resolve. Then go back through the pinning entries, because vendors drop pinning more often than they add it.

One thing to plan for: bypass rules keyed on SNI get less reliable as Encrypted Client Hello spreads. If your bypass logic depends on reading plaintext SNI, find out which rules that covers before it becomes an incident.

Where to put your vantage points

Detection is the consolation prize. What actually fixes this is putting probes where interception can't reach them, and being honest about what each probe is qualified to tell you.

Anything public-facing gets probed from outside your network. Not from a bastion. Not from a CI runner. Not from a VM in the same VPC that egresses through the inspection stack. Outside. That probe sees what your customers see, and for a public endpoint nothing else counts. A monitor running inside the perimeter is measuring your proxy, not your infrastructure.

Internal probes are for internal PKI, and they're good at it: internal CAs, service mesh certs, mTLS endpoints, everything no external prober can reach. Just scope them honestly and never let an internal green tick stand in for external reality.

Then correlate both against Certificate Transparency. CT is ground truth for what a public CA actually issued for your domains. If the leaf your probe reports matches no CT entry for that name, you're either being intercepted or something worse is going on. CT also catches the certs you didn't know existed: the ones a team issued through a different CA account, the ones nobody authorized at all.

That's the gap in the internal Nagios or blackbox_exporter check most teams are still running. The check isn't badly written. It's standing in the wrong place, and no amount of tuning fixes a vantage point. CertPulse probes from outside your network and watches CT independently, so you can say what an external client actually receives.

Go look at where your certificate monitoring runs right now. If the answer is "a jump host" or "the build agents," pull one leaf and count:

openssl s_client -connect yourdomain.com:443 -servername yourdomain.com </dev/null 2>/dev/null \
  | openssl x509 -noout -text | grep -c "Signed Certificate Timestamp"

If that prints 0, your dashboard has been describing your proxy this whole time.

This is why we built CertPulse

CertPulse connects to your AWS, Azure, and GCP accounts, enumerates every certificate, monitors your external endpoints, and watches Certificate Transparency logs. One dashboard for every cert. Alerts when auto-renewal fails. Alerts when certs approach expiry. Alerts when someone issues a cert for your domain that you didn't request.

If you're looking for complete certificate visibility without maintaining scripts, we can get you there in about 5 minutes.