OPC-UA Certificates, where Ignition acts as a OPC-UA Client

Hi,

we want to connect to OCP-UA Servers with a CA signed certificate and have the following challenges:

  1. There is no UIinterface to upload these kind of certificates (Ignition 8.3.4). There is an interface on the Server and Client tab, but it seems that they are mainly for the Ignition OPC-UA Server or the certificates which we get from OPC-UA Clients. Is this correct?

  2. The OPC-UA modul crashs when we import the certificate chain (p7b) and the .crt file to the keystore. Is there a specific format required?

Thank you for your help!

By this, you mean that the Servers are all using CA-signed certificates, and you want to configure that CA-signed certificate as trusted in Ignition so that all Client connections to those Servers are automatically trusted on the Ignition side?

Or do you mean something else?

Yes,

  • we connect via Ignition to other OPC-UA servers
  • The other OPC-UA servers have CA-signed certificates
  • We also have the CA-signed certificates and want to configure Ignition, so the OPC-UA Client auto trust our connection

All that should really be needed is to import the CA certificate on the Client tab of the OPC UA Security page:

It expects DER-encoded certificates.

The DER certificate is accepted by Ignition. Are there any ways to debug the connection.
We still get the following error:

UaException: status=Bad_SecurityChecksFailed, message=Could not verify security on OpenSecureChannel request.

And the connection toggles between connected and Faulted and eventually keeps Faulted.

Can you upload your full log files? First step is to figure out which side is rejecting the connection.

There isn’t much in the logs. I guess this is the relevant part:

2026-03-27 20:32:08:075 milo-netty-event-loop-8
Sent OpenSecureChannelRequest (Issue, id=0, currentToken=-1, previousToken=-1).

2026-03-27 20:32:08:076 milo-netty-event-loop-8
OpenSecureChannel timeout canceled

2026-03-27 20:32:08:076 milo-netty-event-loop-8
Received OpenSecureChannelResponse.

2026-03-27 20:32:08:076 milo-netty-event-loop-8
SecureChannel id=7311, currentTokenId=1, previousTokenId=-1, lifetime=3600000ms, createdAt=DateTime{date=Fri Mar 27 19:32:08 UTC 2026, instant=2026-03-27T19:32:08.045487100Z}

2026-03-27 20:32:08:076 milo-netty-event-loop-8
0 message(s) queued before handshake completed; sending now.

2026-03-27 20:32:08:082 milo-netty-event-loop-9
OpenSecureChannel timeout scheduled for +60000ms

2026-03-27 20:32:08:083 milo-netty-event-loop-9
Sent OpenSecureChannelRequest (Issue, id=0, currentToken=-1, previousToken=-1).

2026-03-27 20:32:08:087 milo-netty-event-loop-9
[remote=/xxx] errorMessage=ErrorMessage{error=StatusCode[name=Bad_SecurityChecksFailed, value=0x80130000, quality=bad], reason=Could not verify security on OpenSecureChannel request.}

2026-03-27 20:32:09:090 milo-netty-event-loop-10
OpenSecureChannel timeout scheduled for +60000ms

2026-03-27 20:32:09:092 milo-netty-event-loop-10
Sent OpenSecureChannelRequest (Issue, id=0, currentToken=-1, previousToken=-1).

2026-03-27 20:32:09:096 milo-netty-event-loop-10
[remote=/xxx] errorMessage=ErrorMessage{error=StatusCode[name=Bad_SecurityChecksFailed, value=0x80130000, quality=bad], reason=Could not verify security on OpenSecureChannel request.}).

These are telling you that the remote server doesn't trust Ignition's client certificate.

Yes, we wanted to exchange the current Ignition client certificate with a CA signed certificate which Ignition can use to connect to other servers. But based on OPC UA Security Settings | Ignition User Manual it looks like you can only regenerate the Ignition certificate but not sign it with a CA and redeploy the CA signed certificate.

