Skip to main content

OPC UA Server

This page documents the server route (OPCUA_SERVER)—the broker hosts an OPC UA server and exposes MQTT topics as nodes. External clients connect to the broker. To connect the broker to a remote OPC UA device instead, see OPC UA Client.
The OPCUA_SERVER route makes your broker host an OPC UA server. External clients—SCADA, UaExpert, PLCs—connect to the broker and see your MQTT topics as OPC UA nodes. Use OPCUA when you need to read from a device. Use OPCUA_SERVER when OT systems need to browse and write values that live on your MQTT bus.
Like turning your broker into a shop window. MQTT messages update what’s on display (node values), and customers (OPC UA clients) can look—and, for writable tags, change items that publish back to MQTT.

When to Use This Route

  • SCADA integration — Let a SCADA or HMI browse broker data without custom drivers.
  • OT/IT bridge — Expose IoT sensor streams to plant-floor tools that speak OPC UA.
  • Bidirectional control — Writable tags let clients adjust setpoints; the broker publishes the new value to MQTT.
  • Dynamic discovery — Wildcard topic mappings create nodes automatically as matching topics first publish.

The sections below cover data flow, configuration, persistence, and production patterns.

Quick Start

Map MQTT topics to fixed OPC UA nodes. Each ADD TAG creates one node; publishing to {SOURCE_TOPIC}/{TagName} updates it.
  • Publish 23.5 to factory/line1/Temperature → node ns=2;s=PlantServer/Temperature reads 23.5.
  • Publish 1042 to factory/line1/Counter → node ns=2;s=PlantServer/Counter reads 1042.

How Data Flows


Server Configuration

All settings live in the OPCUA_SERVER_CONFIG block. Store credentials with GET ENV or GET SECRET. The example below shows a production-oriented config: custom identity, encrypted endpoint, username/password auth, and a persistent certificate store. Clients connect at opc.tcp://<broker-host>:4840/coreflux.

Connection and identity

integer
default:"4840"
TCP port the server listens on (opc.tcp). Each server route claims its port process-wide—two routes cannot share the same port.
string
Optional path appended to the base address, for example /coreflux. Default: empty.
string
default:"CorefluxBroker_OpcUaServer"
Application display name and certificate common name (CN).
string
default:"urn:{host}:Coreflux:{route}"
Application URI. Must match the certificate SAN URI. Changing this regenerates the certificate.
string
default:"urn:coreflux:opcua:{route}"
Namespace URI for exposed nodes.
string
default:"route name"
Root folder under Objects where tags are organized.
string
Directory for own, issuer, trusted, and rejected certificate stores. Defaults to the OPC Foundation PKI layout. In Docker, mount this path as a volume so certificates persist across restarts. See Certificate Management.

Security

boolean
default:"true"
Offer a signed or encrypted endpoint.
This defaults to true to comply with OPC Foundation specifications, which require secure endpoints. For testing purposes, you might need to set it to false.
integer
default:"0"
Security policy offered when USE_SECURITY is true:
integer
default:"0"
Message security mode. Requires SECURITY_POLICY ≥ 1 when using Sign or SignAndEncrypt.
boolean
default:"false"
When USE_SECURITY is true, also offer a None/None endpoint. Use for testing only.

Authentication

boolean
default:"true"
Offer anonymous login. Set to false for production and configure USERNAME/PASSWORD or ALLOW_CERTIFICATE_AUTH.
string
Username for password-based login. Use GET ENV or GET SECRET.
string
Password for password-based login. Use GET ENV or GET SECRET.
boolean
default:"false"
Offer X.509 certificate user authentication.
boolean
default:"false"
Automatically trust connecting client certificates. When false, untrusted client certs are placed in the rejected store.
boolean
default:"false"
Persist served node values across broker restarts. See Data persistence & recovery.
An application instance certificate is auto-generated (self-signed) on first start under PKI_DIR/own. If you change APPLICATION_URI, the certificate is regenerated to match.

Tag and Mapping Configuration

Tags reuse the same vocabulary as the OPC UA client route, but several settings carry different meaning on the server side.

Server vs. client semantics

On server routes, route status Connected means the endpoint is listening—not that a remote session is established.

Explicit NodeId

By default, a tag named Temperature under route PlantServer with ROOT_FOLDER defaulting to the route name maps to ns=2;s=PlantServer/Temperature. Override with ADDRESS:

Writable tag parameters

