Jitter on a game server is rarely caused by insufficient CPU or bandwidth. It is caused by the kernel doing housekeeping at the wrong moment: a network interrupt landing on the core running the tick loop, a CPU deciding to sleep for 400 microseconds, or a UDP receive buffer overflowing during a traffic burst and forcing retransmission. Each of these is a handful of lines in sysctl and IRQ configuration away from being fixed, and each shows up in the 99th-percentile tick delta rather than the average.
Before Tuning: Measure Jitter
Tuning blind is how operators add fifty settings that make things worse. Establish a baseline first: tick delta at the 99th percentile, network interrupt distribution across cores, and CPU idle-state exit latency. These three numbers tell you which sections below actually apply to your host.
# 99th percentile scheduling delay per CPU
cyclictest -p 80 -t -n -i 200 -l 100000 -h 400
# where are NIC interrupts landing?
cat /proc/interrupts | grep eth0
# is the CPU entering deep sleep states?
turbostat --show PkgWatt,CPU%c1,CPU%c6 sleep 10
# current network stack settings
sysctl net.core.netdev_max_backlog net.core.rmem_max net.ipv4.udp_mem
A cyclictest maximum above 1 ms on a modern server means the host is not configured for latency-sensitive work. Game servers running 128 tick have a 7.81 ms budget; a 1-2 ms scheduling delay consumed by kernel work is 15-25% of the entire window.
The sysctl Values That Matter
Settings that change game server jitter, with the values that work in practice. Drop them into /etc/sysctl.d/90-game.conf and load with sysctl --system.
| sysctl key | Default | Game server value | Effect |
|---|---|---|---|
| net.core.rmem_max | 212992 | 16777216 | Stops UDP receive drops under burst |
| net.core.netdev_max_backlog | 1000 | 25000 | Absorbs packet floods without loss |
| net.core.busy_poll | 0 | 50 | Reduces wake-up latency for busy sockets |
| net.ipv4.udp_rmem_min | 4096 | 16384 | Minimum buffer per UDP socket |
| net.ipv4.tcp_low_latency | 0 | 1 | Prefers latency over throughput |
| net.ipv4.tcp_fastopen | 1 | 3 | Cuts handshake RTT for lobby traffic |
| vm.swappiness | 60 | 1 | Prevents swap-induced tick stalls |
| kernel.sched_migration_cost_ns | 500000 | 5000000 | Keeps the tick thread on one core |
The last row is underrated. Raising sched_migration_cost_ns tells the scheduler that migrating a task is expensive, so it leaves the tick thread on its core instead of shuffling it to an idle sibling. On a 32-core node with a single-threaded game loop, this alone reduces 99th-percentile tick delta by 15-30% in measured tests.
C-States, Governor, and IRQ Affinity
sysctl handles the network stack, but three systems-level settings handle timing. Disable deep C-states so the core never pays an exit latency measured in tens to hundreds of microseconds. Set the CPU governor to performance so frequency never ramps down between ticks. Move NIC interrupt processing off the cores running game workloads.
# 1. no frequency scaling on game cores
cpupower frequency-set -g performance
# 2. kill C1E/C6 exit latency
for cpu in /sys/devices/system/cpu/cpu*/cpuidle/state*/disable; do echo 1 > $cpu; done
# 3. steer NIC IRQs away from game cores (cores 0-3)
for irq in $(grep eth0 /proc/interrupts | cut -d: -f1); do
echo 8-15 > /proc/irq/$irq/smp_affinity_list
done
# 4. RPS as backup when the NIC has one queue
echo f0 > /sys/class/net/eth0/queues/rx-0/rps_cpus
On a multi-queue NIC, give each game process its own RX queue and pin the matching queue’s IRQ to a core that is not running the tick loop. On a single-queue NIC, RPS spreads packet processing across cores but adds a softirq hop — measure before and after, since it can increase jitter when the packet rate is low.
Jitter Sources Ranked by Impact
Not every source of jitter matters equally. These are measured contributions to 99th-percentile tick delta on a 16-core host running a 128-tick server, ranked from largest to smallest. Fix them in this order.
| Source | Typical jitter added | Fix |
|---|---|---|
| CPU C-state exit latency | 150-600 us | Disable deep C-states on game cores |
| NIC IRQ on the tick core | 80-400 us | IRQ affinity + RX queue split |
| Task migration between cores | 50-250 us | Raise sched_migration_cost_ns, pin |
| Frequency ramp from idle | 40-150 us | performance governor |
| UDP receive buffer drops | bursty, 1-20 ms | Raise rmem_max and backlog |
| Swap under memory pressure | 2-50 ms | vm.swappiness=1, reserve RAM |
| Transparent huge page compaction | 1-30 ms | madvise mode or disable THP |
The first four are microsecond-scale and disappear with the settings above. The last three are the ones players actually feel: a 20 ms stall in a 7.81 ms tick window is two or three skipped ticks and visible rubber-banding. They only appear under memory pressure or heavy traffic, which is why load testing at realistic player counts matters more than idle benchmarks.
What Not to Change
Avoid enabling huge pages for game servers that allocate in small objects, because compaction pauses appear directly as tick stalls. Do not blanket-disable the scheduler’s load balancing on a shared host; pin only the game process with taskset or cpuset and leave the rest to the kernel. Skip any tuning guide that raises net.core.somaxconn for a UDP game — that backlog is TCP-only and irrelevant to the workload. And never copy a database-server tuning profile onto a game node: aggressive dirty-page flush settings that help write throughput add latency the tick loop cannot absorb.
Verifying the Result
Re-run cyclictest and compare the 99th-percentile and maximum values against baseline. A correctly tuned game host shows a maximum scheduling delay under 200 microseconds and a stable tick delta at the 99th percentile. Average latency barely moves — the gain is entirely in eliminating outliers, which is what players perceive as lag. This tuning needs hardware that exposes all these knobs, which is the case on a bare metal dedicated server for game hosting rather than a virtualised tier where a hypervisor reintroduces the latency you just removed. Rolling the same sysctl values out across a fleet with a config-management role keeps it consistent, and a high-traffic game server deployment is where these reductions translate into fewer lag complaints per player-hour.




Leave a Reply
You must be logged in to post a comment.