OPC UA — Client Cannot Connect: The Certificate Trust Handshake

intermediate · 25 min

You will be able to work out, in under ten minutes, which side of an OPC UA connection refused the other and why — untrusted certificate, wrong Application URI, wrong hostname, expired certificate, or a security policy mismatch — and fix it without breaking the clients that still work.

ℹ️ The mistake almost everyone makes is thinking about one certificate. OPC UA authentication is mutual: the client must trust the server's certificate and the server must trust the client's certificate. Trusting the server in your client and stopping there fixes half the handshake, which is the same as fixing none of it.

Why OPC UA refuses you by default

OPC UA (IEC 62541) builds security into the transport, not on top of it. Every OPC UA application — a client or a server — carries an application instance certificate. That is an X.509 file that identifies the software installation, not a person.

On a secured endpoint, the two applications exchange these certificates during the OpenSecureChannel step, before any tag is read, and each side checks the other against its own trust list: a folder of certificates it has agreed to accept. Per OPC UA Part 2, this authentication is bidirectional. If either side does not recognise the other, the connection is refused. A brand-new server trusts nobody, so the first secured connection attempt from any client is supposed to fail. That is not a fault. It is the commissioning step.

Two qualifications that save hours:

  • On an endpoint with message security mode None, no application instance certificates are exchanged and no trust check happens. This is why a first connection frequently succeeds, and then breaks the instant you select Basic256Sha256. If your connection works and then stops working when you turn security on, you have not broken anything — you have just reached the trust check for the first time.
  • Certificate validation is not only an OpenSecureChannel event. It runs again when the session is created and activated, so a certificate problem can surface after the channel appears to have opened.

Diagnosis first: which side said no?

Do this before you change anything.

Read the status code the client reports. The code narrows the search, but be aware of its limits: a server that refuses a stranger deliberately tells it very little. BadSecurityChecksFailed is the generic refusal, and the client-side code alone cannot separate "untrusted" from "expired" from "wrong key usage". The server's own log is the authoritative witness. Note also that a client raises these same codes when it is the one refusing the server.

Status codeWhat it actually meansWhere to look
BadSecurityChecksFailed (0x80130000)Generic "I refused you." Usually the server does not trust the client certificate, but it also covers expiry, key usage and chain problems.Server log first, then server trust store
BadCertificateUntrusted (0x801A0000)The presented certificate is not in the trust list.Whichever side reported it
BadCertificateUriInvalid (0x80170000)The ApplicationUri sent does not match the URI inside the certificate.Regenerate the certificate
BadCertificateHostNameInvalid (0x80160000)You connected by an address that is not listed in the certificate.Endpoint URL or certificate
BadCertificateTimeInvalid (0x80140000)Certificate expired, or not yet valid.Clock or certificate
BadSecurityPolicyRejected (0x80550000) / BadSecurityModeRejected (0x80540000)Not a trust problem at all — you asked for an endpoint the server does not offer.Client endpoint selection
BadCertificateChainIncomplete (0x810D0000) / BadCertificateRevocationUnknown (0x801B0000)The validator could not complete or revocation-check the chain.Issuers/CA folder and CRLs on the validating side

Then use the rejected folder as your witness — where one exists. Most file-based OPC UA stacks file an unknown certificate they just refused into a rejected (or quarantined) store. Attempt the connection once, then look at that folder on the server. If a new file appeared, timestamped at your attempt, the server saw you and refused you — the problem is server-side trust.

If nothing appeared, do not jump to a conclusion. It means one of: the server never got that far (your client rejected the server first, or you never reached the server); the handshake failed before certificate validation, on policy, mode or transport; the stack could not write to its PKI directory; or the server has no rejected store at all. Embedded PLC servers are the important case here — an S7-1500 does not expose one, and you cannot harvest a certificate off the CPU. On those, the trust material has to move the other way: export the client's own certificate from the client and import it into the engineering tool.

