Zabbix SSL Certificate Monitoring

I do not like certificate expiry surprises, and I do not trust calendar reminders as the only defense. Certificates expire on weekends, behind CDNs, on forgotten admin panels, on odd ports, and on third-party services nobody thought to inventory. That is why I prefer central monitoring over scattered shell scripts living on random servers.

Zabbix is a good fit for this because it can monitor certificate validity centrally, trigger alerts before the deadline, and keep the results visible next to the rest of your infrastructure. If you already run Zabbix, adding certificate checks is much easier than inventing a parallel toolchain. If you do not run Zabbix, it may still be worth it for environments that want one place for network, host, service, and TLS monitoring.

This article focuses on practical certificate expiry monitoring with Zabbix: built-in web checks, the web.certificate.get item available in newer versions, alert thresholds, non-standard ports, third-party endpoints, older-version workarounds, and the common traps that make people think they are monitoring one certificate while they are really monitoring another.

For the broader mindset around certificate inventory and alert thresholds, the SSL certificate monitoring guide is still the best overview. If you use Prometheus elsewhere in the estate, the Prometheus and Node Exporter monitoring article is a good comparison point because the operational trade-offs are different.

Why dedicated certificate monitoring matters

A lot of teams start with ad hoc checks.

They run something like this from cron:

echo | openssl s_client -connect example.com:443 -servername example.com 2>/dev/null | \
openssl x509 -noout -dates

That works for a quick manual check. It does not give you a durable inventory, escalation, history, dashboards, dependencies, or central alerting.

The failure modes I have actually seen in production include:

  • monitoring only the origin while users hit a CDN edge cert
  • monitoring the wrong hostname on a shared IP
  • only checking port 443 while a real service runs on 8443
  • relying on mail reminders from a CA that never reach the right person
  • renewing correctly but forgetting a load balancer or alternate binding

Central monitoring helps because it turns certificate validity into an operational signal rather than tribal knowledge.

Zabbix options for certificate checks

There are a few different ways to approach this depending on your Zabbix version and what exactly you need to monitor.

Built-in web scenarios

Web scenarios are useful when you want to check more than just certificate metadata. They can confirm that an HTTPS endpoint responds correctly over time.

A basic scenario tells you:

  • the URL is reachable
  • HTTPS negotiation works
  • response codes are sane
  • a login page or content match is present

That is broader than certificate expiry alone, which can be helpful.

web.certificate.get in Zabbix 5.4+

For newer Zabbix versions, this is the cleanest certificate-specific option.

The item key syntax is:

web.certificate.get[<dns_name>[,<port>[,<ip>]]]

Example item key:

web.certificate.get[example.com,443]

Or, when you need to connect to a specific IP while validating the certificate for a particular hostname:

web.certificate.get[example.com,443,192.0.2.10]

This matters in environments with split routing, pre-cutover validation, or hostnames pointing through multiple paths.

What web.certificate.get returns

The item returns JSON rather than a single number. Depending on Zabbix version, fields can include data such as:

  • subject
  • issuer
  • serial
  • not_before
  • not_after
  • subject alternative names
  • fingerprint
  • public key algorithm and size
  • days_left
  • validity or error fields

That structure is useful because you can build dependent items from it instead of running multiple external checks.

Agent-based versus agentless monitoring

Certificate monitoring in Zabbix does not always need an agent on the target host.

Agentless checks

For public websites and third-party endpoints, agentless monitoring is often the right choice. Zabbix simply connects to the remote HTTPS service and inspects the certificate presented.

That is ideal when:

  • the endpoint is outside your infrastructure
  • you cannot install an agent
  • you care about what end users actually see from the network path Zabbix uses

Agent-based checks

Agent-based checks make more sense when:

  • you need to inspect local certificate files
  • you are on an older Zabbix version and using external scripts or UserParameters
  • you want host-specific context beyond the network endpoint

I do not force everything into agent mode just because the agent is already there. For public TLS, agentless monitoring is often cleaner.

Setting up web.certificate.get cleanly

In a modern Zabbix deployment, I like to attach certificate checks through a template so I can reuse the same logic across many hosts.

At minimum I define macros such as:

  • {$CERT.HOST}
  • {$CERT.PORT}
  • optionally {$CERT.IP} if I need to pin the connection target

Then I create a master item using the key:

web.certificate.get[{$CERT.HOST},{$CERT.PORT},{$CERT.IP}]

If you do not need a fixed IP, omit the third parameter rather than forcing an empty-value convention the team will forget.

Test the endpoint outside Zabbix first

Before I blame Zabbix, I validate the same target manually.

