A fast ping does not prove that a VPN reads and writes data quickly. It measures one kind of round-trip delay, usually with ICMP. A useful WireGuard assessment separates at least five properties:
- reachability through the intended tunnel;
- round-trip latency and jitter;
- TCP connection-establishment time;
- sustained throughput in both directions;
- response time of the real application, such as a database query.
Run the same tests without the tunnel when possible. The difference between the direct and tunneled paths is more informative than either number in isolation.
Confirm That the Test Uses WireGuard
On Windows, start with the target service rather than ping alone:
Test-NetConnection db.internal.example -Port 1521 -InformationLevel Detailed
Check InterfaceAlias, SourceAddress, RemoteAddress, and TcpTestSucceeded. The interface should be the WireGuard adapter and the source address should belong to the tunnel. A successful result proves that a TCP connection can be established; it does not report useful transaction latency or bandwidth.
Use a hostname or documentation-range address in reusable scripts. Do not publish real internal addresses, tunnel addresses, or database names.
Measure Repeated TCP Connection Time Without Installing Tools
The following Windows PowerShell test opens and closes a new TCP connection repeatedly. Replace the target and port with a service you are authorised to test:
$Target = 'db.internal.example'
$Port = 1521
$Count = 100
$Results = 1..$Count | ForEach-Object {
$Client = [System.Net.Sockets.TcpClient]::new()
$Watch = [System.Diagnostics.Stopwatch]::StartNew()
$Ok = $false
try {
$Client.Connect($Target, $Port)
$Ok = $true
}
catch {
$ErrorMessage = $_.Exception.Message
}
finally {
$Watch.Stop()
$Client.Dispose()
}
[pscustomobject]@{
Test = $_
Success = $Ok
Ms = $Watch.Elapsed.TotalMilliseconds
Error = if ($Ok) { $null } else { $ErrorMessage }
}
}
$Successful = @($Results | Where-Object Success)
$Results | Format-Table Test, Success, @{n='Ms';e={'{0:N2}' -f $_.Ms}}, Error
[pscustomobject]@{
Total = $Results.Count
Successes = $Successful.Count
Failures = $Results.Count - $Successful.Count
MinimumMs = ($Successful.Ms | Measure-Object -Minimum).Minimum
AverageMs = ($Successful.Ms | Measure-Object -Average).Average
MaximumMs = ($Successful.Ms | Measure-Object -Maximum).Maximum
} | Format-List
This measures DNS resolution when a hostname is used, TCP handshake time, operating-system scheduling, and the server’s ability to accept the connection. It does not authenticate to the database, execute SQL, or transfer a representative payload.
In one real troubleshooting session, 100 connection attempts through WireGuard all succeeded, with a minimum of 7 ms, maximum of 17 ms, and average of 9.31 ms. That is evidence of a stable TCP path during that sample—not a universal WireGuard benchmark and not proof of database query performance.
Report Percentiles, Not Only the Average
Averages hide intermittent stalls. Add percentile calculations for a longer run:
$Sorted = @($Successful.Ms | Sort-Object)
function Get-Percentile([double[]]$Values, [double]$P) {
if ($Values.Count -eq 0) { return $null }
$Index = [Math]::Ceiling(($P / 100) * $Values.Count) - 1
$Values[[Math]::Max(0, $Index)]
}
[pscustomobject]@{
P50Ms = Get-Percentile $Sorted 50
P95Ms = Get-Percentile $Sorted 95
P99Ms = Get-Percentile $Sorted 99
}
Use at least several hundred samples across quiet and busy periods. Record packet path, time, client load, server load, and whether the direct path was tested under comparable conditions.
Measure Throughput Separately
Use iperf3 between two endpoints you control, with one endpoint inside the remote network. WireGuard’s own performance notes use iperf3, while also warning that the published comparison is old and should not be treated as a current universal result.
On the controlled server:
iperf3 -s
On the client, test upload through the tunnel:
iperf3 -c 10.0.0.10 -t 30 -P 4
Test the reverse direction:
iperf3 -c 10.0.0.10 -t 30 -P 4 -R
One stream shows single-flow performance; several parallel streams reveal whether one flow is limited by latency, congestion control, or a single CPU path. Avoid running saturation tests against production systems without a maintenance window. They can consume the available WAN capacity and distort application performance.
Test Latency Under Load
Run the repeated connection test or a controlled ping while iperf3 transfers data. A tunnel may have excellent idle latency but severe delay when the link fills. Rising p95/p99 latency under upload often indicates queueing or bufferbloat rather than a WireGuard cryptographic problem.
Also watch both peers for CPU saturation, interface errors, packet loss, MTU symptoms, retransmissions, and endpoint roaming. A throughput plateau with one CPU fully loaded has a different cause from a plateau at the ISP line rate.
Measure the Real Application
For a database, create a read-only, representative test that records connect, authentication, query, and fetch time separately. Repeat it through the direct route and WireGuard. A tiny SELECT is useful for round trips; a controlled result set is useful for fetch throughput. Do not benchmark by repeatedly opening production transactions that alter data.
The diagnostic order is:
- verify route and TCP reachability;
- establish an idle latency baseline;
- collect repeated TCP timings and percentiles;
- test one-way and reverse throughput;
- observe latency while the tunnel is loaded;
- measure the actual application operation.
That sequence answers the real question. WireGuard can be functioning perfectly while a database pool, DNS lookup, storage subsystem, or saturated uplink creates the delay users experience.