How Do I Script Ignition SSL Certificate Expiry Alerts?

Daniel Price6 min read
HMI / SCADAOther ManufacturerTechnical Reference
Licensed PE Working through this on a live machine? A Maine-licensed engineer can take it from here — included with IMD hardware, by the hour for everything else. Book an engineer

Where should the expiry check run, and what path does it take?

Run it from one central Ignition gateway. That gateway opens a TLS connection to each gateway's HTTPS listener, reads the certificate chain from the handshake, and computes the days left from each certificate's notAfter date. You don't need an agent on the target servers or file access to their keystores. The probe also reports the certificate the listener actually serves, so it catches a renewal that was imported but never picked up.

Follow the packet:

Hop Sender Receiver What must pass
1 Gateway timer/scheduled script Project library function Gateway scope, so system.net.sendEmail and SMTP profiles are available
2 Monitoring gateway Target gateway HTTPS port TCP connect through every firewall and VLAN boundary
3 TLS client (Java) Target TLS listener Handshake completes and the server sends its certificate chain
4 Monitoring gateway SMTP server Mail relay accepts the gateway as sender

If a reverse proxy or load balancer sits in front of a gateway, the handshake terminates there, and the probe reads the proxy's certificate. Probe the address your users and clients actually hit.

Can the monitoring gateway reach every HTTPS listener?

Check layer one first. Read the SSL port for each target from its Web Server settings. Don't assume every site uses the same port. Then test raw TCP from the monitoring gateway's host, not from your laptop:

Test-NetConnection gw-line1.example.local -Port <ssl_port>
openssl s_client -connect gw-line1.example.local:<ssl_port> -servername gw-line1.example.local </dev/null | openssl x509 -noout -subject -enddate
Result Meaning Action
TCP fails Routing, firewall, or wrong port Open the path from the monitoring host only
TCP succeeds, handshake fails Port is HTTP-only, or TLS is disabled on that gateway Confirm SSL is enabled in the Web Server section
notAfter= printed Path is good Record the date as the reference for the script check

Check: you have an openssl end date recorded for every gateway in the inventory before you write any script.

How does a gateway script read the certificate's notAfter date?

Ignition scripting is Jython, so the full Java TLS stack is available. The function below uses a trust-all manager on purpose. A monitor has to read certificates that are self-signed, issued by an internal CA, or already expired. A validating client aborts the handshake on exactly those certificates, before you can read the date. The monitor never sends data over this connection. It only inspects the chain.

Wrap an already-connected plain socket with createSocket(raw, host, port, True). That gives you a connect timeout and also sends the hostname as SNI, which proxies need in order to pick the right certificate. Put this in a project library script, for example certcheck:

from java.net import Socket, InetSocketAddress
from javax.net.ssl import SSLContext, X509TrustManager, TrustManager
from java.security import SecureRandom
from jarray import array

class _TrustAll(X509TrustManager):
    def checkClientTrusted(self, chain, authType): pass
    def checkServerTrusted(self, chain, authType): pass
    def getAcceptedIssuers(self): return None

def getCertExpiry(host, port, timeoutMs=5000):
    ctx = SSLContext.getInstance('TLS')
    ctx.init(None, array([_TrustAll()], TrustManager), SecureRandom())
    raw = Socket()
    raw.connect(InetSocketAddress(host, port), timeoutMs)
    raw.setSoTimeout(timeoutMs)
    sock = ctx.getSocketFactory().createSocket(raw, host, port, True)
    try:
        sock.startHandshake()
        now = system.date.now().getTime()
        result = []
        for c in sock.getSession().getPeerCertificates():
            result.append({
                'subject': c.getSubjectX500Principal().getName(),
                'serial': c.getSerialNumber().toString(16),
                'notAfter': c.getNotAfter(),
                'daysLeft': round((c.getNotAfter().getTime() - now) / 86400000.0, 1)})
        return result
    finally:
        sock.close()

The function returns the whole chain, not just the leaf certificate. An intermediate CA certificate can expire before the server certificate, and clients then reject the chain even though the leaf is still valid. Alert on the minimum daysLeft across the chain.

Check: run certcheck.getCertExpiry('gw-line1.example.local', port) in the Designer script console. The first entry's notAfter must match the openssl end date you recorded.

