Fixing network thread hitch warnings in FiveM

Why network thread hitches point at sync and packet load rather than resource code, and how to tell organic load from an attack.

Game servers11 min read

On this page
  1. How the two threads differ
  2. Confirm which thread before you change anything
  3. 1. Too many networked entities
  4. 2. Broadcast events and replicated state bags
  5. 3. Player count against what the server is carrying
  6. 4. OneSync mode and population settings
  7. 5. Server list queries hitting your public endpoint
  8. 6. Hostile traffic
  9. Telling an attack from organic load
  10. If it is an attack
  11. What does not fix this
  12. Related

network thread hitch warning - frame time of 210 milliseconds means the server's network thread was blocked for 210 milliseconds. That is not the thread your scripts run on. It is the thread that moves packets between your server and every connected client and that drives player and entity synchronisation, so a stall there shows up as rubber-banding, entities snapping into place, and joins timing out, while the server itself keeps ticking.

That difference is the whole point of this page. A network thread hitch is a volume problem: how many entities are being synced, to how many players, and how much traffic is arriving. It is very rarely a badly written loop, and the fixes that work for the other warning do nothing here.

How the two threads differ

Main threadNetwork thread
RunsEvery server-side resource tick, event handler, and commandPacket send and receive, player and entity sync, connection handling
Stalls because ofCode that does not yield, or something it waits onThe volume of sync and packets it has to get through
Players reportCommands, menus, and chat answer late; everything pauses togetherRubber-banding, entities teleporting, players timing out, joins failing
Fixed byEditing the resource that blocks itReducing entities, sync scope, or traffic
Warning textserver thread hitch warningnetwork thread hitch warning

If your console says server thread, this is the wrong page: go to server thread hitch warnings.

Some artifact builds run a separate sync thread as well, and txAdmin's performance chart lets you select it. Read it alongside the network thread, since the causes below drive both.

FXServer, on your machineA host that stalls, stalls both threads at onceMain threadserver thread hitch warningEvery resource tick, event handler and commandYour Lua runs hereCitizen.Wait yields itWhat your resources sendevents to every player, replicated state bagsNetwork threadnetwork thread hitch warningPacket send and receive, player and entity sync,connection handlingNo Lua runs hereIts load is volumePackets, both waysEvery connected clientplus server list queries and any hostile trafficarriving on your port
Where each warning comes from. Your Lua runs on the main thread, so Citizen.Wait only ever yields that one; the network thread carries packets and sync to every client, including whatever your resources send. A host that stalls hitches both at once. Illustration.

Confirm which thread before you change anything

  1. Read the warning text, exactly

    The first word is the answer. Copy the line out of the console rather than working from memory or from what someone told you it said.

  2. Look at the per-thread chart

    Open txAdmin's dashboard performance chart and switch between the threads. You are looking for whether one of them is spiking on its own or whether they spike together.

  3. If both threads hitch together, look at the machine

    A host that stalls stalls every thread at once. Check for disk wait, steal time, and swapping before doing anything else, using the checks in the machine underneath the server.

  4. Note whether player count was rising, flat, or falling

    This one line of information sorts most of the causes below, and it separates organic load from an attack better than any other single check.

1. Too many networked entities

The most common cause on an established server, because entity counts only ever go one way without deliberate cleanup.

Symptom. Hitching gets steadily worse through a session and disappears after a restart. It is worse when players cluster, at a city centre, a car meet, or a scheduled in-game event, than when the same number of people are spread out.

Confirm it. With OneSync on, count what exists, once when the server is fresh and again after a busy evening:

Lua
print(('vehicles %d, peds %d, objects %d'):format(
  #GetAllVehicles(), #GetAllPeds(), #GetAllObjects()))

A count that only climbs is your answer. Every networked entity has to be tracked and synced to the players near it, so the work is not the number of entities, it is entities multiplied by the players who can see them. That is why clustering hurts so much more than raw player count.

Fix it. Delete abandoned vehicles and orphaned props on a schedule, in bounded batches. Find the resources that spawn and never delete. Then stop clients adding to the pile: sv_entityLockdown in your server.cfg set to relaxed or strict prevents clients creating entities the server did not authorise, which closes both a performance hole and a cheating one. See server.cfg.

2. Broadcast events and replicated state bags

Symptom. Hitching that started after a resource was added or updated, and that scales with player count. Often a resource whose main-thread cost in the profiler looks trivial, which is exactly why people rule it out.

Confirm it. Find the broadcast patterns:

Shell
grep -rn "TriggerClientEvent(" resources/ | grep -- "-1" | head -40
grep -rn "state:set(" resources/ | head -40

For each hit, ask two questions: how often does this run, and how many recipients does it have. A payload sent to one player once is nothing. The same payload sent to -1 on a one-second timer, with 80 players connected, is 80 sends a second forever.

State bags deserve their own look. Entity(vehicle).state:set('key', value, true) replicates the value to clients. Doing that inside a tick replicates it every frame, for every entity the code touches, and it is one of the easiest ways to saturate the network thread with data nobody reads.

Fix it.

  • Send to the players who need it, not to -1.
  • Use TriggerLatentClientEvent for anything large. It takes a byte rate and spreads the payload over time instead of pushing it all at once.
  • Pass false as the replication argument on state bags clients do not read.
  • Set state on change, not on a timer, and never every frame.

3. Player count against what the server is carrying

Symptom. It starts at roughly the same player count every night. Below that number the server is fine, above it, it hitches consistently.

Confirm it. Line the hitches up against the player graph in txAdmin. If the onset is reproducible at a player count, the load scales with players and this is the cause rather than a coincidence.

Worth understanding before you react: sync work does not grow in a straight line with players. Each player has to be told about the entities and players near them, so 60 players in one place is a great deal more work than 60 players spread across the map. This is why a server that is comfortable at 60 every day falls over during an in-game event with the same 60 people.

Eight players, spread outEach one is told about the player near them.The same eight, in one placeEach one is told about every other player.
The same players, spread out or in one place. Spread out, each is kept told about the few near them; gathered together, every player has to be told about every other, so the lines multiply while the head count stays the same. Illustration.

Fix it. sv_maxclients is a ceiling, not a target. Set it to what your server actually carries with the resources you run, and raise it when you have measured headroom. Raising it because you would like more players adds no capacity, it only lets more people in before the hitching starts, and what you get is clamped to what your OneSync mode and licence tier allow anyway. Reducing the reasons for players to pile into one place, or reducing entity density at events, is the other half of the answer.

4. OneSync mode and population settings

Symptom. Hitching on a server that has grown past its original size, or one that was configured years ago and never revisited.

Confirm it. Type each convar name on its own in the server console. It prints the value currently in force, which is the only reading that counts, since a config line can be overridden later in the file or by another resource:

onesync
onesync_population
sv_maxclients

Fix it. Three things to get right:

  • onesync takes on, legacy, or off. on is the mode that culls entities by distance, so each client only receives what is near it and sync work stops growing with the whole world. legacy does not scale the same way, and off puts you on the base game network model with a 32 client limit. What ceiling you get with on depends on your licence tier, which server.cfg covers.
  • onesync_population decides whether ambient traffic and pedestrians are networked. Turning it off removes an entire class of entities from sync. On a roleplay server where ambient traffic is scenery, this is one of the few settings that changes entity counts by a large factor. Measure your counts before and after with the snippet above rather than assuming.
  • Entity culling and lockdown settings belong to the same conversation. See server.cfg for the full set and where they go in the file.

5. Server list queries hitting your public endpoint

Your server answers /info.json, /players.json, and /dynamic.json on its TCP port, unauthenticated, to anybody who asks. That is how the server list, and every third-party server tracker, reads your details.

Symptom. Inbound and outbound traffic that does not match your player count. Load that continues when the server is nearly empty.

Confirm it. Measure what one of those replies costs you. From the server itself:

Shell
curl -s -o /dev/null -w '%{size_download} bytes\n' http://127.0.0.1:30120/info.json

Compare that with the size of the request that triggered it, which is a few dozen bytes. /info.json carries your full resource list and your server icon embedded in it, so the reply is very much larger than the request. That ratio is the whole attraction for anyone using it as an amplifier, and it is also why a tracker polling you every few seconds is not free.

Fix it. You cannot turn the endpoint off and stay listed, so reduce what it costs: keep the server icon to the small square PNG the server list expects, and keep the resource list from being longer than it needs to be. Both are covered in server.cfg. As a diagnostic only, setting sv_master1 to an empty value unlists the server, so if the load disappears you have confirmed where it came from. That is a test, not a fix, since players cannot find an unlisted server.

6. Hostile traffic

Symptom. The network thread hitches while player count is flat or falling. Players time out in groups. New players cannot join while people already in the game are fine, or everyone drops at once. It starts and stops abruptly rather than building with your evening.

Read the next section before concluding this, because organic load looks similar from the console and the two need completely different responses.

Telling an attack from organic load

Four checks, and you want more than one of them to agree.

1. Does the load track player count? Organic load rises and falls with players. Attack traffic does not care how many people are on your server. Load that climbs while your player count falls is the single strongest signal you can get.

Organic loadTraffic rises and falls with your players.RelativeAn eveningInbound trafficPlayers onlineAttack trafficTraffic jumps while players fall, then stops.RelativeAn eveningInbound trafficPlayers online
The shape that separates the two. Organic traffic rises and falls with your players through the evening; attack traffic climbs while the player count falls, and starts and stops abruptly. Illustration: relative, not measured.

2. Does the bandwidth divide sensibly by player count? Measure inbound traffic during a hitching period. On Linux:

Shell
sar -n DEV 1 10

sar comes from the sysstat package, which is apt install sysstat on Debian and Ubuntu and dnf install sysstat on AlmaLinux and Rocky. rxpck/s is packets received per second and rxkB/s is inbound kilobytes per second, per interface. Divide by your connected player count and compare with the same figure taken on a normal night at the same player count. Organic traffic per player is roughly stable. A figure several times your normal per-player rate is not organic. On Windows, Get-NetAdapterStatistics in PowerShell gives you the counters to do the same arithmetic.

3. Where is it coming from? Take a sample of packets on your game port and count the sources:

Shell
sudo tcpdump -ni eth0 -c 5000 udp port 30120 2>/dev/null \
  | awk '{print $3}' | cut -d. -f1-4 | sort | uniq -c | sort -rn | head -20

Replace eth0 with your interface, and 30120 with your port if you changed it. tcpdump is apt install tcpdump on Debian and Ubuntu, dnf install tcpdump on AlmaLinux and Rocky. Then compare the top talkers with the endpoints of your actual connected players, which txAdmin lists for you. Heavy senders that are not on your player list are not playing your server. Traffic arriving on a port you run nothing on is conclusive on its own.

4. Does it stop when you are empty? Traffic that continues at 4am with nobody connected is not your players.

If it is an attack

Filtering happens at the network edge, before traffic reaches your server, so what to do about it is not a change on the machine. Work through the protection docs:

  • Game presets writes the rules for FiveM, which need both a TCP and a UDP rule on your game port. This is the fastest starting point if you have no rules at all.
  • Port rules covers doing it by hand, and the fact that rules are per protocol, so a game needing TCP and UDP needs two of them.
  • Rate limits covers sizing a ceiling from your own measured peak rather than from an attack. This is where the packets per second you measured in check 2 above become useful.
  • When players cannot connect is where to go if tightening the limits starts cutting real players, which is the usual next problem.

One thing to be clear about: traffic that is dropped at the edge never reaches your server at all. So a network thread hitch that persists while an attack is being filtered is either the portion that is being allowed through, because it resembles legitimate game traffic closely enough, or it is one of the causes higher up this page happening at the same time. Check both rather than assuming the attack explains it.

If you have measured evidence of traffic reaching your server that should not be, open a ticket with the port and protocol, the sample of source addresses you collected, the times, and the packets per second you measured. Those are what turn a report into an investigation.

What does not fix this

  • Adding Citizen.Wait to resources. Your Lua does not run on this thread.
  • More CPU cores. Nothing here is short of cores.
  • A scheduled restart. It resets entity counts, which hides the most common cause of this warning for a few hours at a time and guarantees you never find it.
  • Raising sv_maxclients. It is a ceiling, and raising it during a hitch adds load.

Need a server, or a hand with one?

Pick a plan to deploy a server, or open a ticket in the panel. Support runs 24/7/365.