Session Manager
Getting Started
Introduction InstallationUsage
Usage Connections Session-manager Logging-caching SecurityReference
Testing Examples Auto-publishSession Manager
Overview
PHP's request/response lifecycle destroys all state at the end of every request -- including TCP connections. OPC UA requires a 5-step handshake (TCP -> Hello/Ack -> OpenSecureChannel -> CreateSession -> ActivateSession) that adds 50-200ms per request.
The session manager solves this with a long-running daemon that holds OPC UA connections in memory. PHP requests communicate with the daemon via a lightweight Unix socket IPC protocol.
The session manager is entirely optional. If the daemon is not running, the bundle falls back to direct connections with zero code changes.
Without daemon:
Request 1: [connect 150ms] [read 5ms] [disconnect] -> ~155ms
Request 2: [connect 150ms] [read 5ms] [disconnect] -> ~155ms
With daemon:
Request 1: [open session 150ms] [read 5ms] -> ~155ms (first only)
Request 2: [read 5ms] -> ~5ms
Request N: [read 5ms] -> ~5msStarting the Daemon
php bin/console opcua:sessionThe daemon creates a Unix socket at var/opcua-session-manager.sock (configurable). The OpcuaManager detects this socket and routes traffic through the daemon automatically.
Command Options
php bin/console opcua:session \
--timeout=600 \
--cleanup-interval=30 \
--max-sessions=100 \
--socket-mode=0600| Option | Default | Description |
|---|---|---|
--timeout |
600 |
Session inactivity timeout in seconds |
--cleanup-interval |
30 |
Interval between expired session checks |
--max-sessions |
100 |
Maximum concurrent OPC UA sessions |
--socket-mode |
0600 |
Unix socket file permissions (octal) |
All options can also be configured via YAML config.
Configuration
# config/packages/php_opcua_symfony_opcua.yaml
php_opcua_symfony_opcua:
session_manager:
enabled: true
socket_path: '%kernel.project_dir%/var/opcua-session-manager.sock'
timeout: 600
cleanup_interval: 30
auth_token: '%env(OPCUA_AUTH_TOKEN)%'
max_sessions: 100
socket_mode: 0600
allowed_cert_dirs: ~
log_channel: ~
cache_pool: cache.app
auto_publish: false| Key | Default | Description |
|---|---|---|
enabled |
true |
Enable daemon auto-detection |
socket_path |
%kernel.project_dir%/var/opcua-session-manager.sock |
Unix socket path |
timeout |
600 |
Session inactivity timeout (seconds) |
cleanup_interval |
30 |
Expired session check interval |
auth_token |
null |
Shared secret for daemon IPC |
max_sessions |
100 |
Maximum concurrent sessions |
socket_mode |
0600 |
Socket file permissions |
allowed_cert_dirs |
null |
Certificate directory whitelist |
log_channel |
null |
Monolog channel name (null = default logger) |
cache_pool |
cache.app |
Symfony cache pool service ID (PSR-6, wrapped to PSR-16) |
auto_publish |
false |
Auto-publish for sessions with subscriptions |
When log_channel is set to a channel name (e.g. opcua), the bundle resolves the monolog.logger.opcua service and injects it into both the daemon and all clients. When null, the default Symfony logger service is used.
When cache_pool is set, the bundle wraps the specified PSR-6 cache pool with Symfony\Component\Cache\Psr16Cache and injects the resulting PSR-16 cache into all clients.
Production Deployment
Use systemd to keep the daemon running in production:
[Unit]
Description=OPC UA Session Manager
After=network.target
[Service]
User=www-data
WorkingDirectory=/path/to/symfony-project
ExecStart=/usr/bin/php bin/console opcua:session
Restart=always
RestartSec=5
[Install]
WantedBy=multi-user.target
Enable and start:
sudo systemctl enable opcua-session-manager
sudo systemctl start opcua-session-managerCheck status:
sudo systemctl status opcua-session-manager
sudo journalctl -u opcua-session-manager -fArchitecture
+--------------+ +------------------------------+ +--------------+
| PHP Request | --IPC-->| Session Manager Daemon | --TCP-->| OPC UA |
| (short- |<--IPC-- | |<--TCP-- | Server |
| lived) | | * ReactPHP event loop | | |
+--------------+ | * Sessions in memory | +--------------+
| * Periodic cleanup timer |
+--------------+ | * Signal handlers |
| PHP Request | --IPC-->| |
| (reuses |<--IPC-- | Sessions: |
| session) | | [sess-a1b2] -> Client (TCP)|
+--------------+ | [sess-c3d4] -> Client (TCP)|
+------------------------------+The daemon:
- Listens on a Unix socket for incoming IPC requests
- Creates OPC UA
Clientinstances for each session - Forwards OPC UA operations from PHP to the client
- Serializes/deserializes all OPC UA types over JSON IPC
- Cleans up expired sessions based on inactivity timeout
- Gracefully disconnects all sessions on SIGTERM/SIGINT
- Tracks active subscriptions and transfers them on reconnection
- Manages certificate trust store state across sessions
- Supports
modifyMonitoredItems()andsetTriggering()forwarding
IPC Config Keys
When the daemon creates a ManagedClient for each session, the following additional IPC config keys are forwarded from the connection config:
| Key | Type | Default | Description |
|---|---|---|---|
trustStorePath |
string|null |
null |
Path to the trust store directory for certificate persistence |
trustPolicy |
string|null |
null |
Trust validation policy: fingerprint, fingerprint+expiry, or full |
autoAccept |
bool |
false |
Automatically accept unknown server certificates (TOFU mode) |
autoDetectWriteType |
bool |
true |
Auto-detect OPC UA type on write() when type is omitted |
readMetadataCache |
bool |
false |
Cache non-Value attribute reads (DisplayName, DataType, etc.) |
These map directly to the ManagedClient methods:
$client->setTrustStorePath('/var/opcua/trust');
$client->setTrustPolicy(\PhpOpcua\Client\TrustStore\TrustPolicy::Fingerprint);
$client->autoAccept(true);
$client->setAutoDetectWriteType(true);
$client->setReadMetadataCache(true);Security
The daemon supports multiple security layers:
- IPC authentication --
auth_tokenvalidated with timing-safe comparison - Socket permissions --
0600by default (owner-only access) - Method whitelist -- only documented OPC UA operations allowed
- Session limits -- configurable maximum to prevent resource exhaustion
- Certificate path restrictions --
allowed_cert_dirsconstrains certificate file access - Trust store integration -- certificate trust decisions persisted via
FileTrustStore
Checking Daemon Status
use PhpOpcua\SymfonyOpcua\OpcuaManager;
class DiagnosticsController
{
public function __construct(private readonly OpcuaManager $opcua) {}
public function status(): Response
{
if ($this->opcua->isSessionManagerRunning()) {
// Daemon is running -- ManagedClient will be used
} else {
// Daemon is not running -- direct Client will be used
}
}
}ManagedClient Methods
The ManagedClient (used when the daemon is running) supports the following methods in addition to the standard OpcUaClientInterface:
| Method | Description |
|---|---|
setTrustStorePath(string $path) |
Set the directory for persisting trusted certificates |
setTrustPolicy(TrustPolicy $policy) |
Set the trust validation policy |
autoAccept(bool $enabled) |
Enable/disable automatic certificate acceptance |
setAutoDetectWriteType(bool $enabled) |
Enable/disable write type auto-detection |
setReadMetadataCache(bool $enabled) |
Enable/disable read metadata caching |
setEventDispatcher(EventDispatcherInterface $dispatcher) |
Attach a PSR-14 event dispatcher |
trustCertificate(string $der) |
Programmatically trust a server certificate |
untrustCertificate(string $der) |
Remove a previously trusted certificate |
modifyMonitoredItems(int $subId, array $items) |
Modify sampling/filter of existing monitored items |
setTriggering(int $subId, int $triggeringItem, array $linksToAdd, array $linksToRemove) |
Configure triggered monitoring links |