echo | openssl s_client -connect example.com:443 -servername example.com 2>/dev/null | \
openssl x509 -noout -issuer -subject -dates

For a non-standard port:

echo | openssl s_client -connect example.com:8443 -servername example.com 2>/dev/null | \
openssl x509 -noout -dates

If OpenSSL cannot connect or gets the wrong certificate, Zabbix will not magically fix that.

Creating triggers for certificate expiry

I like a two-level threshold:

  • warning at 30 days
  • critical at 7 days

That gives enough time for normal renewal windows while still creating urgency when needed.

Dependent item for days_left

Because web.certificate.get returns JSON, I usually create a dependent numeric item extracting days_left via preprocessing.

Conceptually:

  • master item: raw JSON from web.certificate.get[...]
  • dependent item: JSONPath extracting the days-left field
  • trigger expressions reference the dependent numeric item

The exact JSON field naming can vary slightly by version, so I always inspect one live sample before cloning the template widely.

Trigger examples

A warning trigger once the dependent item holds the numeric days-left value:

last(/Website certificate by Zabbix agent 2/ssl.cert.days_left)<30

A critical trigger:

last(/Website certificate by Zabbix agent 2/ssl.cert.days_left)<7

If you build your own template, the item path will differ. The important operational point is not the exact template name. It is creating numeric dependent items so triggers stay simple and readable.

I also like a trigger for outright retrieval failure, because “certificate check stopped working” deserves attention too.

Web scenarios versus certificate items: I use both for different questions

A certificate-specific item answers “how long is this cert valid and who issued it?” A web scenario answers “can the application still be reached over HTTPS and does it behave like the right site?” Those are related but not identical questions.

On important endpoints I often configure both:

  • a web.certificate.get[...] item for expiry and metadata
  • a web scenario step hitting a known URL, such as /health or /login

That combination catches cases where the certificate is still valid but the HTTPS service is broken, redirected somewhere unexpected, or returning a maintenance page.

Example web scenario target I like

For an application with a health endpoint:

https://app.example.com/health

I set a required status code or content match in the scenario so the check does more than prove the TCP listener answered.

Zabbix proxy placement can change what certificate you see

This matters more than people expect in distributed environments.

If your checks run through a Zabbix proxy in a branch office or partner network, that proxy’s network path may see a different certificate than the main Zabbix server. That is not hypothetical. I have seen split DNS, regional load balancers, and provider edges return different answers depending on where the probe came from.

So when results look inconsistent, I ask:

  • which server or proxy executed the check?
  • what route did it use?
  • what certificate does OpenSSL show from that exact network location?

In bigger estates, documenting which proxy owns which certificate checks saves a lot of confusion later.

Handling CDNs and origin certificates as separate assets

A CDN-backed service often has at least two certificates that matter operationally:

  • the edge certificate users see
  • the origin certificate used between CDN and backend

I almost never treat those as one monitoring item.

Instead I create separate logical checks and name them explicitly. For example:

  • app.example.com edge cert
  • origin-app01.internal backend cert

That naming choice sounds minor, but it prevents half the wrong-triage conversations after an alert fires.

Monitoring certificates on non-standard ports

This comes up constantly for admin panels, internal APIs, proxies, and appliances.

Do not assume everything is on 443.

Examples:

  • example.com:8443
  • mail.example.com:993
  • registry.example.com:5000

For web.certificate.get, specify the port explicitly:

web.certificate.get[registry.example.com,5000]

And validate manually before rollout:

echo | openssl s_client -connect registry.example.com:5000 -servername registry.example.com 2>/dev/null | \
openssl x509 -noout -dates

The certificate path is often different on alternate ports. I have seen 443 look fine while 8443 quietly served a certificate that expired months ago.

Monitoring endpoints that are not on your servers

This is one of Zabbix’s best use cases.

Sometimes the certificate you care about belongs to:

  • a vendor API
  • a SaaS SSO endpoint
  • an external payment gateway callback URL
  • a CDN edge hostname
  • a partner system you depend on

You may not control those systems, but you still care if their certificate breaks your integrations.

Agentless Zabbix checks are perfect here because they measure the remote endpoint from your monitoring vantage point, not from inside the vendor’s infrastructure.

Dealing with self-signed certificates

Self-signed certificates are common on internal devices and legacy admin interfaces.

Zabbix can still inspect them, but you need to be clear about what you want to alert on.

I usually separate the questions:

  • is the certificate present and not expired?
  • is the certificate trusted?

For many internal services, expiry monitoring matters even when full trust validation is intentionally bypassed elsewhere. That does not mean self-signed is ideal. It just means the operational check is still useful.