Step-by-step

  1. Prove the network first — on the right port. 4840 is the IANA-registered opcua-tcp port defined in OPC UA Part 6, and it is what an S7-1500 uses. It is not what every product ships with. Ignition's OPC UA server defaults to 62541; KEPServerEX defaults to 49320. Confirm the port from the server's own configuration page before testing, then from the client machine run Test-NetConnection <ip> -Port <port> on Windows. No connection means firewall, route, wrong port, or a server that is not running — stop and fix that.
  2. List the endpoints. GetEndpoints does not require message security (Part 4, Discovery Service Set — §5.4.4 in 1.04, §5.5.4 in the current edition; the text reads that the service "shall not require message security but it may require transport layer security"). So it works even when trust is broken. In UaExpert: Add Server → Custom Discovery → enter opc.tcp://<ip>:<port>. Write down each endpoint's Security Policy (the crypto suite) and Message Security Mode (None, Sign, or SignAndEncrypt). If the list appears, the server is alive and this is a security problem.
  3. Establish which direction the certificate has to travel. If the server is a software product with a file-based or web-managed PKI (Ignition, KEPServerEX, most SDK-based servers), attempt the secured connection once deliberately — you want both sides to file each other's certificate, and you will pick them up from the rejected stores. If the server is an embedded PLC with no rejected store, skip the deliberate attempt: export the client's own certificate instead. In UaExpert that is %APPDATA%\unifiedautomation\uaexpert\PKI\own\certs\uaexpert.der.
  4. Trust the client certificate at the server.
  • File-based stacks: copy the .der file from …/pki/rejected/certs into …/pki/trusted/certs.
  • Ignition: Config → OPC UA → Security. Note the Client and Server tabs — a gateway acting as a server quarantines the incoming client's certificate under Server; a gateway acting as a client quarantines the remote server's certificate under Client. Trusting on the wrong tab is a common dead end. Find the entry under Quarantined Certificates and trust it.
  • KEPServerEX: open the OPC UA Configuration Manager, Trusted Clients tab, select the entry, press Trust.
  • Siemens S7-1500 in TIA Portal: requires CPU firmware V2.6 or higher — on older firmware this workflow does not exist. Import the client certificate under OPC UA → Server → Security → Certificates → Trusted clients, then download to the CPU.

Plan an outage for the Siemens step. This is a hardware-configuration change, and downloading it puts the CPU in STOP. Loading certificates while the CPU runs is only possible from TIA Portal V19 with firmware V3.1 or later, and only for certificates that already carry the same certificate ID in the global certificate manager. Do not run step 4 against a live line on the assumption that it is a settings tweak.

  1. Trust the server certificate at the client. Same move, other direction. UaExpert stores its PKI under %APPDATA%\unifiedautomation\uaexpert\PKI, and also offers "Trust Server Certificate" in its connect dialog.
  2. Reconnect. A different status code is progress. Work the new one.
  3. If you get BadCertificateUriInvalid: open the certificate, look at the Subject Alternative Name extension, URI= field. It must match the ApplicationUri the application announces, character for character. Certificates are signed — you cannot edit this. Regenerate the certificate with the correct URI (UaExpert: Settings → Manage Certificates), then re-trust it everywhere. If that client is already trusted by several servers, treat the regeneration as the same blast radius as regenerating a server certificate: every one of them must be re-trusted.
  4. If you get BadCertificateHostNameInvalid: the certificate lists a DNS name and you connected by IP, or the reverse. Connect using exactly the name in the certificate's SAN, or regenerate the certificate to include the address you actually use. Note that some servers return endpoint URLs containing their own hostname, which the client machine may not be able to resolve — check that name resolves before blaming the certificate.
  5. If you get BadCertificateTimeInvalid: compare both clocks in UTC. A PLC that lost power without a battery or NTP source can read a date years off, which makes a perfectly good certificate look expired — or makes a freshly issued one "not yet valid".
  6. If you get BadCertificateChainIncomplete or BadCertificateRevocationUnknown: the usual cause is a CA-issued certificate whose issuer certificate, or whose CRL, is missing from the validating side's PKI store — install the issuer certificate in the issuers (or CA) folder and its revocation list alongside it. But do not assume a CA was involved. Both codes are also raised against self-signed certificates whose Issuer and Subject fields are not byte-identical: several stacks then treat a self-issued certificate as a chain and hunt for an issuer and CRL that never existed. If your certificate is self-signed, regenerating it with matching Issuer and Subject is usually the fix, not installing a CA.
  7. If the policy or mode is rejected: pick an endpoint from the list in step 2. Basic128Rsa15 and Basic256 were deprecated by the OPC Foundation in specification 1.04 — SHA-1 is no longer considered secure — and they are disabled by default in current products. Modern servers expect Basic256Sha256, Aes128_Sha256_RsaOaep, or Aes256_Sha256_RsaPss.
  8. Only now handle the user login. Anonymous, username/password, or user certificate is a separate check that happens after the secure channel opens. If the channel opened and the session was refused, you are past the application certificate problem entirely.

Never do this

Do not delete the PKI folder or regenerate the server's certificate to make the error go away. It works — for the one client in front of you. Every other client that already trusted that server now holds a certificate that no longer matches, and they all drop. You will fix one HMI at 14:00 and lose the historian, the MES link, and two other SCADA nodes by 14:05, with no obvious connection between cause and effect.

