opcua-cli · v4.3.x
Docs · Connecting

Trust store workflow

First contact with a secured server fails until you trust its certificate. Run trust, then re-run your command. The CLI prints the exact follow-up needed.

The CLI's trust store is a directory of accepted server certificates, identified by SHA-1 fingerprint (hex pairs joined by :). Most secured OPC UA servers will be rejected on first connect — their cert isn't in the store yet. This page is the canonical workflow to get past that.

Trust store has to be opted in. The CLI does not install a default trust store on its own. Pass --trust-store=<path> (or --trust-policy=...) on the command line; only then will the underlying FileTrustStore apply its default path resolution (~/.opcua/ on POSIX, %APPDATA%\opcua\ on Windows).

The path

text flow
1. Connect with security on        →  Fails: UntrustedCertificateException
2. CLI prints follow-up commands   →  "opcua-cli trust <endpoint>"
3. Run trust                       →  Server certificate stored
4. Re-run the original command     →  Succeeds

The CLI puts the follow-up commands directly in the error output — you don't memorise the sequence.

In action

Step 1 — first connect fails

bash terminal — first attempt
$ opcua-cli browse opc.tcp://plc.local:4840 \
      -s Basic256Sha256 -m SignAndEncrypt \
      --cert=/etc/opcua/client.pem --key=/etc/opcua/client.key

Error: Server certificate not trusted.
  Fingerprint: a1b2c3d4e5f6...

To trust this certificate, run:
  opcua-cli trust opc.tcp://plc.local:4840

To list trusted certificates:
  opcua-cli trust:list

To skip trust validation for this command:
  opcua-cli browse ... --no-trust-policy

Exit code: 1. The CLI exits non-zero with a non-empty stderr — suitable for CI failure detection.

Step 2 — trust the cert

bash terminal — trust
$ opcua-cli trust opc.tcp://plc.local:4840 --trust-store=/etc/opcua/trust

Status:      Trusted
Fingerprint: a1:b2:c3:d4:e5:f6:78:90:12:34:56:78:90:12:34:56:78:90:ab:cd
Subject:     PLC-Server
Expires:     2027-01-01T00:00:00+00:00

Verify the fingerprint matches the one you expect (per device documentation, vendor email, out-of-band channel). On a hostile network, the download itself is the attack surface; see Securing the bootstrap below.

Step 3 — retry

bash terminal — retry succeeds
$ opcua-cli browse opc.tcp://plc.local:4840 \
      -s Basic256Sha256 -m SignAndEncrypt \
      --cert=/etc/opcua/client.pem --key=/etc/opcua/client.key

Server (Object)
DeviceSet (Object)
Aliases (Object)

The trust store has the cert; subsequent connects validate successfully.

Trust policies

The CLI sends --trust-policy=... to control how strict the validation is:

Policy Validation
(default — not set) Accept anything (insecure; equivalent to --no-trust-policy)
fingerprint Cert's SHA-1 fingerprint must be in the trust store
fingerprint+expiry Fingerprint match and cert within its validity window
full Full X.509 chain validation against the CA bundle in the trust store

Default is no trust policy — the CLI does not validate the cert unless you set --trust-policy. This is convenient for dev but unsafe for production:

bash terminal — production posture
opcua-cli browse opc.tcp://plc.local:4840 \
    -s Basic256Sha256 -m SignAndEncrypt \
    --cert=/etc/opcua/client.pem --key=/etc/opcua/client.key \
    --trust-policy=fingerprint+expiry

fingerprint+expiry is the production default — strict fingerprint matching plus expiry check.

Custom trust store location

Both trust and the connect-time validation accept --trust-store=PATH:

bash terminal — custom store
opcua-cli trust opc.tcp://plc.local:4840 --trust-store=/etc/opcua/trust
opcua-cli browse opc.tcp://plc.local:4840 ... --trust-store=/etc/opcua/trust

Use a system-wide path (/etc/opcua/trust) for shared deployments; use --trust-store="$HOME/.opcua/trust" (or any per-user path) for per-user installs. Remember the CLI does not install a default store for you — at least one trust flag must be on the command line for any trust-related behaviour.

Skipping validation temporarily

--no-trust-policy disables trust validation for one command — useful for one-off diagnostic invocations against a server you don't intend to trust permanently:

bash terminal — skip
opcua-cli endpoints opc.tcp://unknown-server:4840 --no-trust-policy

The endpoints command runs over a transient unsecured channel anyway, so this is fine for discovery. Do not ship --no-trust-policy to production for non-discovery commands — it eliminates the protection that trust store provides.

Securing the bootstrap

opcua-cli trust downloads the cert over an unsecured connection. On a trusted network this is fine; on a hostile one, an attacker between you and the server can substitute their cert and you record the attacker's identity as trusted.

Two safer alternatives:

  1. Out-of-band cert delivery. Have the vendor / operator bring you the server cert via USB / signed email / encrypted bundle. Verify the fingerprint matches their documentation. Then trust:add it directly (currently library-only — for the CLI workflow, copy the .der into the trust-store directory by hand).

  2. Bootstrap over a known-good network. Run trust from a commissioning workstation on a physically-controlled segment. Lock down the trust store afterwards.

For production deployments, treat the trust step like a package signing key — pinned out-of-band, audited.

Listing and removing

Command What
opcua-cli trust:list Print every trusted cert
opcua-cli trust:list --json Same, machine-readable
opcua-cli trust:remove <fingerprint> Drop a cert by fingerprint

Use trust:remove when:

  • The server rotates its certificate (the new fingerprint arrives; the old one is dead weight).
  • A device is decommissioned.
  • A cert was added by mistake.

See Commands · trust for the full reference.

CI pattern

Trust rollouts in CI are typically scripted:

bash ci pattern
# In the CI setup step:
mkdir -p ~/.opcua/trusted
# Drop the pre-vetted cert files into ~/.opcua/trusted/

# Test runs without needing online trust calls
opcua-cli browse opc.tcp://test-server:4840 \
    -s Basic256Sha256 -m SignAndEncrypt \
    --cert=$CLIENT_CERT --key=$CLIENT_KEY \
    --trust-policy=fingerprint+expiry

The CI runner gets the trusted certs pre-installed; the test runs in --trust-policy=fingerprint+expiry posture. No live trust call needed — and no hostile-network exposure during bootstrap.

See Recipes · Batch trust rollout for the operator-side script.