It is technically possible if you are capable of generating a valid CA-signed OPC UA client certificate and private key outside of Ignition, and then importing it into the keystore file under a separate alias. There is an advanced setting on OPC UA connections that allow you to specify the alias and password that are used to retrieve the client certificate and private key from the keystore.

I recommend using KeyStore Explorer to poke around with these files, though I'd imagine it's possible to get things imported with keytool or openssl since keytstores / PFX files are a standard format.

Ah, there is this setting “Keystore Alia”s. Thank you, I will check if we can make it with it :+1:

We are still struggling with connecting to a client with a self-signed CA.

  1. We were able to adde the self-signed client certificate succesfully to the key store under a seperate alias. It is also transferred to the server when we connect to it.
  2. We added the ROOT CA to the Ignition supplementar folder
  3. If we connect to the client we receive the intermediate certificate and the leaf certificate. But when we trust them we receive it again? (see picture below)
  4. The OPC-UA connection switchs between Connected and Failure and evenutall shows the following error:
Exception

UaException: status=Bad_SecurityChecksFailed, message=sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
at org.eclipse.milo.opcua.stack.core.util.validation.CertificateValidationUtil.buildCertPath(CertificateValidationUtil.java:388)
at org.eclipse.milo.opcua.stack.core.util.validation.CertificateValidationUtil.buildTrustedCertPath(CertificateValidationUtil.java:132)
at org.eclipse.milo.opcua.stack.core.security.DefaultClientCertificateValidator.validateCertificateChain(DefaultClientCertificateValidator.java:72)
at org.eclipse.milo.opcua.stack.transport.client.uasc.UascClientMessageHandler.onOpenSecureChannel(UascClientMessageHandler.java:379)
at org.eclipse.milo.opcua.stack.transport.client.uasc.UascClientMessageHandler.decode(UascClientMessageHandler.java:265)
at io.netty.handler.codec.ByteToMessageCodec$1.decode(ByteToMessageCodec.java:42)
at io.netty.handler.codec.ByteToMessageDecoder.decodeRemovalReentryProtection(ByteToMessageDecoder.java:530)
at io.netty.handler.codec.ByteToMessageDecoder.callDecode(ByteToMessageDecoder.java:469)
at io.netty.handler.codec.ByteToMessageDecoder.channelRead(ByteToMessageDecoder.java:290)
at io.netty.handler.codec.ByteToMessageCodec.channelRead(ByteToMessageCodec.java:103)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:442)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:420)
at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:412)
at io.netty.channel.DefaultChannelPipeline$HeadContext.channelRead(DefaultChannelPipeline.java:1357)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:440)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:420)
at io.netty.channel.DefaultChannelPipeline.fireChannelRead(DefaultChannelPipeline.java:868)
at io.netty.channel.nio.AbstractNioByteChannel$NioByteUnsafe.read(AbstractNioByteChannel.java:166)
at io.netty.channel.nio.NioEventLoop.processSelectedKey(NioEventLoop.java:796)
at io.netty.channel.nio.NioEventLoop.processSelectedKeysOptimized(NioEventLoop.java:732)
at io.netty.channel.nio.NioEventLoop.processSelectedKeys(NioEventLoop.java:658)
at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:562)
at io.netty.util.concurrent.SingleThreadEventExecutor$4.run(SingleThreadEventExecutor.java:998)
at io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74)
at java.base/java.lang.Thread.run(Unknown Source)
Caused by: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
at java.base/sun.security.provider.certpath.SunCertPathBuilder.build(Unknown Source)
at java.base/sun.security.provider.certpath.SunCertPathBuilder.engineBuild(Unknown Source)
at java.base/java.security.cert.CertPathBuilder.build(Unknown Source)
at org.eclipse.milo.opcua.stack.core.util.validation.CertificateValidationUtil.buildCertPath(CertificateValidationUtil.java:386)
at org.eclipse.milo.opcua.stack.core.util.validation.CertificateValidationUtil.buildTrustedCertPath(CertificateValidationUtil.java:132)
at org.eclipse.milo.opcua.stack.core.security.DefaultClientCertificateValidator.validateCertificateChain(DefaultClientCertificateValidator.java:72)
at org.eclipse.milo.opcua.stack.transport.client.uasc.UascClientMessageHandler.onOpenSecureChannel(UascClientMessageHandler.java:379)
at org.eclipse.milo.opcua.stack.transport.client.uasc.UascClientMessageHandler.decode(UascClientMessageHandler.java:265)
at io.netty.handler.codec.ByteToMessageCodec$1.decode(ByteToMessageCodec.java:42)
at io.netty.handler.codec.ByteToMessageDecoder.decodeRemovalReentryProtection(ByteToMessageDecoder.java:530)
at io.netty.handler.codec.ByteToMessageDecoder.callDecode(ByteToMessageDecoder.java:469)
at io.netty.handler.codec.ByteToMessageDecoder.channelRead(ByteToMessageDecoder.java:290)
at io.netty.handler.codec.ByteToMessageCodec.channelRead(ByteToMessageCodec.java:103)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:442)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:420)
at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:412)
at io.netty.channel.DefaultChannelPipeline$HeadContext.channelRead(DefaultChannelPipeline.java:1357)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:440)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:420)
at io.netty.channel.DefaultChannelPipeline.fireChannelRead(DefaultChannelPipeline.java:868)
at io.netty.channel.nio.AbstractNioByteChannel$NioByteUnsafe.read(AbstractNioByteChannel.java:166)
at io.netty.channel.nio.NioEventLoop.processSelectedKey(NioEventLoop.java:796)
at io.netty.channel.nio.NioEventLoop.processSelectedKeysOptimized(NioEventLoop.java:732)
at io.netty.channel.nio.NioEventLoop.processSelectedKeys(NioEventLoop.java:658)
at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:562)
at io.netty.util.concurrent.SingleThreadEventExecutor$4.run(SingleThreadEventExecutor.java:998)
at io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74)
at java.base/java.lang.Thread.run(Unknown Source))

