Introduction: Which Layer Is Actually Slow?
“A VPS is slow” is an over-simplified problem. The “lag” a user perceives can come from any layer: the physical link, packet loss, congestion, BGP route selection, or even kernel parameters. Blindly upgrading bandwidth often costs money without solving the real issue.
This manual follows one rule: localize layer by layer, bottom-up — first figure out who is slow, then talk about how to tune it.
User browser
│ 1. App layer: slow DNS / TLS handshake / server processing
│ 2. TCP layer: 3-way handshake, retransmits, congestion window
│ 3. IP layer: hop count, MTU, fragmentation
│ 4. Physical/link: loss rate, jitter, carrier BGP
▼
VPS cloud host
Each layer below gets one command that yields an answer in 30 seconds and one cost-saving move.
Layer 0: Confirm which end is slow first
Many “slowness” cases are not the VPS at all — they are the user’s local network or DNS. Run this group first and eliminate most false positives:
# 1. DNS resolution time (>500ms is almost always local/carrier DNS)
time nslookup your-vps.com 8.8.8.8
time nslookup your-vps.com 223.5.5.5 # Ali DNS (China)
# 2. ICMP latency + loss to the VPS (30 samples)
ping -c 30 your-vps.com
# 3. Reverse check from the VPS side (log in and run)
ping -c 30 1.1.1.1
ping -c 30 8.8.8.8
# 4. Hop count — see where the delay appears
traceroute your-vps.com # Linux/macOS
tracert your-vps.com # Windows
How to read it:
pingis stable but large (>100ms) → cross-continental / cross-carrier; consider a nearby node + CDNpingjitters wildly (±50ms+) → congestion or unstable BGP selectiontracerouteshows a sudden jump at an intermediate hop → that middle AS is the problem; you can’t fix it, reroute around it- VPS side pings out fast, user side is slow → it’s the user’s local network, don’t rush to upgrade the VPS
💡 Cost saver: if most of your users are in China but the VPS sits in a foreign datacenter, move to a nearer region — far cheaper than adding bandwidth. A small domestic VPS + a foreign origin (“dual-node”) beats one big bandwidth line.
Layer 1: DNS is the invisible big one
DNS resolution routinely eats 20–40% of “time to first byte (TTFB)”, yet is the most overlooked.
# Per-stage breakdown: DNS lookup, TCP, TLS, TTFB
curl -w "dns: %{time_namelookup}s tcp: %{time_connect}s tls: %{time_appconnect}s ttfb: %{time_starttransfer}s\n" \
-o /dev/null https://your-vps.com
# See which IP it resolves to (run repeatedly; watch for multi-IP rotation / cross-ocean)
dig +short your-vps.com
dig your-vps.com | grep -A3 ANSWER
Optimizations:
- Use authoritative / public resolvers (Cloudflare 1.1.1.1 / Ali 223.5.5.5) so users get the nearest IP; if you’re on multiple clouds, do geographic DNS steering
- CDN fallback: static assets via CDN improve both
TTFBandDNS, and the free tier covers most small/medium sites - Check whether
NSrecords still point at the old provider (migrating without updating NS is a top cause of “fake slowness”)
# Domain side: CNAME static assets to a CDN
static-your-vps.com. CNAME @your-domain.cdn.cloudflare.com.
💡 Cost saver: a free CDN tier (Cloudflare Free) removes 70% of “it’s laggy” complaints at a fraction of the cost of dedicated bandwidth.
Layer 2: TCP — packet loss & retransmits are the latency killers
Losing 1% of packets makes TCP’s “timeout retransmit” balloon a single request by 200ms+ (default RTO ~200ms). This is the most common true cause of “high but unstable latency.”
# 1. Loss + retransmit stats (the most direct evidence)
cat /proc/net/snmp | grep -E '^(Tcp|Retrans)'
# Watch RetransSegs / OutSegs ratio; >1% is a problem
# 2. Live per-connection congestion state (apt install iproute2)
ss -ti
# In the output: retrans, cwnd (congestion window), rtt are key
# 3. Packet capture to confirm retransmits happen on your VPS NIC
sudo tcpdump -i eth0 -nn 'tcp[tcpflags] & (tcp-retrans|tcp-ack) != 0' -c 20
Kernel tuning (/etc/sysctl.d/99-vps-tuning.conf, for low-latency):
# Early retransmission + TIME_WAIT reuse
net.ipv4.tcp_sack = 1
net.ipv4.tcp_tw_reuse = 1
# High BDP (bandwidth × RTT) → raise buffer caps; cross-ocean, bump to 64MB
net.ipv4.tcp_rmem = 4096 262144 67108864
net.ipv4.tcp_wmem = 4096 262144 67108864
# Congestion control (default cubic; BBR is markedly better on high-RTT links)
net.core.default_qdisc = fq
net.ipv4.tcp_congestion_control = bbr
# NIC queue: multithreaded RPS on multi-core VPS
net.core.netdev_max_backlog = 16384
net.core.rps_cpus = 01 # adjust by core count, 4 cores = 0F
# Apply & verify
sudo sysctl --system
sysctl net.ipv4.tcp_congestion_control # should print bbr
⚠️ BBR precondition: needs kernel ≥ 4.9. Older VPS kernels must install a newer kernel or fall back to
cubic. Check withuname -rfirst.💡 Cost saver:
fq+ BBR stops one 100Mbps line from head-of-line blocking under concurrency — the equivalent of saving the upgrade you were about to buy.
Layer 3: IP layer — MTU & hop count
Two high-frequency, low-profile problems:
3.1 MTU fragmentation (PMTU)
Some links have an MTU other than 1500 (tunnels/VPNs often 1420/1400); large packets get fragmented, and losing one fragment forces a full retransmit.
# Probe the largest non-fragmented MTU to the target (-d disables fragmentation)
ping -d -M do -s 1472 your-vps.com # 1472 = 1500 - 28 (icmp+ip headers)
ping -d -M do -s 1400 your-vps.com # step down until it passes
# If a size passes but larger fails → set the VPS NIC MTU
sudo ip link set eth0 mtu 1400
3.2 Abnormal hop counts
ip route
traceroute your-vps.com | wc -l
High hop count alone isn’t a problem (hops ≠ latency), but a sudden increase in hops usually means an upstream BGP route changed (a carrier pushed traffic to a far AS).
💡 Cost saver: when BGP rerouting inflates latency, ask the upstream to change egress AS — cheaper than adding bandwidth on the user side.
Layer 4: BGP & carriers — “which AS am I on”
The layer most self-hosters ignore. The same VPS can feel 3× different to different users, mostly because of “the BGP path from the user’s carrier to the VPS’s egress AS.”
4.1 Identify your VPS’s egress AS
whois your-vps-ip | grep -iE '^(OrgName|descr|as-number)'
# or: ipinfo.io / bgp.tools
# All IP blocks of an AS (check whether same-AS traffic is internalized)
whois -h whois.radb.net '-i origin AS4837' | head -20
4.2 Judge the user→VPS BGP path
# Online: use bgp.he.net to inspect the hop count between two ASes
# https://bgp.he.net/
Common traps:
- Poor inter-carrier transit: a domestic VPS with only one carrier egress → users on another carrier detour → 2× latency
- Single-line BGP vs multi-line: single-carrier BGP + cross-carrier users = guaranteed lag; use full multi-line BGP or CN2 routes
- Cross-ocean cable congestion: evening peak (20–23h) adds ~50ms on cross-ocean links — a physical law, only solved by a nearer site, no tuning fixes it
4.3 Cost-saving BGP strategy for self-hosters
| Scenario | Recommendation | Monthly cost range |
|---|---|---|
| Mostly domestic users | Domestic multi-BGP / CN2 small VPS | ~$7–20 |
| Domestic + overseas audience | Domestic origin + Cloudflare CDN for overseas | ~$7 + CDN Free |
| Purely overseas users | VPS on the nearest AS | on demand |
| Bursts / high concurrency | VPS + object-storage static offload | cuts peak bandwidth billing |
💡 Cheapest single move: throw large/static files onto object storage (COS/S3/OSS public reads have free tiers) and keep only dynamic APIs on the VPS — bandwidth bill cut roughly in half.
Layer 5: Final VPS kernel network-stack tuning checklist
All the “should-tune” items from the layers above, bundled into one deployable script:
#!/usr/bin/env bash
# vps-net-tune.sh — VPS low-latency network tuning (with pre-checks)
set -euo pipefail
echo "==> Kernel:" ; uname -r
echo "==> Current CC:" ; sysctl net.ipv4.tcp_congestion_control
cat > /etc/sysctl.d/99-vps-net.conf <<'EOF'
# --- Congestion control & queueing ---
net.core.default_qdisc = fq
net.ipv4.tcp_congestion_control = bbr
# --- Buffers (high BDP) ---
net.ipv4.tcp_rmem = 4096 262144 67108864
net.ipv4.tcp_wmem = 4096 262144 67108864
# --- Connections & TIME_WAIT ---
net.ipv4.tcp_tw_reuse = 1
net.netfilter.nf_conntrack_max = 262144
# --- UDP receive (video/realtime) ---
net.core.rmem_max = 16777216
net.core.wmem_max = 16777216
net.core.optmem_max = 65536
# --- NIC RPS multithread (edit rps_cpus to match cores) ---
net.core.netdev_max_backlog = 16384
EOF
sudo sysctl --system
echo "==> After apply:"
sysctl net.ipv4.tcp_congestion_control net.core.default_qdisc
echo "Done. Written to sysctl.d so it loads on every boot."
Quick Troubleshooting Table
| Symptom | Most likely layer | First command |
|---|---|---|
| High TTFB but normal ping | DNS / slow app | curl -w 'dns: %{time_namelookup}s' |
| Stable large latency (>150ms) | Cross-continental / cross-ocean | ping -c 30 + move to a nearer node |
| Wild latency jitter | Congestion / unstable BGP | ss -ti for cwnd swings + traceroute |
| Intermittent big stalls | Loss/retransmit | cat /proc/net/snmp for RetransSegs |
| Only a specific region laggy | BGP egress AS mismatch | look up the AS + bgp.he.net |
| Only laggy at evening peak | Cross-ocean cable congestion | physical law, use a nearer site |
| Big packets lag, small don’t | MTU fragmentation | ping -d -M do -s 1472 |
Summary
The only correct way to debug network latency is layer-by-layer, bottom-up — not “just add bandwidth”:
- Layer 0 first separates “is it the user or the VPS”, removing 80% of false positives
- DNS is often the invisible big one; a CDN is the highest-ROI fix
- TCP loss/retransmit is the most common “unstable latency” cause; BBR + fq queues are free medicine
- BGP egress AS fit decides which users lag; multi-line BGP or a nearer site is the only cure
- The kernel tuning script deploys in one shot and persists across reboots
💰 The cost-saving formula: nearer site + CDN for static + BBR tuning + object-storage offload = 3× better experience, 50%+ lower bandwidth bill. Always diagnose before spending — it’s cheaper than upgrading the VPS, every time.
