A rule that matched yesterday stops matching today because the client came back on a different tunnel address. The allocator is not random, though, and knowing its order turns that surprise into a predictable event. It prefers the address the same client held before, falls back to the least recently used free one, and forgets everything unless you give it somewhere to remember. Rules that assume a lease keep matching until the day they quietly do not.

The order the allocator follows

With one session per common name, the allocator first looks for an address that the same client used previously and that is currently free. If that address is gone, it takes the free address that has been idle longest. With concurrent sessions enabled for the same name, the previous-address preference does not apply and the first free address is used instead, because the sessions are treated as separate peers.

SituationAddress chosenPractical effect
Reconnect, old address still freeThe remembered addressRules appear stable for days
Old address taken by another clientLeast recently used free addressA working rule silently stops matching
Concurrent sessions, same nameFirst free address, per sessionOne identity can hold several leases

Two consequences follow immediately. A client that reconnects quickly usually keeps its address, which is why nobody notices the mechanism until it changes. And a different device that connects during that small window can take the address while it is briefly free, after which the first client receives something else and every rule keyed to the old number is wrong. Neither event produces a log line that looks like a fault.

Why a lease looks stable and then moves

Stability here is an emergent property of reconnects arriving before anyone else claims the address. It is not a promise, and it is not enforced while the client is disconnected. Plan for the address to change rather than being surprised by it. A pool that is barely larger than the device count makes collisions routine, because the least-recently-used fallback has almost nothing to choose from.

Peak concurrency is the number to size against, not the number of users. Someone testing from two devices at once, or a client that reconnects before the old session times out, occupies two leases for a while. A pool sized for the headcount alone will produce exactly the churn people blame on the server.

The persist file remembers, it does not reserve

An address that survives a service restart needs a persist file. It stores the client name and address at shutdown and reads them back when the service starts, which is why a planned restart no longer reshuffles every rule you wrote.

ifconfig-pool-persist /etc/openvpn/ipp.txt
server 10.8.0.0 255.255.255.0

What it does not do is hold the address while the client is away. During that gap the allocator is free to give the number to somebody else, and the file then records a stale preference that the next connection cannot honour. Treat it as a hint that improves restart behaviour, not as static addressing.

Check the pool against every routed subnet while you are in the file. An overlapping range hands out addresses that the server cannot route to, and the symptom is a client that connects, receives an address, and reaches nothing.

Pinning an address properly

When a firewall rule or a DNS record genuinely needs a fixed address, assign it rather than hope for it. A per-client configuration entry can push an address and mask directly, and that entry is keyed by the effective client name.

# client-config-dir entry for "laptop-acme"
ifconfig-push 10.8.0.50 255.255.255.0

Keep fixed addresses outside the dynamic pool so the allocator never hands the same number to another session. Then check the tunnel topology, because it changes how many pool addresses each client consumes: the older point-to-point layout burns a small block per client while a subnet layout uses one address each. A pool that looks generous under one topology can be exhausting under the other.

Confirm the result on the client instead of trusting the server's bookkeeping. Once the session is back, look at the interface address and the pushed route, because a log on the server may report the address that was requested while the interface itself holds another.

The remote address is a separate question

The address in the client's remote line must be reachable before any tunnel exists. Putting a pool address there, such as the server's own tunnel endpoint, means the client tries to reach the endpoint through a tunnel it has not built yet, and the result is a loop or a silent timeout rather than a shortcut.

Use the public endpoint the operator supplied, and keep the pool for what it was designed for: addresses inside the tunnel. Recording the pool range, the fixed assignments and the persist path in one place saves the next person from treating every lease as arbitrary.

ተዛመድቲ ጽሑፋት