For writable tags, configure outbound formatting the same way as on the client route:
boolean
default:"false"
Allow OPC UA clients to write this node. Writes publish to the tag’s SOURCE_TOPIC.
integer
default:"2"
Decimal places applied to FLOAT/DOUBLE values when a client write is published to MQTT.
double
Minimum value after a client write. Values below this are clamped.
double
Maximum value after a client write. Values above this are clamped.
boolean
Per-tag override of the route persistence default. Precedence: tag → mapping → route. See Data persistence & recovery.

Supported data types

Use the same DATA_TYPE values as the OPC UA client route—for example BOOLEAN, INT32, FLOAT, DOUBLE, and STRING. Tags can also expose one-dimensional arrays or struct objects—see Array and struct nodes. For statically defined scalar tags, set DATA_TYPE explicitly. Dynamic nodes infer type from the first MQTT payload (see below).

Dynamic Topic Handling

Wildcard mappings let you expose many MQTT topics without listing each one as an ADD TAG. When a topic matches the pattern for the first time, the route creates a new OPC UA node automatically.

How it works

  1. Define a mapping with a wildcard SOURCE_TOPIC—for example sensors/+/value or plant/#.
  2. Do not add ADD TAG entries under that mapping. The wildcard pattern alone drives node creation.
  3. On the first publish to a matching topic, the route creates an OPC UA node and sets its value from the payload.
  4. Later publishes to the same topic update that node. High-frequency updates use an equality check so unchanged values do not spam change notifications.
Dynamic nodes are read-only. OPC UA clients cannot write them back to MQTT. Use static ADD TAG definitions with WRITABLE true when you need bidirectional control.

Topic → node mapping

The NodeId string identifier mirrors the full MQTT topic path: Under Objects, folder nodes are created from topic segments so clients can browse a hierarchy that matches your MQTT layout—for example sensorspress1value.

Supported wildcards

Combine static and dynamic mappings in the same route. Static ADD TAG entries under a non-wildcard SOURCE_TOPIC behave as fixed nodes; wildcard mappings handle everything else that matches.

Type inference

Dynamic nodes have no DATA_TYPE in the route definition. The broker infers the OPC UA type from the first payload and locks it for the lifetime of that node: If a later publish cannot be converted to the pinned type, the node keeps its previous value and the broker logs an error. Publish a representative value first—do not send a placeholder string if you expect numeric updates later.

Example

This mapping exposes every sensors/<device>/value topic as it appears on the bus:
Sequence:
  1. Route starts; no dynamic nodes exist yet.
  2. Device publishes 45.2 to sensors/press1/value → node ns=2;s=sensors/press1/value appears with value 45.2 (Double).
  3. Device publishes false to sensors/alarm1/value → node ns=2;s=sensors/alarm1/value appears with value false (Boolean).
  4. UaExpert refresh shows both nodes under the sensors folder. Further publishes update values in place.
Retained messages already on the broker when the route starts are seeded the same way as for static tags—matching topics create nodes at startup.

Dynamic node limits

Dynamic nodes are not removed while the route is running. They persist until the route is removed or the broker restarts. Plan wildcard patterns carefully in high-churn environments where topic names change frequently.

Array & Struct Nodes

Scalar tags expose one value per node. Use an array tag when MQTT carries a list of values on one topic, or a struct tag when the payload is a JSON object with named fields. OPC UA clients then browse and write the same shape—no extra config block beyond the tag settings below.

Array tags

An array tag turns a JSON list on MQTT into a single OPC UA array node. Publish a list; the client sees one node with multiple elements. With WRITABLE true, a client edit publishes the full list back to the same topic, retained.
string
Set to "ARRAY" for list values.
string
default:"INT16"
Type of each element: BOOL, INT16, UINT16, INT32, UINT32, INT64, UINT64, FLOAT, DOUBLE, or STRING.
integer
Number of elements in the array. Set this to match your payload length. If omitted, the dimension is unspecified.
Define the tag, then publish a JSON array to {SOURCE_TOPIC}/{TagName}:
What happens:
  • Publish [1.5, 2.5, 3.5] to factory/line1/Levels → node ns=2;s=PlantServer/Levels shows three values.
  • Payloads wrapped as {"value": [1.5, 2.5, 3.5]} are accepted—the broker unwraps the list automatically.
  • A client write to the array node publishes the updated list back to factory/line1/Levels.

Struct tags

