Skip to main content

Troubleshooting

HD won't start

"io_uring_setup: Operation not permitted"

The kernel denies io_uring access. This happens on older kernels or when io_uring is restricted by seccomp or io_uring_disabled sysctl.

# Check if io_uring is disabled
cat /proc/sys/kernel/io_uring_disabled
# 0 = allowed for all, 1 = restricted, 2 = disabled

Fix: Requires kernel 5.11+. On kernels 6.1+ where io_uring_disabled exists, set it to 0:

sysctl -w kernel.io_uring_disabled=0

If running in a container, the container runtime must allow io_uring syscalls. Docker's default seccomp profile blocks io_uring on older versions.

"kTLS not available"

HD logs this when the kernel doesn't support TLS offload. HD will still work -- it falls back to userspace TLS via OpenSSL. Performance will be lower.

Fix: Requires kernel 5.19+ with CONFIG_TLS and CONFIG_TLS_DEVICE enabled. Load the module:

sudo modprobe tls
cat /proc/modules | grep tls

Verify kTLS is active after starting HD:

cat /proc/net/tls_stat
# TlsCurrTxSw and TlsCurrRxSw should be > 0

"bind: Address already in use"

Another process is listening on the same port.

ss -tlnp | grep 3340

Fix: Stop the other process, or change HD's port in the config file.

"failed to open certificate"

HD can't read the TLS certificate or key file.

Fix: Check paths and permissions:

ls -la /etc/hyper-derp/cert.pem /etc/hyper-derp/key.pem
# Must be readable by the HD process user

Performance Issues

Low throughput compared to benchmarks

The published benchmarks use 4 distributed client VMs on GCP. Common reasons for lower numbers in your setup:

  1. Single client bottleneck. One client process can't generate enough traffic to saturate HD. Use multiple client machines or processes.

  2. Network is the limit. Check your NIC bandwidth:

    iperf3 -c <relay-ip> -t 10

    If this is lower than your expected relay throughput, the network is the bottleneck.

  3. kTLS not active. Check /proc/net/tls_stat. Without kTLS, HD uses userspace TLS and loses a significant amount of throughput.

  4. Wrong worker count. Default is one per CPU, but the optimal count depends on your workload. See Configuration: workers.

High latency spikes at 4 vCPU

HD has a known backpressure oscillation at 4 vCPU (2 workers) under sustained load above 50% of the relay ceiling. The p99 latency can spike to several milliseconds.

Workaround: Use 4 workers on 4 vCPU (--workers 4). This reduces the per-worker load and prevents the oscillation. The throughput penalty is small.

Status: A fix (wider hysteresis, minimum pause duration, reduced busy-spin) is in development. See the latency benchmark analysis for details.

High memory usage

Memory scales with worker count and buffer ring size. Each worker allocates a provided buffer ring at startup.

Check the current configuration:

# Default buffer ring: 1024 buffers x 65536 bytes = 64 MB per worker
# 8 workers = 512 MB just for buffer rings

Fix: Reduce buf_ring_size or buf_size in the config if your traffic doesn't need large buffers. For WireGuard traffic (1400-byte frames), buf_size: 4096 is sufficient.

Tailscale/Headscale Integration

Clients won't connect to HD

HD speaks the DERP protocol. Clients need to know about your relay via the DERP map.

For Headscale, add HD to the DERP map in config.yaml:

derp:
server:
enabled: false
urls: []
paths:
- /etc/headscale/derp.yaml

And in /etc/headscale/derp.yaml:

{
"Regions": {
"900": {
"RegionID": 900,
"RegionCode": "hd",
"RegionName": "Hyper-DERP",
"Nodes": [{
"Name": "hd1",
"RegionID": 900,
"HostName": "derp.example.com",
"DERPPort": 3340,
"InsecureForTests": false
}]
}
}
}

For Tailscale (managed), use an ACL policy with a custom DERP map. See Tailscale's custom DERP docs.

Clients connect but can't relay traffic

Check that both peers are connected to the same HD relay node. If using multiple relay nodes, they need mesh keys to forward between each other.

Verify with:

# On the relay, check active peers
curl -s http://localhost:9090/metrics | \
grep hyper_derp_peers_active

Self-signed certs and InsecureForTests

For testing with self-signed certificates, set InsecureForTests: true in the DERP map node config. This disables TLS certificate verification for DERP connections.

Do not use this in production.

Logs

HD logs to stderr by default. Increase verbosity:

hyper-derp -c config.yaml --verbose

Or set log_level: debug in the config file.

Key log messages:

MessageMeaning
kTLS enabledKernel TLS offload is active
kTLS not availableFalling back to userspace TLS
worker N startedWorker thread is up and accepting connections
recv_pausedBackpressure activated on a worker (send queue full)
send_dropA frame was dropped because the destination queue was full
slab_exhaustFrame pool ran out of pre-allocated buffers

Kernel Version Compatibility

FeatureMinimum Kernel
io_uring basic5.11
Provided buffer rings5.19
kTLS (TLS 1.3 AES-GCM)5.19
DEFER_TASKRUN6.1
SQPOLL5.11 (CAP_SYS_NICE)