Zabbix Agent 2 OPC UA Plugin: From a Go Script to a Native Plugin

My journey with OPC UA started at the beginning of 2024, somewhat by chance. A friend reached out needing a quick way to monitor certificate expiration on the OPC UA servers across his industrial line.

There was already some history of using Zabbix with industrial automation and OPC UA, including this discussion in the Zabbix community forum from 2009, but at the time I couldn’t find a production-ready, ready-to-use solution that could simply be deployed and configured.

So, to help him out, I put together a lightweight Go program that could be run as a simple Zabbix UserParameter:

import (
    "github.com/gopcua/opcua"
)

func main() {
    var endpoint = flag.String("endpoint", os.Args[1], "OPC UA Endpoint URL")
    flag.Parse()

    eps, _ := opcua.GetEndpoints(context.Background(), *endpoint)
    ep := eps[0]

    x509Cert, _ := x509.ParseCertificate(ep.ServerCertificate)
    now := time.Now()
    days := int(math.Round(x509Cert.NotAfter.Sub(now).Hours() / 24))
    log.Println(days)
}

That was enough to solve the immediate problem, but it also got me interested in what a deeper integration with Zabbix could look like. As I started building custom extensions and getting more familiar with the Zabbix Agent 2 Go SDK, the simple utility evolved into something much bigger.

Today, I’m happy to release a full-featured native zabbix-agent2-plugin-opcua on GitHub.

Native OPC UA Monitoring for Zabbix

Instead of relying on external scripts through UserParameter, the native plugin maintains persistent connections to multiple OPC UA servers, keeping the overhead low. It communicates directly with opc.tcp:// endpoints, handles security policies and reads values directly from OPC UA nodes.

The plugin currently provides four item keys:

  • opcua.ping: Tests whether a connection to the OPC UA server can be established and returns 1 when the connection is alive and the server state is Good, otherwise 0.
  • opcua.info: Fetches server diagnostic metrics and certificate information as JSON. This includes the server state, start time, current session and subscription counts and certificate expiration.
  • opcua.get: Reads the value of a single OPC UA NodeID and returns the raw node value, such as a string, integer, float or boolean.
  • opcua.discovery: Browses an OPC UA node tree and returns Zabbix Low-Level Discovery (LLD) JSON.

Once the plugin is installed, you can test the items locally using the standard Zabbix Agent 2 CLI:

root@zabbix:~# zabbix_agent2 -t 'opcua.get["opc.tcp://192.168.1.50:4840","ns=2;s=Device1.Temperature"]'

The opcua.discovery item key can also be used to bring OPC UA node discovery into Zabbix. Instead of manually creating items for every PLC, sensor or SCADA tag, Zabbix can browse an OPC UA node tree and generate the corresponding items through Low-Level Discovery.

For example:

root@zabbix:~# zabbix_agent2 -t 'opcua.discovery["opc.tcp://192.168.1.50:4840","","","ns=1234;s=DiscoveryRoot"]'

returns ready-to-use JSON LLD data:

[
  {
    "{#NODEID}": "ns=1234;i=5",
    "{#NODENAME}": "Temperature"
  },
  {
    "{#NODEID}": "ns=1234;i=6",
    "{#NODENAME}": "Speed"
  },
  {
    "{#NODEID}": "ns=1234;i=7",
    "{#NODENAME}": "Humidity"
  }
]

Future Improvements

Persistent polling works well for standard monitoring intervals, but OPC UA is designed around event-driven notifications rather than continuous polling.

Constantly querying an industrial controller every few seconds can introduce unnecessary session traffic and CPU load on edge hardware. One area I’d like to improve is the plugin’s data collection model by using the plugin.Watcher interface provided by the Zabbix Go SDK.

At the moment, the Watcher interface cannot be used by loadable or external plugins in Zabbix Agent 2. If that becomes possible in the future, the plugin could move towards a more event-driven architecture:

  • Native OPC UA Subscriptions: The plugin could establish long-lived sessions and register monitored items directly on the OPC UA server.
  • Push-Based Telemetry: Instead of polling on a schedule, the plugin could receive updates only when a value changes and push them directly into Zabbix via Watcher.Watch().
  • Reduced Bandwidth: Using server-side deadbands and subscription queues could reduce traffic on bandwidth-constrained OT networks.

These are not implemented yet, but they are the direction I’d like to explore as the Zabbix Agent 2 plugin architecture evolves.

Try it out

The plugin is open-source under the Apache 2.0 license and is published on GitHub.

You can compile it directly using the Go toolchain. Pre-built .deb and .rpm packages for Debian, SUSE and Red Hat systems, along with setup instructions and ready-to-use Zabbix templates, are available in the official documentation.