Do not leave "automatically accept all client certificates at runtime" enabled. It is a commissioning aid. Left on, any device that can reach the server's port is trusted, which removes the entire reason OPC UA holds certificates. Turn it on, connect, trust the certificate properly, turn it off, and confirm the client still connects. On an S7-1500 remember that turning it back off is another hardware-configuration download — schedule it with the rest of the work rather than leaving it on "until the next window".

Do not "temporarily" drop to Security Policy None to prove the tags work. That test is almost never removed, and it removes both encryption and authentication from the link.

Do not hand-edit a certificate file. X.509 certificates are cryptographically signed; changing one byte invalidates the signature. Regenerate instead.

Key points

  • OPC UA authentication is mutual — the server must trust the client's certificate and the client must trust the server's, and fixing only one direction leaves the connection refused.
  • The server's rejected or quarantined certificate folder is the fastest diagnostic available: if a new file appeared at the moment you tried to connect, the server saw you and refused you, so the trust problem is server-side.
  • GetEndpoints does not require message security, so you can always list a server's endpoints, security policies, and security modes even while the trust handshake is failing.
  • BadCertificateUriInvalid and BadCertificateHostNameInvalid cannot be fixed by trusting the certificate — the identity baked into the certificate is wrong and the certificate must be regenerated or the connection address changed.
  • Regenerating a server certificate to clear one client's error silently breaks every other client that already trusted that server, which is the most expensive mistake in OPC UA commissioning.

Codes and symptoms

BadSecurityChecksFailed — Security checks failed (0x80130000)
A deliberately vague refusal during the secure channel handshake — the server will not tell a stranger why it said no. In practice it most often means the server does not trust the client's application instance certificate, but it also covers expired certificates, wrong key usage, and a missing certificate chain. The client-side code cannot separate these; read the server's log, and check its rejected certificate store, before assuming. A client raises this code too when it is the side refusing the server.
BadCertificateUntrusted — Certificate is not trusted (0x801A0000)
The certificate presented by the other side is not in this application's trust list. Move it from the rejected or quarantined store into the trusted store, on the side that reported the error. Embedded PLC servers may have no rejected store to move it from — on those, export the peer's own certificate and import it through the engineering tool.
BadCertificateUriInvalid — Application URI does not match the certificate (0x80170000)
The ApplicationUri the application announces does not match the URI carried in the certificate's Subject Alternative Name extension. OPC UA requires these to be identical. The certificate is signed, so it cannot be edited — regenerate it with the correct URI, then re-trust it on every peer that already trusted the old one.
BadCertificateHostNameInvalid — Hostname or IP does not match the certificate (0x80160000)
You connected using an address that is not listed in the certificate — typically connecting by IP address when the certificate only names the DNS hostname, or the reverse. Connect using the name in the certificate, or reissue the certificate including the address you use.
BadCertificateTimeInvalid — Certificate outside its validity period (0x80140000)
The certificate is expired, or not yet valid, when checked against the local clock. Compare both machines in UTC. A PLC or panel whose real-time clock has drifted or reset will reject a perfectly valid certificate.
BadSecurityPolicyRejected — Security policy rejected (0x80550000)
The security policy the client requested does not meet the server's requirements — it is not one the server offers. This is not a certificate problem. Basic128Rsa15 and Basic256 were deprecated in OPC UA specification 1.04 because SHA-1 is no longer considered secure, and they are disabled by default on current servers.
BadSecurityModeRejected — Security mode rejected (0x80540000)
The message security mode requested — None, Sign, or SignAndEncrypt — does not meet the server's requirements and is not offered on that endpoint. Run GetEndpoints and select a mode the server actually advertises.
BadCertificateChainIncomplete — Certificate chain is incomplete (0x810D0000)
The validating side could not assemble a complete chain. Usually a CA-issued certificate was presented and the issuing CA certificate is missing from the validator's PKI store — install the issuer certificate in the issuers or CA folder. But it is also raised against self-signed certificates whose Issuer and Subject fields are not byte-identical: several stacks then treat a self-issued certificate as a chain and search for an issuer that never existed. If the certificate is self-signed, regenerate it with matching Issuer and Subject rather than hunting for a CA.
BadCertificateRevocationUnknown — Revocation status could not be determined (0x801B0000)
The validating application could not confirm whether a certificate has been revoked, usually because no certificate revocation list is present for that issuer. Several stacks treat this as a hard failure. Like chain-incomplete, it can also fire spuriously on a self-signed certificate that a stack has mistaken for a CA-issued one.