Decide which of three limits you have hit before you buy a bigger instance. One core pegged with the rest idle points at per-flow crypto work. Every core saturated during a transfer points at aggregate capacity. Low CPU with a rising retransmit count points at the path. Each one has a different first move, and only one of them is answered by a larger instance.

Three limits, one table, three fixes

LimitReading during a transferFirst move
Single threadone core near 100%, siblings idlespread flows across workers
Aggregate CPUevery core busy, load average climbscapacity, or less work per byte
Path or providerlow CPU, retransmits or steal time highMTU, loss, host contention

Read the rows as diagnoses rather than stages. A single thread at 100% while the others idle is the most common result on a gateway carrying one large tunnel, because encryption for that flow runs on one core. All cores busy during the transfer means the box is already doing as much work as it can. Low CPU with climbing retransmits means the processor is waiting on a network that keeps dropping data.

Fix them in that order of evidence. Spreading flows across workers helps the first case and does nothing for the second. A bigger instance helps the second and wastes money on the first. An MTU or loss fix is the only one that touches the third. Buying capacity before you know which row you are in is the expensive version of guessing.

Separate a pinned thread from a busy host

Start with per-core utilization, so one pinned core cannot hide inside an average. A single run during the transfer shows whether the load is flat or spiky. Both shapes are informative, and a spiky profile often means the sampling window caught scheduler activity rather than encryption.

mpstat -P ALL 1 5

Attribute the busy core to a process, then to a thread. The follow-up run below keeps parent and child threads together, which matters when the daemon forks per connection:

pidstat -u -t -p $(pgrep -f 'openvpn|wireguard|ipsec') 1 10

Kernel crypto never appears in a userspace process counter. Sample threads on their own and compare that view with the process accounting:

top -H -b -n 1 | head -20

Read the socket counters last, because they decide whether more CPU would help at all. Retransmits climbing while CPU stays flat is not a processor problem. Keep the transfer running while you sample, or the numbers describe an idle machine:

ss -ti | grep -E 'retrans|cwnd|rtt'

A controlled transfer, run twice

Idle counters prove nothing, and a benchmark against a random endpoint proves very little. Pick one client, one destination and one representative transfer you are allowed to run, then capture throughput and interactive delay in the same window. A gateway can saturate while a bulk copy still completes, and the delay curve is what users actually feel. Write the transfer, the client count and the daemon version next to the numbers.

Repeat the run with several parallel flows. Scaling up across flows suggests per-flow windowing; staying flat with one core pegged suggests processing.

Then repeat once more with optional workloads stopped. A backup, a log shipper or a database on the same host can produce a busy average that has nothing to do with tunnels. If CPU is still pinned with the host otherwise quiet, the evidence points at the VPN process. If it drops, you were measuring the neighbour, and the gateway was never the problem.

Rule out the path before buying hardware

Rising retransmits, a shrinking congestion window and growing round-trip time all point away from the processor. Compare the tunnel MTU with the path MTU, check the underlying link for loss, and confirm the client's own uplink is not the limit. A bigger gateway does not repair a link that drops packets, and users describe both faults in the same words. The retransmit counter separates them in minutes.

On a virtual server, add the provider's metrics to the guest's own. Steal time is how oversubscription becomes visible inside the guest, and no tuning inside the guest removes it. Guest counters cannot see the host, so a clean guest graph is not proof of a healthy host.

One change, one measurement

Choose the hypothesis the evidence supports, then change exactly one supported setting: worker count, a documented offload option, or the instance size. OpenVPN publishes a performance troubleshooting guide on the same split between processing and transport: OpenVPN performance troubleshooting.

Confirm the offload option is actually active instead of trusting the flag, then rerun the identical transfer and compare like with like. That is the only way to tell a real improvement from a quiet afternoon. Keep a checkpoint you can restore, because the second change is where people lose track of which one helped. One variable per run is slow for a day and fast for a month.

Do not weaken authentication or the cipher suite to make the graph look better. If CPU still does not track the symptom, return to path and application checks rather than upgrading on a guess.

Winndannde jowitiiɗe heen