If self-signed certificates are widespread, I usually document which systems are exceptions and why. Otherwise someone will eventually “fix” the trust model in a way that breaks necessary internal access.

The self-signed certificate practical guide is useful context if your internal estate still depends on them.

Older Zabbix versions: external check or UserParameter

If you are on an older Zabbix release without web.certificate.get, a small script still works well.

Here is a practical script that returns days until expiry using OpenSSL.

#!/bin/bash
set -euo pipefail

HOST="$1"
PORT="${2:-443}"
ENDDATE=$(echo | openssl s_client -connect "${HOST}:${PORT}" -servername "$HOST" 2>/dev/null | \
  openssl x509 -noout -enddate | cut -d= -f2)
END_TS=$(date -d "$ENDDATE" +%s)
NOW_TS=$(date +%s)
echo $(( (END_TS - NOW_TS) / 86400 ))

Save it as /usr/local/bin/check-cert-days.sh and make it executable:

sudo install -m 0755 check-cert-days.sh /usr/local/bin/check-cert-days.sh

Test it:

/usr/local/bin/check-cert-days.sh example.com 443

Then define a Zabbix agent parameter:

UserParameter=ssl.cert.days[*],/usr/local/bin/check-cert-days.sh "$1" "$2"

Restart the agent:

sudo systemctl restart zabbix-agent

And test from the server or a proxy host:

zabbix_get -s 127.0.0.1 -k 'ssl.cert.days[example.com,443]'

I still use this approach occasionally for weird edge cases, even in newer environments.

Alert delivery: email, Slack, and testing before production

An expiry trigger no one sees is not a control. It is a false sense of safety.

I usually route certificate alerts to at least:

  • email for standard operational visibility
  • Slack or another chat tool for team awareness
  • optionally ticketing for critical assets

Test the media and escalation path

Before I trust a new alert, I test:

  1. does the trigger fire?
  2. does the action match the host or template?
  3. does the message reach the right channel?
  4. do acknowledgments and escalations behave correctly?

I have seen monitoring technically “work” for months while alerts were routed to an old email alias nobody read.

Building a certificate dashboard

I like one dashboard dedicated to certificate health instead of burying it across individual hosts.

Typical widgets I include:

  • certificates expiring within 30 days
  • certificates expiring within 7 days
  • certificate check failures
  • top endpoints by soonest expiry
  • last check status for external HTTPS targets

This makes periodic review easy, and it exposes forgotten assets that are technically monitored but never looked at.

Common pitfalls that waste time

Monitoring the wrong hostname

If the endpoint serves different certificates based on SNI, checking only the IP or a generic host can return the wrong certificate.

Always match the hostname users actually access.

Monitoring the CDN instead of the origin, or the origin instead of the CDN

This one causes endless confusion.

If users connect through a CDN, the certificate they see may belong to the CDN edge, not the origin. If your concern is user-facing trust, monitor the edge hostname. If your concern is origin-to-proxy trust, monitor the origin separately.

These are different certificates for different paths.

Only checking port 443

Admin panels, registries, internal apps, and appliances often use alternate ports. Inventory them explicitly.

Forgetting network perspective

A certificate can look fine from a shell on the origin server while external clients hit a different path and see something else entirely.

That is why I like central agentless monitoring from a consistent vantage point.

Not testing actions until the day a cert is near expiry

That is how you discover the alert template still points to last year’s distribution list.

Practical workflow I recommend

When I set up certificate monitoring in Zabbix, I do it in this order:

  1. inventory the hostnames and ports that matter
  2. identify whether I need edge, origin, or both
  3. test the endpoint manually with OpenSSL
  4. add web.certificate.get or the older script-based item
  5. create a numeric dependent item for days left if using JSON
  6. add 30-day and 7-day triggers
  7. wire alert actions to email and chat
  8. force a test condition or use a lab target to verify delivery
  9. build a dashboard for periodic review

That order is boring and repeatable, which is exactly why it works.

Final thoughts

Zabbix is a strong fit for certificate monitoring because it centralizes something that usually fails when it is treated as an afterthought. The built-in web.certificate.get item in newer versions makes the job much cleaner than the old script-only approaches, but the core lessons have not changed: know which hostname and port you are checking, know whether you care about the edge or the origin, and verify the alert path long before production actually needs it.

My default pattern is simple. Warning at 30 days. Critical at 7 days. Manual validation with OpenSSL before rollout. One dashboard for certificate health. And enough operational skepticism to confirm I am monitoring what users really hit, not just what a server owner assumes is in use.

Do that, and certificate expiry stops being a surprise and becomes just another manageable maintenance task.

Scroll to Top