An expired TLS certificate is one of the dumbest ways to take production down, because the outage date was printed inside the certificate the whole time. And yet, if that certificate lives in an Azure Application Gateway listener, an App Service, or an API Management instance, Azure will not say a word before it lapses.
That surprises people, because Azure does alert on certificates in some places. The catch is which places:
- Key Vault certificates get near-expiry Event Grid events. Covered.
- Front Door / CDN managed certificates are auto-rotated by Azure. Covered.
-
App Gateway bring-your-own listener certs (
sslCertificateson the gateway): nothing. -
App Gateway backend trust roots (
trustedRootCertificates, used for end-to-end TLS): nothing. -
App Service uploaded certs (
Microsoft.Web/certificates): the resource exposesexpirationDate, but nothing watches it. -
API Management certs: same story,
expirationDateexists, no one is looking at it.
The uncovered stores are exactly the ones where somebody uploaded a cert by hand two years ago and moved on. That is where the 2 a.m. outages come from.
The DIY sweep
The shape of the fix is simple: a scheduled Function with a subscription-scope Reader role that enumerates every un-alerted certificate store, reads expiry dates, and posts a digest to Teams.
App Service and APIM are the easy part, since they hand you expirationDate as a plain date. App Gateway is where it gets fun.
The gotcha: publicCertData is not what the docs say
For each App Gateway, the ARM GET returns the uploaded listener certs with a base64 publicCertData field. ARM documents it as "Base-64 encoded Public cert data corresponding to pfx". It is not. It is a PKCS7 (P7B) bundle, leaf plus chain, not a bare X.509 certificate. Feed it straight to an X.509 parser and it fails.
So the decode has to be: try PKCS7 first, pick the leaf (end-entity) cert out of the bundle, and read its notAfter. Then fall back to parsing a single DER/PEM cert for blobs that are not bundles (the backend trust roots often are single certs). In Node, node-forge handles the PKCS7 case, and the built-in node:crypto X509Certificate covers the single-cert fallback.
Two more things worth building in:
- Recurring escalation. A one-shot alert 30 days out gets lost. Classify against thresholds (30, 14, 7, 1 days works well) and re-report every expiring cert daily until it is actually renewed.
- A "could not read expiry" bucket. A cert blob that fails to decode is itself a signal. Surface it instead of swallowing the parse error.
And keep it least-privilege: everything above needs only certificate metadata and public cert data through the management plane. Private keys never enter the picture.
If you would rather not maintain it
Disclosure: I work for Katabarwa Labs, and we ship one app that does exactly this, Cert Sentinel. It is one option next to building the sweep yourself, not the only way.
It deploys into your own subscription as a managed application (no vendor backend, nothing leaves your tenant), sweeps App Gateway listener and backend certs, App Service certs, and APIM certs every day at 08:00 UTC, and posts one Teams digest of everything expired, expiring, or unreadable, re-reported daily until renewed. It needs only the Reader role, and it deliberately skips Key Vault and Front Door / CDN managed certs because Azure already covers those.
Listing: https://marketplace.microsoft.com/en-us/product/azure-application/katabarwalabs.cert-sentinel
I wrote up the longer version, including the full DIY pattern, here: https://katabarwalabs.dev/blog/azure-tls-certificate-expiry-alerts
Takeaway
Do not assume Azure is watching your TLS expiry dates. It watches Key Vault and its own managed certs, and that is it. The certs that actually terminate your production traffic on App Gateway and App Service are on you. Whether you build the sweep or buy one, make sure something reads those notAfter dates before your customers do.
Top comments (0)