How do I schedule the check and send the email?

Create a gateway timer script or a scheduled gateway event script in the project that holds the library. The script must run in gateway scope. The SMTP profile is configured on the gateway, and a client-scope call can't use it. Certificate dates move in days, so a daily run is enough.


State Trigger (site choice) Why it matters
WARNING daysLeft at or below 30 Leaves time for CSR, CA turnaround, and a change window
CRITICAL daysLeft at or below 7 Escalate before browsers and clients start rejecting the gateway
UNREACHABLE Connect or handshake exception Don't treat a monitoring gap as "no expiring certificates"

If you already use alarm notification pipelines and on-call rosters, write each gateway's worst daysLeft to a memory tag instead of sending email directly, and alarm on that tag.

Check: set to a value larger than every certificate's remaining life and run the script once. Every target must appear in the email. Then restore the threshold.

Why does the CA reject a renewal CSR from the Web Server page?

A CSR generated from the gateway's Web Server section is signed with the private key already in the keystore. Many CAs refuse a renewal that reuses the previous key, so the request is rejected even though it is well-formed. To get a fresh key on the gateway, you remove the existing certificate and create a new one there. That leaves HTTPS without a valid certificate until the signed replacement is imported. Schedule that as a change window, or avoid it by generating the key off-box:

  1. Generate a new key and CSR on a workstation: openssl req -new -newkey rsa:2048 -nodes -keyout gw.key -out gw.csr. Include the gateway FQDN as the subject and in the SAN list your CA requires.
  2. Submit the CSR and collect the signed certificate plus the intermediate chain.
  3. Bundle the key, certificate, and chain: openssl pkcs12 -export -inkey gw.key -in gw.crt -certfile chain.crt -out gw.p12.
  4. Install the new keystore on the gateway. From Ignition 8.0.3, the gateway hot-reloads its SSL keystore, so the swap does not require a restart.
  5. Re-run certcheck.getCertExpiry against the gateway. The serial and notAfter values must both change.

Keystore hot-reload is also what makes automatic renewal practical. An ACME client such as Let's Encrypt renews on its own schedule and writes a new keystore, and the gateway picks it up live. Inductive Automation publishes a Let's Encrypt guide for Ignition that covers the keystore conversion steps. On a large fleet, the expiry monitor then serves as the backstop that catches a failed automated renewal.

How do I prove the monitor works end to end?

  1. Confirm the script's notAfter for every gateway matches the openssl s_client end date taken from the monitoring host.
  2. Block one target port temporarily, or point one entry at an unused port. The next run must email an UNREACHABLE line for that target.
  3. Point one entry at a test listener that serves a short-lived self-signed certificate. The run must report CRITICAL for it, which proves the trust-all handshake reads certificates a validating client would reject.
  4. Renew one production gateway with a new key. Confirm the next scheduled run shows the new serial, the new notAfter, and no alert line for that host. Also confirm a browser session to the gateway shows the same expiry date.

FAQ

How do I get the SSL certificate expiry date from an Ignition gateway with a script?

Open a TLS socket from a gateway-scope Jython script with SSLContext and a trust-all X509TrustManager. Call startHandshake(), then read getNotAfter() from each certificate returned by getSession().getPeerCertificates().

How do I email an alert before an Ignition SSL certificate expires?

Run the check from a daily gateway timer or scheduled script, compare the minimum daysLeft in each chain against your warning threshold, and call system.net.sendEmail with a gateway SMTP profile. Also report connection failures, so a monitoring gap doesn't look like a healthy fleet.

How do I generate a new CSR when the CA rejects the one from the Ignition Web Server page?

The gateway-generated CSR reuses the existing private key, and many CAs reject key reuse. Generate a new key and CSR externally with openssl req -newkey, then bundle the signed certificate, chain, and new key into a PKCS#12 keystore for the gateway.

Can Ignition renew SSL certificates automatically with Let's Encrypt?

Yes. From Ignition 8.0.3 the gateway hot-reloads its SSL keystore, so an ACME client can drop in a renewed keystore without a restart. Keep the expiry script running anyway to catch renewals that fail silently.

Back to blog