OPC UA Server
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.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.
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
- Writable setpoints
- Dynamic wildcard topics
- Secure production server
- Crash-resilient setpoints
How Data Flows
| Direction | Behavior |
|---|---|
| MQTT → node | Each tag binds to an MQTT topic via SOURCE_TOPIC. A publish on that topic sets the node value. The MQTT payload is authoritative—no scaling or rounding is applied inbound. Retained values present before the route starts are seeded at startup. |
| node → MQTT | When a tag has WITH WRITABLE true, an external client writing the node publishes the new value back to the same SOURCE_TOPIC, retained. FLOAT and DOUBLE values are rounded to DECIMAL_PLACES (default 2) and clamped by MIN_VALUE / MAX_VALUE. Client writes do not loop back into duplicate node updates. |
| Wildcard mappings | Wildcard SOURCE_TOPIC patterns create nodes on first publish. See Dynamic topic handling. |
Server Configuration
All settings live in theOPCUA_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
opc.tcp). Each server route claims its port process-wide—two routes cannot share the same port./coreflux. Default: empty.Objects where tags are organized.Security
true to comply with OPC Foundation specifications, which require secure endpoints. For testing purposes, you might need to set it to false.USE_SECURITY is true:| Value | Policy |
|---|---|
0 | None |
3 | Basic256Sha256 |
4 | Aes128_Sha256_RsaOaep |
5 | Aes256_Sha256_RsaPss |
SECURITY_POLICY ≥ 1 when using Sign or SignAndEncrypt.| Value | Mode |
|---|---|
0 | None |
1 | Sign |
2 | SignAndEncrypt |
USE_SECURITY is true, also offer a None/None endpoint. Use for testing only.Authentication
false for production and configure USERNAME/PASSWORD or ALLOW_CERTIFICATE_AUTH.GET ENV or GET SECRET.GET ENV or GET SECRET.rejected store.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
| Setting | Client (OPCUA) | Server (OPCUA_SERVER) |
|---|---|---|
SOURCE_TOPIC | Topic where read values are published | Bidirectional binding — MQTT sets the node; client writes publish back here |
WRITABLE | Broker may write the remote device | External clients may write the node |
DESTINATION_TOPIC | Write-command topic | Not used on server routes |
ADDRESS | Required remote NodeId | Optional explicit NodeId; default s={ROOT_FOLDER}/{TagName} |
EVERY | Poll interval | Ignored — updates are event-driven from MQTT |
QUERY events | On-demand read/write | Not supported |
SCALING / OFFSET / DEADBAND | Applied to reads from the device | Not applied inbound — MQTT payload is authoritative |
DECIMAL_PLACES / MIN_VALUE / MAX_VALUE | Applied to reads | Applied when a client write is published to MQTT |
STORE TO / triggers | Supported on client routes | Not supported |
Explicit NodeId
By default, a tag namedTemperature 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:SOURCE_TOPIC.FLOAT/DOUBLE values when a client write is published to MQTT.Supported data types
Use the sameDATA_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 anADD TAG. When a topic matches the pattern for the first time, the route creates a new OPC UA node automatically.
How it works
- Define a mapping with a wildcard
SOURCE_TOPIC—for examplesensors/+/valueorplant/#. - Do not add
ADD TAGentries under that mapping. The wildcard pattern alone drives node creation. - On the first publish to a matching topic, the route creates an OPC UA node and sets its value from the payload.
- Later publishes to the same topic update that node. High-frequency updates use an equality check so unchanged values do not spam change notifications.
ADD TAG definitions with WRITABLE true when you need bidirectional control.
Topic → node mapping
The NodeId string identifier mirrors the full MQTT topic path:| MQTT topic (first publish) | OPC UA NodeId |
|---|---|
sensors/press1/value | ns=2;s=sensors/press1/value |
sensors/tank2/value | ns=2;s=sensors/tank2/value |
plant/line3/motor/rpm | ns=2;s=plant/line3/motor/rpm |
Objects, folder nodes are created from topic segments so clients can browse a hierarchy that matches your MQTT layout—for example sensors → press1 → value.
Supported wildcards
| Pattern | Matches | Example |
|---|---|---|
+ (single level) | Exactly one topic segment | sensors/+/value matches sensors/press1/value but not sensors/line1/temp/value |
# (multi-level) | Zero or more segments at the end | plant/# matches plant/line1/temp and plant/line1/motor/rpm |
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 noDATA_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:
| First MQTT payload | Inferred OPC UA type |
|---|---|
Numeric (42, 23.5) | Double |
true or false (case-insensitive) | Boolean |
| Anything else | String |
Example
This mapping exposes everysensors/<device>/value topic as it appears on the bus:
- Route starts; no dynamic nodes exist yet.
- Device publishes
45.2tosensors/press1/value→ nodens=2;s=sensors/press1/valueappears with value45.2(Double). - Device publishes
falsetosensors/alarm1/value→ nodens=2;s=sensors/alarm1/valueappears with valuefalse(Boolean). - UaExpert refresh shows both nodes under the
sensorsfolder. Further publishes update values in place.
Dynamic node limits
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. WithWRITABLE true, a client edit publishes the full list back to the same topic, retained.
"ARRAY" for list values.BOOL, INT16, UINT16, INT32, UINT32, INT64, UINT64, FLOAT, DOUBLE, or STRING.{SOURCE_TOPIC}/{TagName}:
- Publish
[1.5, 2.5, 3.5]tofactory/line1/Levels→ nodens=2;s=PlantServer/Levelsshows 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 inSTRUCT_FIELD_DEFINITIONS.
"STRUCT" for object payloads.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.- Publish
{"bRunning": true, "iRpm": 1450, "sState": "RUN"}tofactory/line1/Motor→ clients seeMotor.bRunning,Motor.iRpm, andMotor.sStateunder the struct node. - With
WRITABLE true, a client writing one field to OPC-UA (for example1600toMotor.iRpm) merges into the current struct and publishes the full object back to MQTTfactory/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
- Anonymous (development)
- Username and encryption
- Certificate user auth
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’strusted 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 underPKI_DIR:
| Folder | Holds | File form |
|---|---|---|
own | This server’s certificate (own/certs) and private key (own/private) | *.der + *.pfx |
issuer | Trusted CA / issuer certificates for chain validation | *.der |
trusted | Trusted client certificates (trusted/certs) | *.der |
rejected | Client certificates seen during a handshake but not yet trusted | *.der |
Where certificates live
WhenPKI_DIR is not set, it defaults to %LocalApplicationData%/OPC Foundation/pki:
| OS | Default path |
|---|---|
| macOS | ~/Library/Application Support/OPC Foundation/pki |
| Linux | $XDG_DATA_HOME/OPC Foundation/pki or ~/.local/share/OPC Foundation/pki |
| Windows | %LOCALAPPDATA%\OPC Foundation\pki |
Inspecting and exporting certificates
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_URIand the certificate SAN in sync. Strict clients refuse the handshake on a mismatch; changingAPPLICATION_URIregenerates the certificate to match. - Clients trust this server by adding
PKI_DIR/own/certs/<name>.derto 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: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:WITH AUTO_ACCEPT_UNTRUSTED_CERTIFICATES true to auto-trust every connecting client certificate.
Supply your own server certificate
The server route has noCLIENT_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:
Rotation and renewal
- Auto-generated certificates — Delete files in
PKI_DIR/own/certsandPKI_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 toBadWaitingForInitialData until the next publish). Set WITH PERSISTENCE true on OPCUA_SERVER_CONFIG to snapshot served values and restore them on startup.
| Node | Owner | Default | On restart |
|---|---|---|---|
WRITABLE true | Client-written setpoint lives only in the server | Persisted | Restored authoritatively—wins over retained MQTT; returns a good value instead of BadWaitingForInitialData |
| Read-only / MQTT-fed | Fed from live MQTT | Not persisted | Recovered from live MQTT (retained message / next publish) |
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 untilplant/line1/LastGoodTempnext publishes.Scratchmapping 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 withWITH PERSISTENCE "true" so the last received value is re-served on cold start until the next live publish overrides it.
TotalEnergy or DailyPeak reads the last published value—a good value rather than BadWaitingForInitialData—until the next publish refreshes it.
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
Deploy the route
Connect with an OPC UA client
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.Confirm MQTT → node
factory/line1/Temperature. Refresh the client and confirm the node value updates.Limitations
Troubleshooting
Route status Red — failed to start server
Route status Red — failed to start server
- 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).
Client rejects the server certificate on every reconnect
Client rejects the server certificate on every reconnect
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 certificate rejected
Client certificate rejected
- Client cert not trusted — Move it from
PKI_DIR/rejected/certstoPKI_DIR/trusted/certs, or setAUTO_ACCEPT_UNTRUSTED_CERTIFICATES truefor testing only - Client cert has no application URI SAN — The connecting client must present a certificate with a valid URI in Subject Alternative Name
Strict client refuses the security handshake
Strict client refuses the security handshake
APPLICATION_URI must match the certificate SAN URI. Leave both at their defaults, or change them together so they stay in sync.Client write has no effect
Client write has no effect
- Confirm the tag has
WITH WRITABLE true. - Subscribe to the tag’s
SOURCE_TOPICand check for the retained publish after the write. - Verify
MIN_VALUE/MAX_VALUEare not clamping the written value away from what you expect.
Anonymous connection rejected
Anonymous connection rejected
ALLOW_ANONYMOUS is false. Connect with the configured username and password, or use certificate authentication if enabled.Node reads BadWaitingForInitialData
Node reads BadWaitingForInitialData
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
Persist PKI in production
Persist PKI in production
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.Disable anonymous access
Disable anonymous access
ALLOW_ANONYMOUS false on plant networks and require username/password or certificate authentication. Anonymous endpoints are appropriate for lab or isolated development only.Reserve ports
Reserve ports
SERVER_PORT. Document which route owns which port to avoid startup conflicts when adding routes.Use secrets for credentials
Use secrets for credentials
GET ENV and GET SECRET over literals in route definitions. Never commit passwords or client credentials to version control.Match security to the client
Match security to the client
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.Enable persistence for client-written setpoints
Enable persistence for client-written setpoints
WITH PERSISTENCE true on OPCUA_SERVER_CONFIG so client-written values survive a broker restart instead of returning BadWaitingForInitialData. See Data persistence & recovery.
