How to monitor private PKI, client, code-signing and S/MIME certificates that no scanner can reach
Published 15 Sep 2026 · Updated 15 Sep 2026
The certificates a scanner never sees
Every certificate checker works the same way: connect to a host on a port, read the certificate the server presents, look at the date. That covers every service that answers TLS on an address the checker can reach, and leaves out a whole class of certificates never presented to the internet. Those expire just the same.
An internal certificate authority. Many organisations run a private PKI: Active Directory Certificate Services, a Vault or step-ca issuer, an OpenSSL root somebody set up years ago. It issues certificates for intranet sites, internal APIs, service-to-service TLS, the VPN, 802.1X Wi-Fi, printers and management interfaces. None of those hosts is reachable from outside and none of the certificates is in the public transparency logs, so no hosted checker has seen one. The authority's own root and intermediates expire too, and when one goes, everything under it goes with it.
Client certificates. In mutual TLS the client presents a certificate and the server checks it. A connection from outside shows the server's certificate, never the client's, so the certificate a service uses to call another, or a device uses to join a network, is invisible to any scan. When it expires the client is refused, and the error lands in the caller's logs.
Code-signing certificates. Installers, drivers, packages and scripts are signed so an operating system will run them. The certificate sits on a hardware token or in an HSM in the build pipeline, presented to nobody. A build with a timestamped signature usually keeps working after the certificate expires; the next build cannot be signed, and release day is when that is discovered.
S/MIME and other signing certificates. A mail-signing certificate lives in a mailbox; a SAML or OIDC signing certificate lives in an identity provider's metadata, and when it expires single sign-on stops everywhere. A Kubernetes cluster set up with kubeadm has client certificates that expire after a year by default. Each has a "not after" date, and each is served on no port anyone can dial.
The common thread is that the failure never looks like a certificate. It looks like a login that stopped working, a build that will not sign, two services that refuse each other. Whoever issued the certificate wrote the date in a spreadsheet and has since changed teams.
Report it instead of fetching it
hostnag cannot connect to these, and does not try. A reported certificate is a monitor that holds a copy of the certificate you give it and judges the copy on a schedule. Its target is a label, what the certificate belongs to, never resolved. Two ways hand over the copy, and both replace the one already held.
Paste it. The wizard has a box for the PEM text and a file picker that takes a .pem, .crt or .cer; the monitor page has the same box. Paste, save, and the parsed certificate is on the page.
Post it. Each reported certificate has a URL of its own, https://hostnag.com/api/cert/<token>, minted when the monitor is created. POST the certificate as the request body, PEM or DER, up to 64 KB. The token is the whole credential: no account, key or header, and anyone with the URL can post to it, so the monitor page has a control to rotate it if it leaks. The door answers 204 when the copy is recorded, 400 with a one-line reason when the body is not a certificate, 413 over the cap, 429 for a second post within a second, and 404 for an unknown token.
What it does with the body is strict. It parses the certificate once and keeps the parsed fields, the subject, the issuer, the names it covers, the validity dates, the SHA-256 fingerprint and the serial number, along with when the copy arrived, whether it was pasted or posted and, for a post, the address it came from. It never keeps the file, and it does not check who signed it: a reported certificate is judged on its dates and watched for changes, not validated against a chain. In a bundle the first certificate is the one judged. A PEM that carries a private key, the usual export mistake, is refused whole: nothing is stored, and the reason comes back in the 400. hostnag never needs the key, only the certificate, which is public by design.
Post one by hand
curl -sS -X POST --data-binary @cert.pem https://hostnag.com/api/cert/<token>
openssl pkcs12 -in signing.pfx -clcerts -nokeys | openssl x509 \
| curl -sS -X POST --data-binary @- https://hostnag.com/api/cert/<token>
openssl s_client -connect host.internal:443 -servername host.internal </dev/null 2>/dev/null \
| openssl x509 | curl -sS -X POST --data-binary @- https://hostnag.com/api/cert/<token>
The first line posts a file. The second extracts the certificate alone from a PKCS#12 bundle, -nokeys so the key stays where it is. The third reads the certificate an intranet host is serving right now and posts that. The monitor page prints the first and third with your token in place. On Windows, Export-Certificate writes a certificate out of the store as DER, which the door accepts as is.
Let a schedule report it
A certificate posted once is watched until the next renewal and then forgotten again, since the copy hostnag holds is the old one. The answer is to have the machine that holds it post on a schedule. The Beacon, a plain script served at https://hostnag.com/beacon.sh for Linux and macOS and https://hostnag.com/beacon.ps1 for Windows, reads a config file with one line per monitor. The line for a reported certificate is the word cert, the token and the path of the file:
cert <token> /etc/pki/tls/certs/internal-api.pem
On each run the script takes the file's SHA-256 and posts it only when the hash has changed since the last post or a day has passed, so a renewed certificate is reported on the first run after it lands and a stable one is posted once a day as a sign of life. Between posts the line is silent; --verbose prints "posted" or "unchanged" on a hand run. The Beacon dialog on the group page writes the config with each monitor's name above its line and a placeholder path to replace, prints the download, verify and schedule lines for cron or schtasks, and shows each certificate being heard from. The script needs curl and sha256sum or shasum, is unsigned and readable end to end, and is removed by deleting it, its config and the small .state file it keeps beside the config. It never reads the key.
Judged daily, on the same ladder
The copy is judged in-process, once a day, without connecting to anything: the interval is 1440 minutes on every plan and cannot be shortened, because a copy does not change between posts. A paste or a post pulls the next judgement forward, so a new copy shows its expiry within a minute.
The judgement is the one a served certificate gets. The group's alert policy holds a ladder of days before expiry, 30, 14, 7, 3, 1 by default, and each rung is its own alert, "certificate expires in 7 days" with the date, the label and the issuer, repeating daily inside the last window. When a renewed copy arrives with a later date, the open expiry alerts resolve and, with recovery notices on, a note names the new date. A new fingerprint raises a Changed alert, and a new issuer raises one naming the old and new authority, both behind the group's notify-on-change setting, on by default. So a code-signing certificate renewed by a colleague and posted by the Beacon announces itself, and one reissued by another authority does too.
One thing is different. A served certificate can be fetched again tomorrow; a reported one is only as fresh as its last post. So the monitor takes an optional cadence, expect a copy every 1, 7, 30 or 90 days, matched to how often the Beacon or your own job posts. Silence past the cadence plus 1 day of grace is a Couldn't check with the days overdue, never a Down: a missing post means the script stopped, not the certificate. After three failed judgements in a row a warning says there is no report for the label and the script that posts to hostnag may have stopped; the next post resolves it. Without a cadence, the last copy is trusted indefinitely.
Check it now, then watch it
Check it now: the SSL checker reads the certificate a public host serves, with its days left, chain and hostname match, which covers everything a scanner can reach. For the rest there is no public tool by design: nothing outside your network can see them, which is why they are reported.
Watch it for good: certificate monitoring fetches every certificate hostnag can reach and mints a report URL for every one it cannot, judging the copy daily on the same ladder, with the same change alerts.
Check it now
Watch it for good
Watch this for good.
Start free