TL;DR — Reverse Connection in OPC UA
Primary spec: OPC 10000-6 (Mappings) §7.1.2.6 and §7.1.3 — the
ReverseHelloMessage and Server-initiated connections.Related: OPC 10000-12 (Discovery / GDS) §4.4 — discovery for reverse connections · OPC 10000-4 (Services) — SecureChannel and Session.
The Use Case — Why Would a Server Call the Client?
In classic OPC UA the Client connects to the Server: the Server listens on a TCP port (default 4840) and the Client dials in.
That model breaks the moment the Server lives behind a firewall or NAT with no inbound ports open — which is the normal situation for equipment on a factory floor, in a substation, on a wind turbine, or inside a customer's plant.
flowchart LR
subgraph plant["🏭 OT Network (plant floor)"]
S["OPC UA Server<br/>on a machine/PLC"]
end
subgraph fw["🧱 Firewall / NAT<br/>(no inbound ports)"]
end
subgraph it["🏢 IT / Cloud (SCADA, MES, historian)"]
C["OPC UA Client"]
end
C -. "❌ inbound blocked" .-> fw
fw -. "❌" .-> S
style S fill:#2d6a4f,color:#fff
style C fill:#1d3557,color:#fff
style fw fill:#6a040f,color:#fff
Typical scenarios:
| Scenario | Who is where |
|---|---|
| Cloud/IT collecting from OT | Client in the datacenter; hundreds of Servers on isolated plant networks |
| Remote machine monitoring | Machine-builder's Client at HQ; Servers deployed at customer sites |
| DMZ crossing | Client in the enterprise zone; Servers in the tightly firewalled control zone |
| Cellular / edge devices | Devices on carrier-grade NAT with no routable public address |
The security policy on these networks is almost always the same: "nothing initiates a connection into the OT network." Opening an inbound port on the plant firewall is exactly what security teams refuse to do.
The Problem It Solves
Reverse Connection inverts who dials the phone so the TCP connection direction is decoupled from the OPC UA Client/Server roles.
- The Server opens the outbound TCP socket to the Client — outbound is what firewalls allow.
- Once the socket is up, the roles stay normal: the Client is still the Client, the Server is still the Server. Only the transport was initiated by the Server.
Per OPC 10000-6 §7.1.2.6: "the ReverseHello Message allows Servers behind firewalls with no open ports to connect to a Client and request that the Client establish a SecureChannel using the socket created by the Server."
flowchart LR
subgraph plant["🏭 OT Network"]
S["OPC UA Server"]
end
subgraph fw["🧱 Firewall / NAT"]
end
subgraph it["🏢 IT / Cloud"]
C["OPC UA Client<br/>(listens on a ClientUrl)"]
end
S -->|"① outbound TCP<br/>(allowed)"| fw --> C
C -->|"② SecureChannel + Session<br/>over the SAME socket"| S
style S fill:#2d6a4f,color:#fff
style C fill:#1d3557,color:#fff
style fw fill:#e09f3e,color:#000
Key insight: the Client now needs a reachable address — a ClientUrl, defined in OPC 10000-12 §3.1.4 as "a physical address available on a network that allows Servers to initiate a reverse connection". The many firewalled Servers do not — which is exactly the topology you want when one Client aggregates many Servers.
The ReverseHello Message
OPC UA's transport (UACP, the OPC UA Connection Protocol) defines four message types in the header (OPC 10000-6 §7.1.2.2): HEL, ACK, ERR, and RHE (ReverseHello).
The ReverseHello carries just two fields (Table 77):
| Field | Type | Meaning |
|---|---|---|
| ServerUri | String | The ApplicationUri of the Server sending the message (≤ 4096 bytes). Lets the Client decide whether it wants this Server. |
| EndpointUrl | String | The Endpoint URL the Client must echo back in its Hello. Should be one of the URLs from GetEndpoints; used to look up configuration. |
The Handshake — Forward vs Reverse
In a normal connection the Client sends Hello first. In a reverse connection the Server sends ReverseHello first, then the handshake proceeds identically to a normal one (OPC 10000-6 §7.1.3).
sequenceDiagram
participant S as 🏭 Server (behind NAT)
participant C as 🏢 Client (reachable ClientUrl)
Note over S,C: Server was configured by a SecurityAdmin<br/>with the Client's ClientUrl
S->>C: ① open outbound TCP socket
S->>C: ② ReverseHello (ServerUri, EndpointUrl) [RHE]
Note over C: Client checks ServerUri.<br/>If unacceptable, close the socket.<br/>If too busy, Error Bad_ServerTooBusy.
C->>S: ③ Hello (buffer sizes, echoes EndpointUrl) [HEL]
S->>C: ④ Acknowledge (buffer negotiation) [ACK]
C->>S: ⑤ OpenSecureChannel Request
S->>C: ⑥ OpenSecureChannel Response
Note over S,C: SecureChannel established, Server trust verified
C->>S: ⑦ CreateSession / ActivateSession
Note over S,C: Normal OPC UA from here — read, write, subscribe
Once the Server sends
ReverseHello, the Client drives everything else. Buffer negotiation, SecureChannel, and Session are exactly as in a forward connection — the reverse part is only the socket setup and the first message.
Connection Lifecycle and Keep-Alive Behaviour
The spec is prescriptive about robustness in the Server-initiated flow (OPC 10000-6 §7.1.3, Table 78). Note that the socket is outbound — the Server dials the Client, it does not listen:
- The Server keeps at least one warm outbound spare — "Servers shall maintain at least one open socket without an active Session with each Client it is configured to connect to." This is a socket the Server already dialled (possibly with a SecureChannel already open) but with no Session yet, so the Client can
CreateSessioninstantly instead of waiting for a fresh reverse handshake. - Once a SecureChannel forms on that spare, the Server shall dial a new outbound spare so one is always idle and ready.
- Timeouts: the side accepting the incoming socket — the Client here — must close any socket that does not receive a
Hello/ReverseHellowithin a configurable timeout (default ≤ 2 minutes). - Back-off: if the Client returns an
Error, the Server logs it and re-dials after a configured delay. Clients should not be hammered.
stateDiagram-v2
[*] --> Connecting: Server opens outbound socket
Connecting --> Idle: ReverseHello sent, socket kept spare
Idle --> Active: Client establishes SecureChannel/Session
Active --> Idle: Server opens a NEW spare socket
Idle --> Connecting: socket closed / no Hello (timeout <= 2 min)
Active --> Connecting: SecureChannel closed, reconnect after delay
Connecting --> Connecting: Client Error (e.g. ServerTooBusy), back off and retry
Discovery — How Does the Server Learn the ClientUrl?
Reverse connect cannot use the normal discovery flow, because Clients do not register with a Local Discovery Server (OPC 10000-12 §4.4). Two options exist:
| Method | How | Reference |
|---|---|---|
| Out-of-band | A SecurityAdmin types the ClientUrl into the Server's configuration (GUI or file) | OPC 10000-12 §4.4.2 |
| Global Discovery (GDS) | SecurityAdmin calls QueryApplications on a GDS with the "RCP" ServerCapabilityFilter; ClientUrls come back in DiscoveryUrls prefixed with rcp+ | OPC 10000-12 §4.4.3 |
The
rcp+prefix on a DiscoveryUrl (e.g.opc.rcp+tcp://client.example.com:4841) marks it as a reverse endpoint; URLs without the prefix are for forward connections.
Either way, reverse connect is administrator-provisioned, not auto-discovered: someone must configure which Servers dial which Clients. See also Part 12 — Global Discovery Server.
What Reverse Connect Does Not Change
Everything above the transport is untouched:
- Security is identical — same SecureChannel, same certificate exchange, same trust checks, same SecurityPolicies. The Client still verifies that it trusts the Server's certificate.
- Client and Server roles are unchanged — the Server serves data; the Client browses, reads, writes, and subscribes.
- The application layer is unchanged — Sessions, Subscriptions, and Services all behave normally.
Certificate implications — no separate certificate needed
A common question: does a reverse-connect Server (or Client) need a certificate different from its conventional Application Instance Certificate?
No. The spec reuses the ordinary Application Instance Certificate on both ends. ReverseHello only opens the socket; the moment real security begins — OpenSecureChannel — the Client is still the initiator, exactly as in forward mode, and each side presents the same certificate it always uses (OPC 10000-6 §7.1.3; validation per OPC 10000-4 Table 100).
sequenceDiagram
participant S as 🏭 Server
participant C as 🏢 Client
S->>C: ReverseHello(ServerUri, EndpointUrl)
Note over C: ⚠️ ServerUri is PLAINTEXT and UNAUTHENTICATED<br/>— only a cheap early filter (DoS hardening)
C->>S: Hello / Acknowledge
C->>S: OpenSecureChannel Request (Client Application Instance Cert)
S->>C: OpenSecureChannel Response (Server Application Instance Cert)
Note over S,C: 🔐 REAL authentication here:<br/>Server proves possession of its private key.<br/>Same cert as forward connect — the root of trust.
Why the
ServerUrifilter is not enough: per OPC 10000-2 §6.14, reverse connect is "an additional security concern for the Client… the Client needs to validate that the connection is from an appropriate Server and not a denial of service attack." TheServerUriinReverseHellocarries no signature — the Client only truly authenticates the Server via certificate validation duringOpenSecureChannel.
What the same certificate must still satisfy — all met by the normal certificate, nothing new to issue:
| Check (OPC 10000-4 Table 100) | Applies to | Reverse-connect implication |
|---|---|---|
URI (Bad_CertificateUriInvalid, not suppressible) | Both app certs | The Server cert's ApplicationUri must equal the ServerUri it advertises in ReverseHello; the Client cert URI must match its clientDescription at CreateSession |
HostName (Bad_CertificateHostNameInvalid, suppressible) | Server cert only | The Server cert's SAN must list a hostname matching the EndpointUrl it advertises. In NAT setups this is often an internal name — admins may need to suppress this non-critical error |
| HostName for Client cert | — | Skipped for Client Application Instance Certificates, same as forward — a Client cert never needs a hostname or SAN |
| Trust List, chain, signature, validity, revocation | Both app certs | Unchanged from forward connect |
Does the Server cert need the NAT device's DNS or IP in its SAN? No — not the NAT address. The HostName check compares the EndpointUrl the Server advertised in ReverseHello against the cert SAN — not the TCP source address the socket arrived from:
hostname( EndpointUrl in ReverseHello ) == a SAN entry in the Server certificate
The NAT device's public IP is merely the socket peer; the spec's hostname check never inspects it. The NAT address only matters if you deliberately advertise it as the EndpointUrl.
| Option | EndpointUrl the Server advertises | Required SAN entry |
|---|---|---|
| ① Internal identity (recommended) | opc.tcp://plc01.plant.local:4840 | plc01.plant.local — the NAT address never enters the cert |
| ② NAT public FQDN (also reachable forward via NAT) | opc.tcp://plant-gw.example.com:4840 | then yes — plant-gw.example.com must be in the SAN |
| ③ Suppress the check | anything | The HostName check is suppressible (non-critical); the admin tolerates the mismatch and an audit event is raised |
Why ① is cleanest: in reverse connect the
EndpointUrlis a logical identifier the Client never routes to — the socket is already open. Tie it and the SAN to the Server's stable internal identity and the certificate stays valid no matter what public IP or port the NAT presents. Baking the NAT's public address into the certificate is fragile: it breaks whenever the mapping, gateway name, or public IP changes. Either way, the checks that actually pin the Server's identity are the non-suppressible URI check and the Trust List check — neither involves the NAT.
Broken Connections, Reconnection and Subscription Repair
Reverse connect relocates who is responsible for reconnecting — with a knock-on effect on how Subscriptions survive an outage.
Who reconnects? The Server, not the Client
Because the Server is behind NAT and has no reachable address, the Client cannot re-open the socket. Per OPC 10000-6 §7.1.3 / Table 78, the Server owns transport recovery:
"If the SecureChannel is closed, the Server shall create a new socket if there is not already one without an active SecureChannel." … "If the Server receives an Error Message it shall close the socket, log the error and reconnects after a delay specified in its configuration."
So the retry and backoff logic that lived on the Client in forward connect now lives on the Server — reconnect cadence, exponential backoff, and the "keep one spare idle socket" duty are all Server-side configuration.
flowchart LR
subgraph FWD["🔀 Forward connect"]
FC["Client owns:<br/>• detect drop<br/>• retry + backoff<br/>• re-open socket<br/>• reactivate session<br/>• repair subscriptions"]
end
subgraph REV["↩️ Reverse connect"]
RS["Server owns:<br/>• re-open socket<br/>• retry + backoff<br/>• keep spare socket"]
RC["Client STILL owns:<br/>• reactivate session<br/>• repair subscriptions<br/>(but only AFTER the<br/>server re-opens the socket)"]
end
style FC fill:#1d3557,color:#fff
style RS fill:#2d6a4f,color:#fff
style RC fill:#1d3557,color:#fff
Only the transport reconnection moved. Session reactivation and Subscription repair are still the Client's job — they simply cannot begin until the Server has re-opened the socket and sent a fresh
ReverseHello.
Can Subscriptions be repaired as before? Yes — but now time-gated by the Server
Once a new socket and SecureChannel are up, the Client repairs a Subscription with the exact same Services as in forward connect:
| Step | Service | Reference |
|---|---|---|
| Re-bind the Session to the new SecureChannel | ActivateSession on a different SecureChannel | OPC 10000-4 §5.7.3 |
| Move Subscriptions to the reactivated Session | TransferSubscriptions — allowed even for Anonymous if same ApplicationUri and Sign/SignAndEncrypt | OPC 10000-4 §5.14.7 |
| Recover missed NotificationMessages | Republish from the retransmission queue | OPC 10000-4 §5.14.6 |
sequenceDiagram
participant S as 🏭 Server (behind NAT)
participant C as 🏢 Client
Note over S,C: ✂️ connection breaks
Note over C: ⏳ Client can only WAIT —<br/>it cannot dial the Server
loop Server retry + backoff (its config)
S-->>C: (re)open socket + ReverseHello
end
C->>S: Hello / OpenSecureChannel (new SecureChannel)
C->>S: ActivateSession (rebind old Session)
C->>S: TransferSubscriptions (subscriptionIds)
S-->>C: availableSequenceNumbers
C->>S: Republish (missed messages)
S-->>C: recovered NotificationMessages ✅
Note over S,C: back to normal — IF done before timeouts
The catch — a race the Client can no longer win alone. Subscriptions and Sessions only survive server-side within their timeouts: a Subscription is deleted with Bad_Timeout if no Publish or keep-alive arrives within its lifetime, and a Session times out if not reactivated within its RequestedSessionTimeout.
In forward connect the Client controls reconnect speed, so it can beat those timeouts. In reverse connect it cannot force reconnection:
⚠️ The Server's reconnect-backoff delay must be shorter than the Subscription lifetime and Session timeout. If the Server backs off longer than the Subscription lifetime, the Subscription is deleted before the Client ever gets a channel to transfer it — it must then be recreated from scratch, losing queued notifications.
Reverse connect therefore couples two values that forward connect kept independent: the Server's backoff configuration and the Subscription's lifetime.
Mitigations: keep the Server backoff aggressive and the Subscription lifetime generous so the repair window always fits inside it; use Durable Subscriptions (OPC 10000-4) to extend lifetimes dramatically; and make sure the Client resumes Publish immediately after TransferSubscriptions to reset the lifetime counter.
Shortfalls and Limitations
Reverse connect is a pragmatic firewall workaround, not a silver bullet:
| # | Limitation | Why it hurts |
|---|---|---|
| 1 | The Client now needs a reachable address | The NAT problem is moved, not deleted. If both ends are firewalled, reverse connect alone does not help. |
| 2 | Manual, static provisioning | Each Server must be configured with each target ClientUrl. No plug-and-play; scales poorly to thousands of Servers without a GDS and automation. |
| 3 | Idle sockets cost resources | Servers must keep a spare socket open per configured Client at all times — connection state that grows with fan-out. |
| 4 | One Server reaches configured Clients only | A Server dials only the Clients it was told about. Ad-hoc "any Client connects to any Server" discovery is lost. |
| 5 | The firewall still sees a persistent outbound tunnel | Security teams may object to a long-lived outbound connection from OT to IT just as much as to an inbound port; it must be governed. |
| 6 | Point-to-point, connection-oriented | Still a 1:1 socket model. No store-and-forward, no buffering across outages, no fan-out to many consumers. |
| 7 | Reconnection storms | Many Servers reconnecting to one Client after an outage need careful back-off; the spec warns Clients may reply Bad_ServerTooBusy. |
| 8 | NAT keep-alive | Long-lived idle sockets can be dropped by stateful NAT or firewalls; TCP keep-alives must be tuned or the "always-ready" socket silently dies. |
The two questions that bite in practice
The single biggest behavioural change is that connection freedom is lost in both directions. Reverse connect swaps the forward model's "listen and accept anyone" for pre-provisioned, point-to-point dialling.
Can a Server easily serve multiple Clients? Possible — but not "easy" the way forward connect is.
| Forward connect | Reverse connect | |
|---|---|---|
| Per-client config | None — the Server just listens on one port | One ClientUrl entry per target Client, admin-provisioned |
| Sockets | One listening socket serves all Clients | One persistent outbound socket per configured Client, kept idle and ready |
| New or unknown Client | Any Client that can reach the port connects | ❌ Impossible — a Server can only reach Clients it was configured for |
OPC 10000-6 §7.1.3: "the Server is configured… to connect to one or more Clients. For each Client, the administrator shall provide an EndpointUrl," and "Servers shall maintain at least one open socket without an active Session with each Client it is configured to connect to."
So N Clients means N static configuration entries and N maintained sockets. It scales, but linearly in configuration and connection state — the opposite of the "one port, many clients" simplicity of forward connect.
Can a Client connect to any Server it wants? No. In reverse connect the Client is passive and cannot initiate. The whole premise is that the Server is behind NAT and has no reachable address, so the Client has nothing to dial. The Client may only accept or reject an incoming Server: it checks the ServerUri and closes the socket if unacceptable, or returns Bad_ServerTooBusy if out of resources. It can filter, never initiate.
flowchart TB
subgraph FWD["🔀 Forward connect — free"]
direction LR
FC["Client"] -->|"dials any reachable Server"| FS1["Server A"]
FC -->|"and any other"| FS2["Server B"]
FSN["…any other Client"] -->|"one listening port"| FS1
end
subgraph REV["↩️ Reverse connect — provisioned"]
direction LR
RS1["Server A<br/>configured: Client X"] -->|"dials only X"| RC["Client X"]
RS2["Server B<br/>configured: Client X"] -->|"dials only X"| RC
RC -. "❌ cannot dial an<br/>unconfigured Server" .-> RSX["Server Z"]
end
FWD ~~~ REV
style FC fill:#1d3557,color:#fff
style RC fill:#1d3557,color:#fff
style RSX fill:#6a040f,color:#fff
Bottom line: a Server can fan out to many Clients and a Client can host many Servers — but only the specific pairings an administrator provisioned. Neither side keeps the "reach anyone" freedom of forward connect; that flexibility is the price of firewall traversal.
Alternatives That Keep Native opc.tcp Binary Connections
If the goal is to traverse NAT while still using OPC UA's native opc.tcp binary transport — keeping the full Client/Server Services model (Browse, Read, Write, Call, Subscribe) exactly as before — then the right alternatives operate beneath OPC UA at the network layer. They tunnel or relay the raw opc.tcp socket, so the OPC UA stack is completely unaware anything changed.
This rules out the broker and PubSub family (MQTT, AMQP): those replace request/response Services with fire-and-forget messaging and a different encoding. They solve NAT too, but they are not opc.tcp.
flowchart LR
Q{"Which layer<br/>solves NAT?"}
Q -->|"OPC UA transport itself"| RC["✅ OPC UA Reverse Connect<br/>native opc.tcp, no extra infra"]
subgraph TUNNEL["🟢 Network layer — opc.tcp preserved, Services intact"]
direction TB
VPN["VPN overlay<br/>WireGuard / IPsec / OpenVPN"]
SSH["SSH reverse tunnel<br/>ssh -R (RFC 4253)"]
RELAY["TCP relay / rendezvous<br/>TURN (RFC 8656) · ICE (RFC 8445)"]
VPN ~~~ SSH ~~~ RELAY
end
Q -->|"Below OPC UA"| TUNNEL
subgraph MODEL["🔴 Changes the model — not opc.tcp"]
direction TB
PUBSUB["OPC UA PubSub<br/>over MQTT / AMQP<br/>(OPC 10000-14)"]
end
Q -->|"Messaging layer"| MODEL
style RC fill:#2d6a4f,color:#fff
style TUNNEL fill:#1d3557,color:#fff
style MODEL fill:#6a040f,color:#fff
All of the network-layer options are transparent to OPC UA: the Client still opens or receives an opc.tcp://… connection, SecureChannel, Session and Subscriptions work unchanged, and OPC UA's own end-to-end security still applies on top.
| Approach | How it beats NAT | Keeps opc.tcp + Services? | Reference | Trade-off |
|---|---|---|---|---|
| Reverse Connect | Server dials the Client outbound; roles unchanged | ✅ Native — it is OPC UA | OPC 10000-6 §7.1.3 | Needs one reachable side; admin-provisioned |
| VPN overlay | An encrypted overlay makes both ends "local"; then a normal forward opc.tcp connection works | ✅ Fully transparent | WireGuard; IPsec (RFC 4301/4303); OpenVPN | Heavier infrastructure; per-site keys; another network to operate |
SSH reverse tunnel (ssh -R) | An outbound SSH session forwards a remote port back to the firewalled Server's opc.tcp port | ✅ Byte-transparent | RFC 4253 | Simple and ad-hoc but operationally fragile; not centrally managed |
| Generic TCP relay / rendezvous (TURN, ICE/STUN) | A public relay forwards the TCP stream, or peers punch a direct path; the relay carries raw opc.tcp bytes | ✅ Bytes preserved via an external agent | RFC 8656 (TURN), RFC 8445 (ICE), RFC 8489 (STUN) | Not OPC UA-native; the relay is a bandwidth and trust bottleneck; STUN/ICE fail on symmetric NAT |
| OPC UA PubSub over MQTT/AMQP | Both ends are outbound clients of a central broker; neither accepts inbound | ❌ Not opc.tcp, no Services | OPC 10000-14; MQTT v5.0 / AMQP 1.0 | Great for fan-out, buffering, and cloud — but you lose request/response semantics |
Security note: because OPC UA already provides end-to-end SecureChannel security, a VPN or tunnel is defence in depth, not a replacement. Do not drop OPC UA encryption just because a VPN is present.
Rule of thumb for keeping native opc.tcp: one side reachable → Reverse Connect (zero extra infrastructure). Neither side reachable, or many isolated sites → VPN overlay. Quick point-to-point or lab work → SSH reverse tunnel. Only if you must, and can add an agent → TURN/ICE relay. Willing to trade Services for fan-out and buffering → PubSub over MQTT.
Key Takeaways
- Reverse connect flips the socket direction, not the roles — the Server dials out; Client and Server keep their normal jobs (OPC 10000-6 §7.1.3).
- It exists for the firewall and NAT reality of OT — no inbound ports on the plant network, one Client aggregating many hidden Servers.
ReverseHellocarries onlyServerUriandEndpointUrl; after it, the handshake is identical to a forward connection (Table 77).- Servers keep a spare idle socket per configured Client, reconnect with back-off, and honour a ≤ 2-minute handshake timeout (Table 78).
- Discovery is admin-driven — out-of-band, or a GDS
QueryApplicationswith the"RCP"filter returningrcp+URLs (OPC 10000-12 §4.4). - Security is unchanged — same SecureChannel, certificates, and trust model.
- No separate certificate — both ends reuse their normal Application Instance Certificate. The
ReverseHelloServerUriis unauthenticated, so real authentication still happens atOpenSecureChannel. The only caveat: the Server cert's SAN should match theEndpointUrlit advertises. - Reconnection moves to the Server — the Client cannot dial a NAT'd Server, so retry, backoff, and socket recovery are the Server's duty. Session reactivation plus
TransferSubscriptionsandRepublishremain the Client's job, but only after the Server re-opens the socket. Keep Server backoff shorter than the Subscription lifetime or Subscriptions time out and must be recreated; Durable Subscriptions help. - It moves the NAT problem, it does not erase it — to keep native
opc.tcpbinary plus Services, stay at the network layer: Reverse Connect first, then a VPN overlay, SSH reverse tunnel, or TCP/TURN relay. Step outsideopc.tcpto PubSub over MQTT only when you deliberately trade Services for fan-out and buffering.
Compiled from OPC 10000-6 (§7.1.2, §7.1.3, Tables 73–78), OPC 10000-12 §3.1.4 and §4.4, and OPC 10000-4. Non-OPC-UA alternatives reference the cited IETF RFCs and OASIS standards. For normative wording always consult the official specifications.