Game latency drives most of the delay a player feels on screen, and it rarely comes from a single cause — it is several layers of delay stacked on top of each other. A practical way to attack it is to split the work into three areas: picking a location physically close to your players, improving the network route a packet travels, and tuning the tick loop and packet flow inside the server's own code. This article walks through all three with real, applicable steps.
Where does latency come from?
The delay a player sees is actually the sum of several parts:
- Propagation delay: the distance the signal travels over fiber. Light moves through glass at roughly 200,000 km/s, so even 3,000 km means about ~15 ms of one-way baseline. You cannot erase this in code; you only shrink it by shortening the distance.
- Queuing and processing delay: the time a packet waits in routers along the path, in the server's network card and in the game loop.
- Serialization delay: the time it takes to write the packet onto the wire, which becomes noticeable for large packets on low bandwidth.
- Tick delay: because the server updates state at fixed intervals, incoming input may wait until the next tick.
The goal is to win a few milliseconds at every layer you control, because they add up.
1. Choosing the right location
The biggest and cheapest win usually comes from moving the server closer to your player base. If most of your players are in Turkey and Europe, keeping the server in a central region such as Frankfurt, Amsterdam or Istanbul saves dozens of milliseconds compared to a US data center. A single good decision can matter more than every later optimization combined.
Before committing to a location, measure it for real. To see the baseline round trip from the target region:
ping -c 20 server-ip
mtr -rwzc 50 server-ip
In the mtr output, look at packet loss and latency spikes at each hop; this lets you tell whether the problem is distance or a bad intermediate carrier. If your player base spans multiple continents, the right answer is regional servers that route each player to the nearest one, not a single giant server.
2. Improving the network route
Even when the geographic distance between two points is short, internet traffic sometimes follows a long, poor route. With mtr or traceroute you can spot needless detours or a transit provider with frequent packet loss.
- Provider and peering quality: choose a host with good peering agreements that connects close to your players' ISPs. Even in the same data center, different providers take different routes.
- Use UDP: for real-time game traffic, TCP's retransmission and in-order delivery guarantees usually hurt — waiting for a lost, stale position packet delays the fresh one. Most games build their own reliability layer on top of UDP.
- MTU and fragmentation: keep packets below the path MTU (typically 1500 bytes, lower with tunnels) so fragmentation and its added delay never happen.
3. Operating system and socket settings
On the Linux side, a few settings cut how long the server holds a packet. The most important is disabling Nagle's algorithm, which batches small packets and therefore adds visible delay to interactive traffic:
int flag = 1;
setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &flag, sizeof(flag));
For UDP sockets, keep the send/receive buffers large enough to resist bursts and drive the socket in non-blocking mode with epoll. Under load, a buffer overflowing and dropping packets is a common cause of spiking ping. Tune kernel parameters like net.core.rmem_max based on real load, not blindly.
4. Tick rate and the game loop
The server updates the world at a fixed frequency (tick rate). A 20 Hz loop steps every 50 ms; 60 Hz every ~16.7 ms. A higher tick rate shortens how long input waits to be processed but raises CPU cost. In practice, fast-paced games favor 30–60 Hz.
What matters is that the loop stays fixed and predictable. A clean fixed time step looks like this:
const double dt = 1.0 / 30.0; // 30 Hz
double accumulator = 0.0;
double last = now();
while (running) {
double current = now();
accumulator += current - last;
last = current;
while (accumulator >= dt) {
update(dt); // advance the simulation
accumulator -= dt;
}
flush_outgoing(); // send state right away
}
The critical point here is to send state immediately after update instead of buffering it; otherwise you give back the tick gain in an artificial queue. Also move heavy work inside a tick (database writes, file I/O, logging) onto a separate thread from the main loop, so one slow query does not freeze every player.
5. Managing bandwidth and jitter
As important as raw ping is jitter — the variation in delay. A steady 60 ms feels better than a ping that constantly jumps between 20 and 120. To reduce jitter:
- Shrink state updates with deltas (sending only what changed); less data means less serialization delay.
- Keep the per-player send rate steady; sudden traffic bursts inflate queues.
- Use a small interpolation buffer on the client to smooth out network fluctuation.
Frequently Asked Questions
Does raising the tick rate lower ping?
Not the network ping directly; network delay is about distance and route. But because it shortens how long input waits to be processed, it reduces the total delay the player feels. Being steady and low matters more than being high.
Should I use UDP or TCP?
UDP for real-time position/input traffic. TCP's in-order delivery hurts because waiting for an old packet delays the new one. Where you need reliability (important events, for example), build your own lightweight acknowledgment layer on top of UDP.
My players are on different continents — what do I do?
You cannot please everyone with one server. Run regional servers and route each player to the one with the lowest measured ping; physical distance cannot be beaten with code.
Is your server's ping too high? I can help you hunt down delay across location, network route and the game loop and turn it into a measurable improvement. Get in touch and let's look at your setup together.