BadProtocolVersionUnsupported
BadProtocol
BadProtocolVersionUnsupported means the two ends could not agree on the OPC UA TCP protocol version during the Hello and Acknowledge exchange. In practice it rarely means a real version gap: it means you are talking opc.tcp to something that is not an opc.tcp listener, or a middlebox mangled the handshake.
What you actually see
The TCP connection is accepted and then torn down within milliseconds, before any endpoint list, certificate prompt or session, with an error naming the protocol version.
Why it happens
- Something else is listening on that portAn HTTP server, a TLS listener, a Modbus TCP gateway or a web UI on the port you dialled will answer the socket and then produce nonsense in response to the Hello frame. The client decodes the reply as a malformed or unsupported handshake. This is the most common real cause, and it is why an open port alone never proves you found an OPC UA server.
- Wrong transport for the endpointopc.tcp, opc.https and opc.wss are different transport profiles with different framings. Pointing an opc.tcp client at a WebSocket or HTTPS listener - 4843 is the registered default for secure WebSockets and DTLS in the reference stack - opens the socket and fails the handshake. The scheme in the URL decides the framing, not the port number.
- A proxy or TLS terminator in the pathThe OPC UA TCP framing begins with a HEL message and is not HTTP. Any device that inspects, rewrites or wraps the byte stream - a TLS-terminating proxy, an application-layer gateway, a protocol-aware firewall - corrupts it. The receiver then reports a version it cannot parse rather than a network error.
- Genuinely incompatible or hand-written stacksA very old embedded server against a modern client, or a cut-down implementation that writes the header incorrectly. This is rare on mainstream SDKs but common on hand-rolled microcontroller stacks and on protocol bridges written from the spec text.
- Reverse connect answered by the wrong listenerA server dialling a client reverse-connect port - commonly 65300 - that is actually occupied by another application produces the same failure from the server side. Check which process owns the listening port on the client host before assuming the server is at fault.
How to fix it
In diagnostic order: work down, do not skip ahead.
- Prove what is actually on the portCapture the first packets with the Wireshark OPC UA dissector on the port in question. A real OPC UA TCP listener answers a HEL frame with an ACK. Anything else - an HTTP response line, a TLS ServerHello, silence followed by a close - identifies the service on that port immediately.
- Check the URL scheme against the listenerConfirm you are using opc.tcp:// against a UA TCP listener. If the server offers only opc.wss or opc.https, an opc.tcp client cannot talk to it whatever the port. Read TransportProfileUri on each EndpointDescription rather than inferring the transport from the port number.
- Take the path apartTry the same connection from the server host itself, then from the same subnet, then from the client subnet. If it works locally and fails across a boundary, something in between is rewriting the stream. Remove the proxy, or use reverse connect so the flow is a plain outbound opc.tcp connection.
- Update the older of the two implementationsIf the capture shows a genuine OPC UA handshake carrying a version the peer refuses, upgrade the older side. On embedded servers that means firmware; on clients, pin a current SDK release rather than an ancient one.
- Confirm with a reference clientPoint UaExpert or opcua-commander at the same URL. If a reference client fails identically, the problem is on the wire and not in your code, which saves a long detour through your own client.
Where it shows up, by tool and stack
| Tool / stack | What to do there |
|---|---|
| Wireshark OPC UA dissector | The decisive tool here. It decodes the HEL, ACK and ERR frames of the handshake, so you can see the version fields both sides sent and whether the reply is OPC UA at all. |
| open62541 (C) | The client logs the handshake and reports the connection state through its state callback before any channel or session exists. A failure that never reaches channel state narrows the search to the transport immediately. |
| UA-.NETStandard (C#) | The transport binding is chosen from the URL scheme. Passing an https or wss URL where a TCP transport is configured, or the reverse, surfaces as a handshake failure rather than a clear configuration error. |
| node-opcua | The transport error is emitted before any endpoint selection. Running the bundled endpoint-dump tool against the URL is the quickest way to see whether the listener speaks OPC UA TCP at all. |
| UaExpert | Discovery fails with nothing appearing in the tree. If Add Server by URL produces an immediate error rather than an endpoint list, stop investigating certificates and find out what owns the port. |
What it looks like in the log
Transport error: BadProtocolVersionUnsupported during Hello/Acknowledge exchange
Received 15 bytes from opc.tcp://gw01:4840 that could not be decoded as an Acknowledge frame
Connection to opc.tcp://gw01:4840 closed by peer immediately after Hello
Confirm the fix
Capture a fresh connect and confirm HEL is answered by ACK, then that GetEndpoints with security None returns a non-empty endpoint list from the same address.
Stop it happening again
Record the transport profile alongside the URL for every server in your inventory, and keep protocol-aware proxies and TLS terminators off opc.tcp paths.
Stuck on more than one of these? The troubleshooting clinic works through broken configurations end to end, on camera.
Troubleshooting clinic →Last verified 7 September 2026 against OPC UA 1.05.07. OPC UA Connect is an educational resource and is not affiliated with the OPC Foundation.