A struct tag maps a JSON object to a folder-like node with one child per field. Field names in MQTT must match the names you declare in STRUCT_FIELD_DEFINITIONS.
string
Set to "STRUCT" for object payloads.
string
Comma-separated field list in name:type form—for example bRunning:BOOL,iRpm:INT,sState:STRING. If you reuse a definition from an ADS or EtherNet/IP route (name:offset:type:size), the server uses the name and type only; offset and size are ignored.
What happens:
  • Publish {"bRunning": true, "iRpm": 1450, "sState": "RUN"} to factory/line1/Motor → clients see Motor.bRunning, Motor.iRpm, and Motor.sState under the struct node.
  • With WRITABLE true, a client writing one field to OPC-UA (for example 1600 to Motor.iRpm) merges into the current struct and publishes the full object back to MQTT factory/line1/Motor—for example {"bRunning": true, "iRpm": 1600, "sState": "RUN"}.
  • Fields that were never set by MQTT and were not written by the client are left out of that publish.
DECIMAL_PLACES, MIN_VALUE, and MAX_VALUE apply to scalar writable tags only—not to individual struct fields.

Security Examples

For lab or trusted networks where encryption is not required:

Certificate Management

A secured OPC UA server connection requires two certificates that trust each other: the broker’s application instance certificate (identifies and secures the server) and each client’s certificate (must be in the server’s trusted store when using certificate authentication). For client-side trust workflows when the broker dials out to remote servers, see OPC UA Client.

PKI directory layout

Both client and server routes store certificates in four folders under PKI_DIR:

Where certificates live

When PKI_DIR is not set, it defaults to %LocalApplicationData%/OPC Foundation/pki: Relocate the store to a path you control and mount it as a volume in Docker:
In Docker, the default PKI location is ephemeral—the broker regenerates its certificate on every restart and clients that trusted the previous one will reject the new one. Always set PKI_DIR to a mounted volume.

Inspecting and exporting certificates

