A policy that allows one session per account sounds settled until the second login arrives. Two outcomes are possible: the newcomer replaces the incumbent, or the incumbent keeps the slot and the newcomer is refused. Only the second is a first-session-wins policy. The client option most people reach for enables concurrency and therefore does not produce it.

Two contracts hide behind one setting

With concurrency disabled, a second connection presenting the same certificate name can displace the session already running. Turning concurrency on permits several sessions at once, which is the opposite of a limit. Neither state is a rejection policy, so the decision has to be made somewhere else entirely.

Write the contract down before implementing anything: who is refused, what evidence identifies the incumbent, and what happens when a session dies without a clean disconnect. A policy nobody has stated in those terms cannot be tested later.

Eviction lands before your connect hook

In the 2.6 server code path, early setup for a new client removes existing instances with the same name when concurrency is disabled. That removal happens before the connect handler chain runs. A script that notices the duplicate and returns failure therefore rejects the newcomer after the incumbent has already been dropped, which is precisely the outcome the policy was meant to prevent. The callback order in the upstream implementation is the only reliable answer for your build.

StageWho can still refuseEffect on the incumbent
Certificate checkServer trust configurationNone
Authentication decisionThe authentication controllerNone, because establishment has not run
Connect handlerA profile scriptAlready evicted in the same-name case
Post-establishmentStatus tooling, monitoringBoth sessions already exist or one is gone

Polling the status file is not a gate either. Two attempts arriving together can both observe an empty slot before either commits, and both proceed.

Move the decision into authentication

The management interface can take over the authentication decision after certificate verification. In that arrangement the controller is not a monitor: it receives each connecting client instance and answers whether that instance may proceed. Because the answer arrives before establishment, a refusal leaves the incumbent untouched. The same channel reports disconnects, which is how the controller learns that a slot was legitimately released.

Every new instance has to pass that gate, including reconnects and any path that re-authenticates without a prompt. A gate that some instances never reach protects nothing, and the omission stays invisible until somebody exercises the bypass.

Tokens can walk past the gate

Once a token has been issued, the server can validate it internally and skip external authentication for that connection. The management call never happens, so a slot check implemented only there never sees the new instance. That behaviour is a convenience for users and a hole for a concurrency policy. Make token-authenticated connections pass an equivalent authoritative check, or disable the token path when the policy demands a decision on every connection.

Do not compensate by accepting every token unconditionally. An expired or invalid token still has to fail, and it has to fail through the same decision point as everything else.

Bind the reservation to an instance, not an address

An address is poor identity: a reconnect from the same laptop can arrive from a different one, and a copied credential can arrive from the same one. Key the reservation to the client instance the server assigns, so that a re-authentication of the already admitted instance is recognised as the owner. A new instance identifier has to acquire its own slot through the first-wins decision even when it presents the same token.

Release only the reservation belonging to the attempt that failed. A delayed disconnect event from an older attempt must not clear the slot of a newer owner, which is why the release names the instance rather than the account. After the controller restarts, reconcile the live sessions before admitting anyone, and apply the same rule on every gateway that serves the account.

Decide separately how a genuinely stale slot is recovered. An administrator needs a documented way to clear it, and that path should not be reachable by a user who simply clicks reconnect.

Test the pairs that matter: two near-simultaneous logins, a reconnect after a clean teardown, a re-authentication of the incumbent, a denied newcomer, and a controller restart. In every case the incumbent must stay usable and the newcomer must be refused without collateral damage to other accounts.

Tënë sô zo