performanceresmononesyncoptimisation13 min read · updated 9/7/2026

Find the resource that is eating your frames

Almost every "our server lags" thread is people guessing. There are two measurements that end the guessing in about five minutes — client resmon and the server profiler — and once you have them, the fix is usually obvious and usually small.

Fixing FiveM server lag: a method, not a checklist

#First, separate the three kinds of lag

They have completely different causes and completely different fixes, and treating them as one problem is why people spend weeks on this.

SymptomWhat it actually isWhere to look
Low FPS, smooth networkClient-side script or streamed assetsresmon on the client
Good FPS, rubber-bandingNetwork or server tickServer profiler, netgraph
Freezes at specific placesAn MLO or a texture dictionaryStreaming, not scripts

#resmon: the client-side answer

Open the client console with F8 and type resmon 1. You get a live table of every resource with its CPU time per tick and its memory. Sort by the time column and look at the top five.

The numbers are milliseconds per frame. At 60 FPS you have 16.6 ms for everything, and the game itself wants most of it.

Per-tick timeVerdict
under 0.05 msIdle. Normal for a well-written resource doing nothing.
0.05 – 0.20 msFine. Active resource doing real work.
0.20 – 0.50 msWorth a look, especially if it is idle-state.
over 0.50 msA problem. Something is looping every frame that should not be.
over 2 msThis resource alone is costing you visible frames.

#The loops that cause it

In practice, the overwhelming majority of client-side cost comes from one pattern: a while true loop with Wait(0) that runs whether or not it needs to. Wait(0) means every frame, forever.

-- what it usually looks like
CreateThread(function()
  while true do
    Wait(0)
    local ped = PlayerPedId()
    local coords = GetEntityCoords(ped)
    for _, spot in pairs(Config.Spots) do
      if #(coords - spot.xyz) < 2.0 then
        DrawMarker(...)
        if IsControlJustPressed(0, 38) then openMenu(spot) end
      end
    end
  end
end)

The same thing, at a fraction of the cost

Sleep long when far away, short only when close enough to matter. The behaviour is identical and the cost drops by one to two orders of magnitude.

CreateThread(function()
  while true do
    local sleep = 1000
    local coords = GetEntityCoords(PlayerPedId())

    for _, spot in pairs(Config.Spots) do
      local dist = #(coords - spot.xyz)
      if dist < 20.0 then
        sleep = 0
        DrawMarker(...)
        if dist < 2.0 and IsControlJustPressed(0, 38) then
          openMenu(spot)
        end
      end
    end

    Wait(sleep)
  end
end)

Better still: no loop at all

For fixed locations, a point or zone library removes the loop entirely — the library keeps one shared loop for every zone on the server instead of one per resource.

-- ox_lib zones: one shared loop, not one per resource
lib.zones.sphere({
  coords = vec3(24.5, -1347.2, 29.5),
  radius = 2.0,
  onEnter = function() lib.showTextUI('[E] Shop') end,
  onExit  = function() lib.hideTextUI() end,
})

#The server side: the profiler

The server has a real profiler built in. Record a few hundred frames while the server is busy, then read the result in a browser — it gives you a flame graph of exactly where the tick went.

  • A server tick should complete in well under 5 ms. Sustained above that and players feel it as rubber-banding.
  • The usual offenders are database queries inside a loop, and per-player timers that all fire on the same tick.
  • A single slow synchronous MySQL call blocks the whole server, not just that player.
# in the server console
profiler record 500
profiler save
profiler view

#OneSync entity culling

Under OneSync Infinity the server decides which entities each client is told about. Tuning that is one of the few settings changes that genuinely helps a busy server, because it reduces both network traffic and client-side entity count.

set onesync_distanceCullVehicles true
set onesync_forceMigration true
set onesync_workaround763185 true

#Streamed assets and stutter

Freezing when you drive into a specific area is a streaming problem, not a script problem. Large MLOs, high-resolution vehicle textures and clothing packs all compete for the same budget.

  • Vehicle textures are the most common waste: a 4096 × 4096 dirt map on a single car is not unusual and is entirely unnecessary. 1024 is plenty.
  • Check for duplicate texture dictionaries — two resources streaming the same .ytd name is undefined behaviour and one of them will win at random.
  • Raise pool sizes only when the console actually tells you a pool is full, and raise only that pool.
  • Textures should be DXT compressed. An uncompressed .ytd costs several times the VRAM for no visible gain.
# only when the console names the pool
set sv_poolSizesIncrease "{ \"TxdStore\": 25000, \"CScriptEntityExtension\": 1000 }"

#What to do, in order

  1. 01 Measure

    resmon 1 on a client standing still, and profiler record 500 on the server.

  2. 02 Fix the top one

    Not the top five. One change, then measure again, so you know what actually helped.

  3. 03 Look for Wait(0)

    grep your resources for "Wait(0)" and read every hit. Most are wrong.

  4. 04 Check for duplicates

    Two HUDs, two fuel scripts, two notification systems — very common on servers built from a leaked base.

  5. 05 Only then tune convars

    Pool sizes and culling help a healthy server. They will not save one with a 4 ms script in it.

Questions

Does a custom minimap affect performance?
No. A minimap resource replaces texture dictionaries the game already loads and sets the radar zoom levels once. There is no per-frame cost and VRAM use is the same as the stock map — roughly 113 MB.
How many players can one server hold?
It depends entirely on your resources, not on the hardware or the slot count. A clean server with well-written scripts holds 128 comfortably; a server with a dozen Wait(0) loops struggles at 40.
Is more CPU cores the answer?
Rarely. FXServer is largely single-threaded for the main tick, so single-core clock speed matters far more than core count. Buy clock speed, not cores.
My server is fine at 20 players and unusable at 60. Why?
Something is per-player and on a loop. Look for anything that iterates GetPlayers() on a timer — the cost grows with the square of the player count when both the loop and the work inside it scale.

Ready to pick a map?

Twelve themes on three base map styles, $8 each, instant download.