Tutorial: Troubleshoot and Optimize VPN Performance in Access Server
VPN running slow? Diagnose and fix Access Server performance issues step by step — covers CPU and AES-NI checks, DCO configuration, MTU and fragmentation, UDP vs TCP, iperf3 throughput testing, MTR path analysis, and split tunnel optimization.
Overview
This tutorial shows you how to identify and troubleshoot VPN performance issues with Access Server.
A slow VPN connection can result from several different factors, including:
Limited internet or cloud-instance bandwidth.
High CPU utilization.
Insufficient encryption performance.
Packet loss or high latency.
TCP transport behavior.
MTU fragmentation.
Incorrect routing or tunnel configuration.
Unnecessary network hops.
The tests in this tutorial help you distinguish between latency, which measures delay, and throughput, which measures how much data can pass through the connection over time.
You’ll compare performance outside and inside the VPN tunnel, verify hardware and Data Channel Offload acceleration, inspect the network path, and use iperf3 to measure VPN throughput.
Tip
Measure performance, change one variable, and then measure again. Don’t change multiple settings at the same time, or you may not know which change affected the result.
Prerequisites
Before you begin, ensure you have:
Console access to the Access Server host.
Root privileges for commands that inspect or change Access Server settings.
A VPN client from which you can reproduce the performance issue.
Access to a private resource behind Access Server when testing end-to-end throughput.
curl,ping, and other standard networking utilities.Optional:
iperf3, MTR, and OpenSSL for the related tests.
Note
In our documentation, we use example IPv4 addresses and subnets reserved for documentation, such as 192.0.2.0/24, 198.51.100.0/24, and 203.0.113.0/24.
Ensure you replace them with valid IPv4 addresses and subnets for your network(s).
Establish a performance baseline
Before changing Access Server settings, measure your connection speed in two states:
Raw link speed: Your connection speed outside the VPN tunnel. This is the ceiling your VPN connection can never exceed. Test this first using a tool like
iperf3between client and server, or a speed test service.VPN throughput: Your connection speed while connected to the VPN. Run the same test again with the VPN active.
Compare the two results. The difference represents your VPN overhead: the cost of encryption, routing, and protocol processing.
Important
If your raw link speed is already slow, no amount of Access Server tuning will help. Address the underlying network limitations before investigating VPN-specific settings.
Tip
Change one variable at a time. Measure before and after each change. Changing multiple settings at once makes it impossible to know which change had an effect.
Monitor CPU utilization while reproducing the VPN performance issue:
Connect to the Access Server console.
Run:
top -1
Press
1to display utilization for each CPU core.Reproduce the slow VPN connection while monitoring the output.
Check whether:
One core is consistently at or near full utilization.
All cores are heavily utilized.
The
openvpnor Access Server processes are consuming most of the available CPU.CPU utilization remains low while throughput is poor.
Press
qto quit.
High CPU utilization can indicate that encryption, packet processing, or other Access Server workloads are limiting throughput.
Hardware AES acceleration, commonly identified by the AES New Instructions (AES-NI) CPU feature, can significantly improve AES cipher performance.
Run the following to check if your CPU has hardware AES:
grep -m1 -o 'aes' /proc/cpuinfo
Example output:
aes
If
aesappears, the CPU supports hardware AES acceleration.AES-256-GCMwill generally provide strong performance on this hardware.If
aesdoesn't appear, a cipher such asCHACHA20-POLY1305may perform better on the system.
Note
Both AES-256 GCM and CHACHA20-POLY1305 are compatible with Data Channel Offload (DCO). Avoid legacy CBC ciphers for new configurations unless you have a specific compatibility requirement.
Use OpenSSL to estimate the system’s raw encryption capacity. The test measures cipher throughput only. It doesn't include:
OpenVPN packet framing.
Packet identifiers and authentication.
Tunnel interface processing.
Context switching.
Network latency or packet loss.
Therefore, actual VPN throughput will be lower than the OpenSSL result.
To test a single process:
openssl speed -evp aes-256-gcm -seconds 10 -bytes 1420
This tests
AES-256-GCMwith 1,420-byte buffers for 10 seconds.To test multiple CPU cores:
openssl speed -evp aes-256-gcm -seconds 10 -bytes 1420 -multi 2
Example output:
root@openvpnas:~# openssl speed -evp aes-256-gcm -seconds 10 -bytes 1420 -multi 2 Forked child 0 Forked child 11 +DT:AES-256-GCM:10:1420 +DT:AES-256-GCM:10:1420 +R:12206839:AES-256-GCM:10.0000002 +R:12292238:AES-256-GCM:10.0000003 Got: +H:1420 from 0 Got: +F:25:AES-256-GCM:1733371138.00 from 04 Got: +H:1420 from 1 Got: +F:25:AES-256-GCM:1745497796.00 from 15 version: 3.0.13 built on: Tue Jun 2 19:33:25 2026 UTC options: bn(64,64) compiler: gcc -fPIC -pthread -m64 -Wa,--noexecstack -Wall -fzero-call-used-regs=used-gpr -DOPENSSL_TLS_SECURITY_LEVEL=2 -Wa,--noexecstack -g -O2 -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer -ffile-prefix-map=/build/openssl-gza1f8/openssl-3.0.13=. -fstack-protector-strong -fstack-clash-protection -Wformat -Werror=format-security -fcf-protection -fdebug-prefix-map=/build/openssl-gza1f8/openssl-3.0.13=/usr/src/openssl-3.0.13-0ubuntu3.11 -DOPENSSL_USE_NODELETE -DL_ENDIAN -DOPENSSL_PIC -DOPENSSL_BUILDING_OPENSSL -DNDEBUG -Wdate-time -D_FORTIFY_SOURCE=3 CPUINFO: OPENSSL_ia32cap=0xfffa32234f8bffff:0x1c07ab AES-256-GCM 3478868.93k6
-multi 2spawned two independent workers, one per core (Forked child 0, andForked child 1).This means Child 0 encrypted 12206839 buffers of 1420 bytes in 10 seconds.
This means Child 1 encrypted 12292238 buffers of 1420 bytes in 10 seconds.
This means Child 0 processed 1733371138 bytes/s = 12206839 x 1420 = ~17.33GB in 10s (1.733GB/s).
This means Child 1 processed 1745497796 bytes/s = 12292238 × 1420 = ~17.45GB in 10s (1.745GB/s).
The aggregate cipher throughput reported in units of 1000 bytes/s (k = 1000 here, not 1024). So this is 3478868930 bytes/s — and it's simply the sum of the two children (1.733 GB/s + 1.745 GB/s).
Conclusion:
Per core: ~1.73 GB/s ≈ 13.9 Gbit/s
Both cores (aggregate): ~3.48 GB/s ≈ 27.8 Gbit/s
This CPU can encrypt ~14 Gbit/s on a single core
The result indicates that raw AES encryption capacity is unlikely to be the bottleneck at substantially lower VPN speeds.
Important
This is raw cipher throughput only. It doesn't include OpenVPN's per-packet framing, HMAC/packet-ID handling, the tun device, or context-switching overhead, so real tunnel throughput will always be well below this. The point of the test isn't to predict tunnel speed, but it's to confirm whether the CPU's crypto can be ruled out as the bottleneck.
Access Server can run multiple VPN daemons to use the available CPU cores.
Connect to the Access Server console and get root privileges.
Check the number of CPU cores:
sacli GetNCores
Example output:
2
Check the configured VPN daemons:
sacli ConfigQuery | grep "n_daemons"
Example output:
"vpn.server.daemon.tcp.n_daemons": "2", "vpn.server.daemon.udp.n_daemons": "2",
The configured number of TCP and UDP daemons should generally match the number of available CPU cores. To set both values to the detected CPU core count:
sacli --key "vpn.server.daemon.tcp.n_daemons" --value "`sacli GetNCores`" ConfigPut sacli --key "vpn.server.daemon.udp.n_daemons" --value "`sacli GetNCores`" ConfigPut
Restart the Access Server services:
sacli start
Repeat the performance test after the restart.
Data Channel Offload (DCO) moves OpenVPN data-channel processing into the operating-system kernel, reducing context switching and improving VPN throughput.
For background information, refer to OpenVPN Data Channel Offload (DCO).
Verify that DCO is active and used by Access Server:
sacli support | grep ovpn-dco
Example output:
ovpn-dco availability: True, ovpn version: ovpn-net-next/main-7.0.0-rc4-48671cb
The output should report that DCO is available.
Verify the DCO interfaces:
ip -details link show | grep ovpn
Example output:
ovpn addrgenmode random numtxqueues 1 numrxqueues 1 gso_max_size 65536 gso_max_segs 65535 tso_max_size 65536 tso_max_segs 65535 gro_max_size 65536 ovpn addrgenmode random numtxqueues 1 numrxqueues 1 gso_max_size 65536 gso_max_segs 65535 tso_max_size 65536 tso_max_segs 65535 gro_max_size 65536 ovpn addrgenmode random numtxqueues 1 numrxqueues 1 gso_max_size 65536 gso_max_segs 65535 tso_max_size 65536 tso_max_segs 65535 gro_max_size 65536 ovpn addrgenmode random numtxqueues 1 numrxqueues 1 gso_max_size 65536 gso_max_segs 65535 tso_max_size 65536 tso_max_segs 65535 gro_max_size 65536 ovpn addrgenmode random numtxqueues 1 numrxqueues 1 gso_max_size 65536 gso_max_segs 65535 tso_max_size 65536 tso_max_segs 65535 gro_max_size 65536 ovpn addrgenmode random numtxqueues 1 numrxqueues 1 gso_max_size 65536 gso_max_segs 65535 tso_max_size 65536 tso_max_segs 65535 gro_max_size 65536
Depending on the installed Access Server and DCO version, the interface name may contain
ovpnorovpn-dco.If DCO isn't installed or available, refer to Tutorial: Install, Verify, and Troubleshoot OpenVPN DCO in Access Server.
For the best performance, use a client platform and configuration that support DCO. Windows and Linux clients support DCO.
OpenVPN Connect for Windows
Note
This procedure applies to OpenVPN Connect for Windows. OpenVPN Connect for macOS, iOS, and Android don’t currently support DCO.
Launch OpenVPN Connect.
Select Menu → Settings.
Select Advanced Settings.
Under Security Level, confirm that Preferred (Recommended) is selected.
Confirm that Enable DCO is enabled.
Connect to the VPN.
Review the client log.
When DCO is active, the log contains entries similar to:
Connected via ovpn-dco-win fixed mssfix=1380 EVENT: CONNECTED user@203.0.113.5:1194 (203.0.113.5) via /UDP-DCO on ovpn-dco-win/172.27.232.2/ gw=[172.27.232.1/] mtu=1420
OpenVPN 3 Linux
Connect to the console and get root privileges.
If the VPN session is already connected, check if DCO is in use:
openvpn3 sessions-list
Without DCO, the output may show:
Connected to: udp:203.0.113.5:1194 Device: tun0
To start a session with DCO enabled, run:
openvpn3 session-start --config <CONFIG_NAME> --dco true
When DCO is active, the session output should show values similar to:
Connected to: udp-dco:203.0.113.5 Device: tun
Note
The precise device and connection labels may vary by client version.
Routers and other OpenVPN clients
DCO support and configuration vary by router manufacturer and OpenVPN client implementation. Refer to the router or client documentation to determine whether DCO is supported and how to enable it.
Test the connection using both UDP and TCP.
Use UDP whenever the network permits it. Running a TCP-based application inside a TCP VPN tunnel can create competing retransmission and congestion-control behavior, sometimes called TCP-over-TCP performance degradation. Packet loss can cause throughput to decrease sharply.
TCP on port 443 remains useful on restrictive networks that block or throttle UDP. The recommendation to prefer UDP is about performance, not reachability.
Run the same throughput test:
Over a UDP VPN connection.
Over a TCP VPN connection.
Compare the results.
A substantially faster UDP result can indicate that TCP transport behavior is contributing to the slow VPN connection.
MTR combines traceroute and repeated ping tests. It can help show where packet loss or latency begins along the network path.
Install MTR on the client. (On Windows, use WinMTR.)
Connect to the VPN.
Run MTR against the target resource behind Access Server:
mtr -rwzbc 100 203.0.113.10
Note
The
-zflag reports the ASN for each hop. When genuine packet loss is confirmed, the ASN identifies which network operator is responsible.Example output:
Start: 2026-07-13T10:15:02+0000 HOST: client-laptop Loss% Snt Last Avg Best Wrst StDev 1. AS12345 192.0.2.1 0.0% 100 1.2 1.4 1.1 3.0 0.3 2. AS12345 198.51.100.10 0.0% 100 8.5 9.1 8.2 15.0 1.1 3. AS64500 198.51.100.22 18.0% 100 12.0 12.4 11.8 22.0 1.6 4. AS64500 203.0.113.9 0.0% 100 12.1 12.6 11.9 20.0 1.4 5. AS12345 203.0.113.10 0.0% 100 12.3 12.8 12.0 19.0 1.3
Interpret packet loss:
Don't assume that packet loss reported at a single intermediate hop represents real end-to-end loss.
In the example, hop 3 reports
18%loss, but later hops report0%. This usually means that the router is deprioritizing ICMP responses addressed to itself while continuing to forward traffic normally.Packet loss is more likely to be genuine when it:
Begins at one hop.
Continues through each subsequent hop.
Appears at the final destination.
Interpret latency:
Review the
Avgcolumn.A large increase at one hop that remains relatively stable afterward can represent a normal long-distance or provider transition.
Latency that continues increasing across subsequent hops may indicate congestion or another deteriorating condition along the path.
High latency reduces responsiveness even when measured throughput is acceptable. Packet loss can reduce both responsiveness and throughput.
The maximum transmission unit (MTU) is the maximum packet size an interface or tunnel can carry without fragmentation.
An MTU mismatch can cause large packets to be fragmented or dropped. This MTU fragmentation may produce poor throughput even when packet loss tests appear normal.
Check the VPN interface MTU:
ip addr | grep mtu|grep as0
Example output:
112: as0t0: <POINTOPOINT,NOARP,UP,LOWER_UP> mtu 1420 qdisc noqueue state UNKNOWN group default qlen 1000 113: as0t1: <POINTOPOINT,NOARP,UP,LOWER_UP> mtu 1420 qdisc noqueue state UNKNOWN group default qlen 1000 114: as0t2: <POINTOPOINT,NOARP,UP,LOWER_UP> mtu 1420 qdisc noqueue state UNKNOWN group default qlen 1000 115: as0t3: <POINTOPOINT,NOARP,UP,LOWER_UP> mtu 1420 qdisc noqueue state UNKNOWN group default qlen 1000
In this example, the VPN interface MTU is
1420. For information about changing this setting in the Admin Web UI, refer to VPN Server — MTU settings.
Find the largest packet that doesn't fragment:
On a Linux client:
ping -M do -s 1472 <target>
On a Windows client:
ping -f -l 1472 <target>
If the test fails because fragmentation is required, reduce the payload size until the ping succeeds.
For IPv4 ICMP:
Total packet size = ICMP payload + 8-byte ICMP header + 20-byte IP headerFor example:
1500 = 1472 + 8 + 20
For TCP over IPv4:
MTU = TCP payload + 20-byte TCP header + 20-byte IP headerThe TCP payload limit is called the maximum segment size (MSS).
For an MTU of
1500:MSS = 1500-20-20MSS = 1460
Note
The original relationship is
MSS = MTU - IP header - TCP header.
Cloud providers often limit network performance based on the virtual-machine size or service tier.
Review your provider's documentation for:
Maximum ingress and egress bandwidth.
Burst bandwidth limits.
Per-instance packet-processing limits.
Shared versus dedicated CPU resources.
Additional bandwidth charges.
Regional or inter-zone transfer constraints.
Ensure that the Access Server instance size supports the expected number of users and aggregate VPN throughput. A smaller instance may reach its provider-imposed network limit even when CPU usage appears low.
iperf3 is a useful tool for measuring network throughput because it avoids disk, browser, and web-server performance variables.
The following tests isolate different parts of the connection:
Client to the Access Server public IP, outside the VPN.
Client to the Access Server VPN IP, through the tunnel.
Client to a target resource behind Access Server.
Comparing the results helps identify where throughput decreases. This example assumes the client is a Linux machine.
Install iperf3
Install
iperf3:Ubuntu/Debian:
sudo apt update && sudo apt install iperf3 -y
RHEL:
sudo yum install epel-release -y && sudo yum install iperf3 -y
Test the raw link outside the VPN
This test measures the raw connection between the client and the Access Server public IP.
On Access Server, start the
iperf3server:iperf3 -s
On the client, test TCP upload:
iperf3 -c <server-ip>
Test TCP download:
iperf3 -c <server-ip> -R
Test UDP upload:
iperf3 -c <server-ip> -u
Test UDP download:
iperf3 -c <server-ip> -R -u
Record the results as the raw-link baseline.
Note
UDP tests require an appropriate target bandwidth to be meaningful. By default,
iperf3may send UDP at a relatively low rate. Use-bto set the intended test rate when necessary.
Test throughput through the VPN tunnel
Connect the client to the VPN.
On Access Server, identify the VPN IP address of an
as0tinterface:ip -4 addr show as0t0 | grep -o 'inet [0-9.]*' | cut -d' ' -f2
Example output:
172.27.224.1
Start the
iperf3server on Access Server:iperf3 -s
On the client, test TCP upload:
iperf3 -c 172.27.224.1
Test TCP download:
iperf3 -c 172.27.224.1 -R
Test UDP upload:
iperf3 -c 172.27.224.1 -u
Test UDP download:
iperf3 -c 172.27.224.1 -R -u
Compare these results with the public-IP baseline.
A large reduction through the VPN may indicate a tunnel, CPU, cipher, DCO, transport, or MTU-related bottleneck.
Test a private resource behind Access Server
Start the
iperf3server on the target resource:iperf3 -s
Before testing, verify that traffic to the resource uses the VPN tunnel.
On Linux:
ip route get <internal-server-ip>
The route should reference the VPN interface, such as
tun0.On Windows:
tracert -d <internal-server-ip>
The first hop should be the VPN gateway address.
From the VPN client, test TCP upload:
iperf3 -c <internal-server-ip>
Test TCP download:
iperf3 -c <internal-server-ip> -R
Test UDP upload:
iperf3 -c <internal-server-ip> -u
Test UDP download:
iperf3 -c <internal-server-ip> -R -u
Compare these results with the raw public-IP test and the test to the Access Server VPN IP.
If performance is good to the Access Server VPN IP but poor to the target resource, investigate the network between Access Server and that resource.
A file download provides an additional real-world throughput test.
Connect to the console (root privileges aren't required, as we use a non-privileged port).
On Access Server, create a 1GB file and serve it:
mkdir ~/tmp-web-server/ cd ~/tmp-web-server/ dd if=/dev/zero of=./file1gb.dd bs=1M count=1024
Serve the directory over TCP port
8080:python3 -m http.server 8080
Ensure that:
Port
8080is allowed through the applicable firewall or cloud security group.Access is limited to the required test client where possible.
No other service is using the port.
Test outside the VPN by downloading the file from the client:
wget http://<server-ip>:8080/file1gb.dd
Record the reported download speed.
Test through the VPN by connecting the client to the VPN and running:
wget http://<vpn-internal-server-ip>:8080/file1gb.dd
Compare the results.
After testing, stop the Python HTTP server on the Access Server by pressing
Ctrl+C.Remove the temporary file and directory when they're no longer needed:
rm -rf ~/tmp-web-server
Split tunnel
A split-tunnel configuration sends only traffic for specified private networks through Access Server. Other traffic continues to use the client’s normal internet connection.
Split tunnel can improve perceived VPN performance by reducing:
Access Server load.
Unnecessary tunnel traffic.
Added latency for public internet destinations.
Full tunnel
A full-tunnel configuration sends all client traffic through Access Server.
This can:
Increase Access Server bandwidth and CPU usage.
Add latency to general internet traffic.
Route traffic through an unnecessary geographic or network path.
Increase cloud egress usage.
Full tunnel may still be required by your organization’s security or monitoring policies. Choose the routing model that satisfies both your security requirements and performance goals. To review or change the relevant routing settings, refer to Tutorial: Configure Network Settings with the Admin Web UI.
Review the full path between:
The VPN client.
Access Server.
The private target resource.
Where possible:
Deploy Access Server geographically close to the users.
Deploy Access Server close to the target resources.
Avoid routing traffic across unnecessary regions or networks.
Reduce intermediate gateways, proxies, and inspection devices.
Use private cloud networking between Access Server and target resources.
Investigate sustained latency increases or packet loss at provider boundaries.
The best deployment location minimizes the combined network delay between the users, Access Server, and the protected resources.
Interpret the results
Use the following comparisons to narrow down the issue:
Result | Likely area to investigate |
|---|---|
Raw-link throughput is slow | Client ISP, server provider, cloud-instance limits, or the general network path |
Raw link is fast but VPN throughput is slow | CPU, cipher, DCO, VPN protocol, MTU, or Access Server configuration |
UDP is fast but TCP VPN is slow | TCP-over-TCP behavior, packet loss, or congestion |
Access Server VPN IP is fast but a private resource is slow | Network path, routing, firewall, or resource-side limits behind Access Server |
OpenSSL throughput is low | CPU capacity or missing hardware acceleration |
CPU is saturated during testing | Instance sizing, daemon count, cipher, or DCO |
Packet loss continues to the destination | Network or provider problem along the path |
Latency is consistently high | Geographic distance, routing, congestion, or unnecessary hops |
Small packets work but large packets fail | MTU mismatch or MTU fragmentation |
Full tunnel is slow for public internet traffic | Added server load, egress path, or geographic detour |
Next steps
After resolving the bottleneck, repeat the same baseline and VPN throughput tests to confirm the improvement.