opcua-client · v4.3.x
Docs · Connection

Endpoints and discovery

Before opening a channel, the client asks the server which endpoints it offers — what URLs, what security policies, what user-token policies. Discovery is how those answers find their way into the connection you actually open.

An OPC UA server exposes one or more endpoints. Each endpoint is a URL plus a security policy, a security mode, and a list of accepted identity-token policies (anonymous, username, certificate). A server that supports Basic256Sha256 + Sign and Basic256Sha256 + SignAndEncrypt advertises two endpoints, not one.

Discovery is the GetEndpoints service call that returns the full catalogue, plus the server certificate and the identity-token policy IDs the client needs in order to authenticate correctly.

Listing endpoints

php examples/list-endpoints.php
use PhpOpcua\Client\ClientBuilder;

$client = ClientBuilder::create()->connect('opc.tcp://localhost:4840');

foreach ($client->getEndpoints('opc.tcp://localhost:4840') as $ep) {
    printf(
        "%-50s %-10s %-20s\n",
        $ep->endpointUrl,
        $ep->securityMode->name,
        basename($ep->securityPolicyUri)
    );
}

$client->disconnect();

Output, against a real server, looks like:

text sample output
opc.tcp://plc.local:4840                          None       None
opc.tcp://plc.local:4840                          Sign       Basic256Sha256
opc.tcp://plc.local:4840                          SignAndEncrypt  Basic256Sha256
opc.tcp://plc.local:4840                          Sign       Aes256Sha256RsaPss
opc.tcp://plc.local:4840                          SignAndEncrypt  Aes256Sha256RsaPss

Each row is an EndpointDescription DTO:

Parameters
$endpointUrl
string required

The discovery URL — usually the same opc.tcp://host:port you would connect to. Discovery does not require authentication or security; the call runs over a transient, unencrypted channel that the client opens and closes around the request.

$useCache
bool optional default true

When true, results are cached per endpoint URL for 300 seconds via the configured PSR-16 cache. Pass false to force a fresh GetEndpoints round-trip — useful when a server's endpoint list changes (rare) or when debugging.

The returned EndpointDescription carries every field you need to configure the next connection:

Property Use
endpointUrl The exact URL to pass to connect() — may differ from the discovery URL (load-balanced setups, multi-homed servers).
securityPolicyUri Pass to SecurityPolicy::from() to recover the enum case.
securityMode MessageSecurityMode — None / Sign / SignAndEncrypt.
serverCertificate DER-encoded server cert. Feed it to the trust store, or pin it.
userIdentityTokens Array of UserTokenPolicy — one per acceptable auth mode.
transportProfileUri Always …UA-TCP UA-SC UA-Binary for this library.
securityLevel Server-assigned hint; higher is stronger. Not authoritative — pick by policy + mode you trust, not by this number.

Discovery runs automatically on connect()

You rarely need to call getEndpoints() by hand. ClientBuilder::connect() runs discovery for you whenever the client needs information it does not yet have — most often: the server's certificate (if security is on and no certificate is configured), or any identity-token policy ID (anonymous, username, certificate).

Changed in v4.3.1 the discovery

trigger now fires whenever the anonymous, username, or certificate policy ID is unknown. Before v4.3.1 it only fired for the anonymous policy; servers advertising non-standard policy IDs for username or certificate auth (open62541, Siemens S7) responded with BadIdentityTokenInvalid.

The flow inside connect() is:

  1. 01

    HEL/ACK transport handshake

    TCP socket open, OPC UA Hello / Acknowledge framing, buffer-size negotiation. No cryptography yet.

  2. 02

    Discovery (only if needed)

    If the client lacks the server certificate or a required policy ID, it opens a transient unsecured channel, sends GetEndpoints, picks the entry whose policy URI + mode match the builder configuration, and closes the channel. Result is cached.

  3. 03

    Open the real secure channel

    OpenSecureChannel (OPN) — sign-only for ECC, sign and/or encrypt for RSA. The server certificate from discovery is the input to the asymmetric key exchange.

  4. 04

    Create and activate the session

    CreateSession + ActivateSession. The identity token (anonymous, username, certificate) is encrypted under the channel keys using the policy ID that discovery returned.

Picking the right endpoint manually

When the same server publishes multiple endpoints with the same policy, the library picks the first match. To pick a different one — for example, an internal opc.tcp://10.x.x.x:4840 URL when the discovery URL is the public one — iterate manually and feed the chosen endpointUrl back into a new builder:

php targeted endpoint selection
use PhpOpcua\Client\ClientBuilder;
use PhpOpcua\Client\Security\SecurityPolicy;
use PhpOpcua\Client\Security\SecurityMode;

$probe = ClientBuilder::create()->connect('opc.tcp://plc.local:4840');

$endpoint = null;
foreach ($probe->getEndpoints('opc.tcp://plc.local:4840') as $ep) {
    if ($ep->securityMode === SecurityMode::SignAndEncrypt
        && $ep->securityPolicyUri === SecurityPolicy::Basic256Sha256->value
    ) {
        $endpoint = $ep;
        break;
    }
}
$probe->disconnect();

if ($endpoint === null) {
    throw new RuntimeException('Server does not offer the required endpoint.');
}

$client = ClientBuilder::create()
    ->setSecurityPolicy(SecurityPolicy::Basic256Sha256)
    ->setSecurityMode(SecurityMode::SignAndEncrypt)
    ->setClientCertificate('/etc/opcua/client.pem', '/etc/opcua/client.key')
    ->connect($endpoint->endpointUrl);

Caching behaviour

Discovery is cached against the PSR-16 cache the builder is configured with — InMemoryCache with a 300-second TTL by default. The cache key includes the endpoint URL hash, so probing the same server from multiple clients in the same PHP process avoids redundant round-trips.

Bypass the cache with getEndpoints($url, useCache: false). Flush all cached entries (browse, resolve, endpoints, discovered types) with $client->flushCache(). See Observability · Caching.