TL;DR — Reverse Connection in OPC UA

Primary spec: OPC 10000-6 (Mappings) §7.1.2.6 and §7.1.3 — the ReverseHello Message 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:

ScenarioWho is where
Cloud/IT collecting from OTClient in the datacenter; hundreds of Servers on isolated plant networks
Remote machine monitoringMachine-builder's Client at HQ; Servers deployed at customer sites
DMZ crossingClient in the enterprise zone; Servers in the tightly firewalled control zone
Cellular / edge devicesDevices 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):

FieldTypeMeaning
ServerUriStringThe ApplicationUri of the Server sending the message (≤ 4096 bytes). Lets the Client decide whether it wants this Server.
EndpointUrlStringThe 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 CreateSession instantly 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/ReverseHello within 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:

MethodHowReference
Out-of-bandA 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 ServerUri filter 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." The ServerUri in ReverseHello carries no signature — the Client only truly authenticates the Server via certificate validation during OpenSecureChannel.

What the same certificate must still satisfy — all met by the normal certificate, nothing new to issue:

Check (OPC 10000-4 Table 100)Applies toReverse-connect implication
URI (Bad_CertificateUriInvalid, not suppressible)Both app certsThe 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 onlyThe 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 certSkipped for Client Application Instance Certificates, same as forward — a Client cert never needs a hostname or SAN
Trust List, chain, signature, validity, revocationBoth app certsUnchanged 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.

OptionEndpointUrl the Server advertisesRequired SAN entry
① Internal identity (recommended)opc.tcp://plc01.plant.local:4840plc01.plant.localthe NAT address never enters the cert
② NAT public FQDN (also reachable forward via NAT)opc.tcp://plant-gw.example.com:4840then yesplant-gw.example.com must be in the SAN
③ Suppress the checkanythingThe HostName check is suppressible (non-critical); the admin tolerates the mismatch and an audit event is raised

Why ① is cleanest: in reverse connect the EndpointUrl is 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:

StepServiceReference
Re-bind the Session to the new SecureChannelActivateSession on a different SecureChannelOPC 10000-4 §5.7.3
Move Subscriptions to the reactivated SessionTransferSubscriptions — allowed even for Anonymous if same ApplicationUri and Sign/SignAndEncryptOPC 10000-4 §5.14.7
Recover missed NotificationMessagesRepublish from the retransmission queueOPC 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:

#LimitationWhy it hurts
1The Client now needs a reachable addressThe NAT problem is moved, not deleted. If both ends are firewalled, reverse connect alone does not help.
2Manual, static provisioningEach Server must be configured with each target ClientUrl. No plug-and-play; scales poorly to thousands of Servers without a GDS and automation.
3Idle sockets cost resourcesServers must keep a spare socket open per configured Client at all times — connection state that grows with fan-out.
4One Server reaches configured Clients onlyA Server dials only the Clients it was told about. Ad-hoc "any Client connects to any Server" discovery is lost.
5The firewall still sees a persistent outbound tunnelSecurity 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.
6Point-to-point, connection-orientedStill a 1:1 socket model. No store-and-forward, no buffering across outages, no fan-out to many consumers.
7Reconnection stormsMany Servers reconnecting to one Client after an outage need careful back-off; the spec warns Clients may reply Bad_ServerTooBusy.
8NAT keep-aliveLong-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 connectReverse connect
Per-client configNone — the Server just listens on one portOne ClientUrl entry per target Client, admin-provisioned
SocketsOne listening socket serves all ClientsOne persistent outbound socket per configured Client, kept idle and ready
New or unknown ClientAny Client that can reach the port connectsImpossible — 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.

ApproachHow it beats NATKeeps opc.tcp + Services?ReferenceTrade-off
Reverse ConnectServer dials the Client outbound; roles unchangedNative — it is OPC UAOPC 10000-6 §7.1.3Needs one reachable side; admin-provisioned
VPN overlayAn encrypted overlay makes both ends "local"; then a normal forward opc.tcp connection works✅ Fully transparentWireGuard; IPsec (RFC 4301/4303); OpenVPNHeavier 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-transparentRFC 4253Simple 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 agentRFC 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/AMQPBoth ends are outbound clients of a central broker; neither accepts inbound❌ Not opc.tcp, no ServicesOPC 10000-14; MQTT v5.0 / AMQP 1.0Great 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

  1. 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).
  2. It exists for the firewall and NAT reality of OT — no inbound ports on the plant network, one Client aggregating many hidden Servers.
  3. ReverseHello carries only ServerUri and EndpointUrl; after it, the handshake is identical to a forward connection (Table 77).
  4. Servers keep a spare idle socket per configured Client, reconnect with back-off, and honour a ≤ 2-minute handshake timeout (Table 78).
  5. Discovery is admin-driven — out-of-band, or a GDS QueryApplications with the "RCP" filter returning rcp+ URLs (OPC 10000-12 §4.4).
  6. Security is unchanged — same SecureChannel, certificates, and trust model.
  7. No separate certificate — both ends reuse their normal Application Instance Certificate. The ReverseHello ServerUri is unauthenticated, so real authentication still happens at OpenSecureChannel. The only caveat: the Server cert's SAN should match the EndpointUrl it advertises.
  8. 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 TransferSubscriptions and Republish remain 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.
  9. It moves the NAT problem, it does not erase it — to keep native opc.tcp binary plus Services, stay at the network layer: Reverse Connect first, then a VPN overlay, SSH reverse tunnel, or TCP/TURN relay. Step outside opc.tcp to 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.