This doesn't come into play at all. You need to put the root CA certificate that you're using to sign all of your OPC UA certificates into $IGNITION/data/config/local/com.inductiveautomation.opcua/client/security/pki/trusted/certs if you want the client to trust all servers it encounters when they have a valid certificate chain signed by that CA.

Careful with your terminology. It's not self-signed if you're dealing with CAs.

Thank you for the input. We will check what happens if we add the Root and Intermedate CA to the pki/trusted

CA-signed not self-signed is correct!

The certs might already be there since you've been marking them trusted from the quarantined area.

That they keep showing back up means that on subsequent connection attempts the certificates still aren't trusted for some reason.

You'll probably end up having to turn on loggers and share your certificates, via support if you don't want to do it publicly.

After we added the root certificate, we did not receive any certifcate from the server - no intermediate and no leaf. But we still have the connection failure.

We also found out, that the certificate from the server does not have the Non-repudiations Key Usage. Maybe thats one of the issue

We opened a ticket as we can't share all certification information publicly.

@Kevin.Herron Are there any mandatory Key Usages Ignition expects from the server certificates beside these two:

image

All OPC UA applications should have the same expectations for application instance certificates: UA Part 6: Mappings - 6.2.2 Application Instance Certificate

So yes, it seems you are missing 2:

For RSA keys, the keyUsage shall include digitalSignature, nonRepudiation, keyEncipherment and dataEncipherment

Hi Kevin, just want to clarify if we did it correct.

  • Root is in trusted
  • Intermediate is in issuer
  • Leaf does not appear anymore.

Is this the expected behavior. Does Ignition check the leaf in memory accepts it and never puts it to trusted?

I think that setup would be okay. You might as well have them both in trusted, but as long as one of the chain components is in trusted it should work.

If you configured it with root in issuers and intermediate trusted that means that the root is valid for the purpose of building the chain of trust, but it's the intermediate that grants automatic trust of leafs. This way you can have a corporate root and e.g. an intermediate per "factory" or "location" or whatever, and you would only implicitly trust leaf certs from that one intermediate, instead of all leaf certs issued underneath the root.

But having them swapped like you do should not matter.