BadTcpServerTooBusy
BadServer limits
BadTcpServerTooBusy is the server refusing a new transport connection because it is at capacity. It is returned early, before any secure channel exists, so it is a load signal rather than a configuration error. Back off with jitter, stagger client connects, and reduce concurrent connection attempts.
What you actually see
New connections are refused at the handshake while existing clients keep working, and the refusals cluster at plant startup or after a server restart.
Why it happens
- Connect stormEvery client dials at once after a power event or a server restart. Session establishment is CPU-bound on the RSA handshake and certificate chain validation, so the server queues behind cryptography. Handshakes time out, clients retry, and the retries make it worse - a positive-feedback loop.
- Transport-level connection limit reachedServers cap concurrent transport connections independently of session count. A fast deterministic refusal at this layer is the correct server behaviour and is preferable to accepting a connection it cannot service.
- Connection leak on the client sideClients that open a channel per operation, or fail to close channels after a timed-out handshake, accumulate transport connections. The server hits its cap with only a few real clients present.
- CPU starvation from something else on the boxAn embedded server sharing a CPU with the PLC control task, or a virtual machine with a noisy neighbour, cannot complete handshakes fast enough. The refusal is a symptom of the host, not of OPC UA configuration.
- Scanning or probing trafficVulnerability scanners and asset-discovery tools open and abandon connections to port 4840 in bulk. On a small embedded server this alone can exhaust the transport connection budget.
How to fix it
In diagnostic order: work down, do not skip ahead.
- Stagger the client connectsAdd a randomised startup delay and bounded exponential backoff with jitter to every client. Staggering connects is the most effective client-side measure against connect storms and it usually resolves this on its own.
- Honour the refusal instead of hammeringOn BadTcpServerTooBusy, wait and retry with an increasing delay; do not retry immediately. An immediate retry orphans the previous attempt's server-side state and deepens the queue.
- Count connections and sessions on the serverBrowse Server_ServerDiagnostics for the session summary, and check the OS-level count with ss -tn state established '( sport = :4840 )'. A large gap between transport connections and live sessions means abandoned handshakes.
- Reduce connections per clientOne session per client per endpoint, many subscriptions inside it. Collectors configured with a session per tag group multiply both connections and handshake CPU for no benefit.
- Give the server CPU headroom or raise the capOn a software server, check CPU during a connect burst before raising the connection limit. On an embedded server the cap reflects real constraints - reduce demand instead.
- Keep scanners off the portExclude OPC UA ports from aggressive scanning schedules, or restrict them to a maintenance window. This is a common and easily missed source of load on PLC-embedded servers.
Where it shows up, by tool and stack
| Tool / stack | What to do there |
|---|---|
| UA-.NETStandard (C#) | The reference server returns this fast and deterministically under load by design, with limits in the ServerConfiguration. It establishes on the order of a thousand or more concurrent sessions cleanly on a typical multi-core machine; beyond that, throughput is bounded by handshake CPU. |
| Siemens S7-1500 | The integrated server shares the controller CPU with the control task. Connect bursts and large browses both compete with scan-cycle work, so refusals here often mean you need fewer clients rather than more configuration. |
| open62541 (C) | Single-threaded by default, so a slow handshake blocks the accept loop. If you need many concurrent connects, use the multithreaded build or reduce concurrency on the client side. |
| node-opcua | connectionStrategy with maxDelay and randomisation gives you backoff with jitter without extra code. Set maxRetry deliberately rather than leaving an unbounded retry loop running. |
| OPC Publisher | Stagger endpoint startup rather than declaring hundreds of endpoints that all dial at once, and watch the reconnect metrics after a restart of the module. |
What it looks like in the log
Hello rejected: BadTcpServerTooBusy
Refusing new connection from 10.20.3.15: connection limit reached
Handshake queue depth 42; refusing connections until it drains
Confirm the fix
Restart the server with staggered client startup and confirm every client connects on its first or second attempt, with server CPU staying below saturation through the burst.
Stop it happening again
Mandate backoff with jitter and staggered startup in client configuration standards, keep one session per client per endpoint, and alarm on refusal counts so a growing client population is visible before it becomes an outage.
Stuck on more than one of these? The troubleshooting clinic works through broken configurations end to end, on camera.
Troubleshooting clinic →Last verified 7 September 2026 against OPC UA 1.05.07. OPC UA Connect is an educational resource and is not affiliated with the OPC Foundation.