Skip to main content

Tutorial: Troubleshoot and Optimize VPN Performance in Access Server

Abstract

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 iperf3 between 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:

  1. Connect to the Access Server console.

  2. Run:

    top -1
  3. Press 1 to display utilization for each CPU core.

  4. Reproduce the slow VPN connection while monitoring the output.

  5. Check whether:

    • One core is consistently at or near full utilization.

    • All cores are heavily utilized.

    • The openvpn or Access Server processes are consuming most of the available CPU.

    • CPU utilization remains low while throughput is poor.

  6. Press q to 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.

  1. Run the following to check if your CPU has hardware AES:

    grep -m1 -o 'aes' /proc/cpuinfo
    • Example output:

      aes
  2. If aes appears, the CPU supports hardware AES acceleration. AES-256-GCM will generally provide strong performance on this hardware.

  3. If aes doesn't appear, a cipher such as CHACHA20-POLY1305 may 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.

  1. To test a single process:

    openssl speed -evp aes-256-gcm -seconds 10 -bytes 1420

    This tests AES-256-GCM with 1,420-byte buffers for 10 seconds.

  2. 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

      1

      -multi 2 spawned two independent workers, one per core (Forked child 0, and Forked child 1).

      2

      This means Child 0 encrypted 12206839 buffers of 1420 bytes in 10 seconds.

      3

      This means Child 1 encrypted 12292238 buffers of 1420 bytes in 10 seconds.

      4

      This means Child 0 processed 1733371138 bytes/s = 12206839 x 1420 = ~17.33GB in 10s (1.733GB/s).

      5

      This means Child 1 processed 1745497796 bytes/s = 12292238 × 1420 = ~17.45GB in 10s (1.745GB/s).

      6

      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.

  1. Connect to the Access Server console and get root privileges.

  2. Check the number of CPU cores:

    sacli GetNCores
    • Example output:

      2
  3. 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",
  4. 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
  5. Restart the Access Server services:

    sacli start
  6. 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).

  1. 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
  2. The output should report that DCO is available.

  3. 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
      
  4. Depending on the installed Access Server and DCO version, the interface name may contain ovpn or ovpn-dco.

  5. 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.

  1. Launch OpenVPN Connect.

  2. Select Menu → Settings.

  3. Select Advanced Settings.

  4. Under Security Level, confirm that Preferred (Recommended) is selected.

  5. Confirm that Enable DCO is enabled.

  6. Connect to the VPN.

  7. 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

  1. Connect to the console and get root privileges.

  2. If the VPN session is already connected, check if DCO is in use:

    openvpn3 sessions-list
  3. Without DCO, the output may show:

    Connected to: udp:203.0.113.5:1194
    Device: tun0
  4. To start a session with DCO enabled, run:

    openvpn3 session-start --config <CONFIG_NAME> --dco true
  5. 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:

  1. Over a UDP VPN connection.

  2. Over a TCP VPN connection.

  3. 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.

  1. Install MTR on the client. (On Windows, use WinMTR.)

  2. Connect to the VPN.

  3. Run MTR against the target resource behind Access Server:

    mtr -rwzbc 100 203.0.113.10

    Note

    The -z flag 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
  4. 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 report 0%. 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.

  5. Interpret latency:

    • Review the Avg column.

    • 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.

  1. 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.

  2. 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>
  3. 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 header

    • For example:

      • 1500 = 1472 + 8 + 20

  • For TCP over IPv4:

    • MTU = TCP payload + 20-byte TCP header + 20-byte IP header

    • The TCP payload limit is called the maximum segment size (MSS).

    • For an MTU of 1500:

      • MSS = 1500-20-20

      • MSS = 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:

  1. Client to the Access Server public IP, outside the VPN.

  2. Client to the Access Server VPN IP, through the tunnel.

  3. 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 throughput through the VPN tunnel

  1. Connect the client to the VPN.

  2. On Access Server, identify the VPN IP address of an as0t interface:

    ip -4 addr show as0t0 | grep -o 'inet [0-9.]*' | cut -d' ' -f2
    • Example output:

      172.27.224.1
  3. Start the iperf3 server on Access Server:

    iperf3 -s
  4. On the client, test TCP upload:

    iperf3 -c 172.27.224.1
  5. Test TCP download:

    iperf3 -c 172.27.224.1 -R
  6. Test UDP upload:

    iperf3 -c 172.27.224.1 -u
  7. Test UDP download:

    iperf3 -c 172.27.224.1 -R -u
  8. 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

  1. Start the iperf3 server on the target resource:

    iperf3 -s
  2. 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.

  3. From the VPN client, test TCP upload:

    iperf3 -c <internal-server-ip>
  4. Test TCP download:

    iperf3 -c <internal-server-ip> -R
  5. Test UDP upload:

    iperf3 -c <internal-server-ip> -u
  6. Test UDP download:

    iperf3 -c <internal-server-ip> -R -u
  7. 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.

  1. Connect to the console (root privileges aren't required, as we use a non-privileged port).

  2. 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
  3. Serve the directory over TCP port 8080:

    python3 -m http.server 8080
    • Ensure that:

      • Port 8080 is 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.

  4. Test outside the VPN by downloading the file from the client:

    wget http://<server-ip>:8080/file1gb.dd
  5. Record the reported download speed.

  6. Test through the VPN by connecting the client to the VPN and running:

    wget http://<vpn-internal-server-ip>:8080/file1gb.dd
  7. Compare the results.

  8. After testing, stop the Python HTTP server on the Access Server by pressing Ctrl+C.

  9. 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.