Give clients the public certificate (own/certs/*.der). The private key never leaves the broker.

The server’s own certificate

On first start the broker auto-generates a self-signed server certificate (CN={APPLICATION_NAME}, O=Coreflux; default APPLICATION_NAME is CorefluxBroker_OpcUaServer) in PKI_DIR/own. Its SAN URI matches APPLICATION_URI (default urn:{host}:Coreflux:{route}):
  • Keep APPLICATION_URI and the certificate SAN in sync. Strict clients refuse the handshake on a mismatch; changing APPLICATION_URI regenerates the certificate to match.
  • Clients trust this server by adding PKI_DIR/own/certs/<name>.der to their trusted list—the same workflow as trusting a server certificate on the client route.

Enable certificate user authentication

Offer the X.509 user token so clients authenticate with a certificate instead of—or alongside—username and password:
When ALLOW_CERTIFICATE_AUTH true, the application instance certificate doubles as the user identity—the client’s certificate must be trusted and must carry a valid application URI SAN.

Trust connecting client certificates

When a client connects with certificate authentication, its certificate must be in the server’s trusted store:
1

First connect

The client’s certificate lands in PKI_DIR/rejected/certs.
2

Move to trusted

3

Reconnect

The client connects successfully.
For testing only, set WITH AUTO_ACCEPT_UNTRUSTED_CERTIFICATES true to auto-trust every connecting client certificate.

Supply your own server certificate

The server route has no CLIENT_CERT_PATH-style parameter. To run with a CA-issued certificate instead of the auto-generated one, place it in the own store before first start, with a subject matching CN={APPLICATION_NAME}, O=Coreflux and a SAN URI matching APPLICATION_URI:
If a valid matching certificate is already present, the broker uses it instead of generating one.

Rotation and renewal

  • Auto-generated certificates — Delete files in PKI_DIR/own/certs and PKI_DIR/own/private, then restart the route; the broker regenerates.
  • Changing APPLICATION_URI — Regenerates the certificate; re-trust it on every client.
  • Plan rotation before expiry—an expired application instance certificate fails the handshake on both ends.

Data Persistence & Recovery

By default a server route holds node values in memory only—a broker restart resets them (nodes return to BadWaitingForInitialData until the next publish). Set WITH PERSISTENCE true on OPCUA_SERVER_CONFIG to snapshot served values and restore them on startup.
The same ownership-aware policy as the Modbus TCP Server route applies: Client-written setpoints survive a restart; live telemetry always reflects the current MQTT source.

Per-tag and mapping overrides

WITH PERSISTENCE true|false on an individual ADD TAG—or a whole ADD MAPPING—overrides the route default:
  • Setpoint — restored authoritatively to the last client-written value.
  • LastGoodTemp — served from snapshot until plant/line1/LastGoodTemp next publishes.
  • Scratch mapping tags — never snapshotted; start cold on every restart.

Caching slow or infrequently-updated sources

MQTT-fed nodes are not persisted by default. When a source updates infrequently—an hourly energy total, a shift-latched value—force-persist with WITH PERSISTENCE "true" so the last received value is re-served on cold start until the next live publish overrides it.
After a restart, a client browsing TotalEnergy or DailyPeak reads the last published value—a good value rather than BadWaitingForInitialData—until the next publish refreshes it.
Persistence applies only to explicitly-declared ADD TAG nodes. Wildcard/dynamic nodes created from MQTT topic patterns are not snapshotted—a slow source behind a wildcard mapping still starts cold.

Storage & durability

  • Values are written through a non-blocking background worker on every change (rapid updates coalesce to the latest value).
  • The snapshot lives in an encrypted-at-rest database under the broker data directory.
  • A snapshot is keyed to the route’s tag definitions by a fingerprint. If addresses or types change between runs, the stale snapshot is discarded rather than replayed into reshaped nodes.

Verify Your Server

1

Deploy the route

Add the route definition to your broker and confirm route status shows Connected (the endpoint is listening).
2

Connect with an OPC UA client

Use UaExpert or any OPC UA browser. Connect to opc.tcp://<broker-host>:<SERVER_PORT> (append ENDPOINT_PATH if configured). When using security, trust the server certificate—export it from PKI_DIR/own/certs or follow the trust workflow in Certificate Management.
3

Confirm MQTT → node

Publish a test value to a bound topic—for example factory/line1/Temperature. Refresh the client and confirm the node value updates.
4

Confirm node → MQTT

For a writable tag, write a value from the OPC UA client. Subscribe to the tag’s SOURCE_TOPIC with MQTT Explorer or any MQTT client and confirm the retained publish arrives.

Limitations

Each OPCUA_SERVER route binds exclusively to its SERVER_PORT. A second route on the same port fails to start and reports a red route status.

Troubleshooting

  • Port in use — Another process or server route already uses SERVER_PORT. Choose a different port or stop the conflicting service.
  • PKI directory not writable — Ensure the broker can write to PKI_DIR (or the default PKI location).
The application instance certificate regenerates when PKI_DIR is ephemeral—for example, an unmounted Docker volume. Mount PKI_DIR as a persistent volume so client trust stores remain valid across restarts.
  • Client cert not trusted — Move it from PKI_DIR/rejected/certs to PKI_DIR/trusted/certs, or set AUTO_ACCEPT_UNTRUSTED_CERTIFICATES true for testing only
  • Client cert has no application URI SAN — The connecting client must present a certificate with a valid URI in Subject Alternative Name
APPLICATION_URI must match the certificate SAN URI. Leave both at their defaults, or change them together so they stay in sync.
  • Confirm the tag has WITH WRITABLE true.
  • Subscribe to the tag’s SOURCE_TOPIC and check for the retained publish after the write.
  • Verify MIN_VALUE / MAX_VALUE are not clamping the written value away from what you expect.
ALLOW_ANONYMOUS is false. Connect with the configured username and password, or use certificate authentication if enabled.
The SOURCE_TOPIC has not published yet (or no retained value). Publish the topic, enable route persistence for client-written setpoints, or force-persist a slow MQTT-fed source with WITH PERSISTENCE "true". See Data persistence & recovery.

Best Practices

Mount PKI_DIR as a persistent volume so server certificates survive broker restarts. Ephemeral storage regenerates the application instance certificate on every boot and breaks client trust stores. See Certificate Management.
Set ALLOW_ANONYMOUS false on plant networks and require username/password or certificate authentication. Anonymous endpoints are appropriate for lab or isolated development only.
Each server route binds exclusively to its SERVER_PORT. Document which route owns which port to avoid startup conflicts when adding routes.
Prefer GET ENV and GET SECRET over literals in route definitions. Never commit passwords or client credentials to version control.
Align SECURITY_POLICY and MESSAGE_SECURITY with what your SCADA or HMI supports. If the client cannot negotiate encryption, enable ALLOW_NO_SECURITY for testing only—not in production.
Set WITH PERSISTENCE true on OPCUA_SERVER_CONFIG so client-written values survive a broker restart instead of returning BadWaitingForInitialData. See Data persistence & recovery.

Next Steps

OPC UA Client Route

Connect to remote OPC UA servers and poll device data into MQTT.

Industrial Routes Overview

Compare protocols and choose the right industrial route for your architecture.
Last modified on July 3, 2026