Two servers with the same CPU count can sustain very different throughput, because the number that matters is how many cores the active data path can actually use. A single user-space daemon processing one client's packets is limited by one core, while an offloaded kernel path can spread that work. Neither statement tells you what a given machine will do for your traffic, which is why measurement comes before core counting.

The comparison below is about where packets are processed, not about which product is faster in the abstract. Those are different questions with different answers.

One core per flow on the user-space path

In a traditional Community OpenVPN instance the data channel is processed in user space, and a busy connection can pin one CPU while overall machine utilisation stays unremarkable. That is why adding cores to a host already saturated on a single flow changes nothing: the scheduler has no way to divide that work. The per-client structure bounds one client's transfer by one core's capacity, whatever the total core count. Read per-core utilisation during a test instead of the aggregate figure, or the bottleneck will look like headroom.

Running several server instances on one host distributes separate clients across processes. It does not parallelise one existing connection, and it costs separate pools, listeners and routing decisions.

The other path: packets in the kernel

Data Channel Offload moves supported packet handling and cryptography into the kernel, where the work can use several cores. That changes the capacity question from one core to the machine, but only for configurations the implementation supports: a compatible kernel module, an eligible data cipher, and a configuration without the features offload excludes. Installing a module is not the same as using it, so confirm the active mode from the connection's own log or status output. A version banner proves what is available, not what is running.

Offload does not remove authentication or the control channel, and it does not turn every deployment into a multi-core one. It is local acceleration of a compatible data path, and the requirements differ by platform and version.

What the comparison actually decides

The real choice is between accepting a per-flow ceiling, spreading clients across instances, and moving to a supported offload configuration. Each addresses a different problem, and picking the wrong one costs money without moving the number. The test that matters is the one shaped like the way the service is used.

Measure three shapes: one client with one transfer, one client with several flows, and several clients together. A high total in the third does not prove that a single download can reach it, and a slow single transfer does not prove the server has run out of aggregate capacity. The results answer different questions and both are worth having.

Only the middle shape tells you whether concurrency inside one client helps, which is usually a property of the transport and the path rather than of the server. Only the first tells you the ceiling a single application will see. Keep the workload identical between runs so the comparison means something.

Measuring so the number means something

Record the server and client versions, the active data path, the negotiated data cipher, the transfer direction, packet sizes, flow count and duration. Watch per-core activity and the packet and queue drops at both ends, because the client's own interface, the provider network or the destination service can become the limit first. Change one variable at a time, since a run that alters cipher, compression, MTU and offload together cannot tell you which one mattered. Keep the access policy you actually intend to deploy during the test, because a lab arrangement nobody will run proves little.

Then leave headroom for the control channel, for renegotiation and for everything else that runs on the host.

Report what the configuration sustained under the intended concurrency rather than a rate extrapolated from somebody else's hardware. A number with its test conditions attached is useful; a promise without them is a guess that will be quoted back at you.

Ũhoro ũhĩtĩte