If your server stutters at peak, the instinct is to rent a bigger box. Hold off. In nearly every server I have been called in to fix, the lag was not the hardware. It was a few resources doing too much work on the main thread, plus a content folder full of vehicles shipping 4K textures nobody can see.
This is how I approach FiveM server optimization: find the real offenders with the tools FiveM already gives you, fix the biggest ones first, and measure that it worked. It is written for owners and junior devs who want to do it themselves. I am oxince, and I build and optimize FiveM resources for a living, including RP servers past 1000 players, so the numbers below come from the docs and the F8 console, not a marketing page.
It is almost always resources, not hardware
FiveM's server runtime is, for practical purposes, single-threaded for script execution. Every resource, every event and every database call competes for time on one main server thread, and that thread has to finish its work each tick or it falls behind. Two things follow from that.
First, throwing cores at it does almost nothing. A 4-core CPU at 4.5 GHz beats a 16-core at 2.8 GHz for FiveM, because what matters is single-core clock speed and IPC. Extra cores do not make one busy thread faster. Second, a fast CPU cannot outrun a while true loop with no wait. If a script never yields, no amount of GHz saves you. You fix the code.
So the order is: server-side scripts first, then entity and streaming load, then client assets, and only then hardware. Hardware buys headroom at peak. It does not fix inefficient code.
Client FPS and server tick are not the same thing
These get confused constantly, so separate them first. Client FPS is how many frames a player's machine renders. It depends on their GPU, their settings, and how heavy your client-side resources and streamed assets are, so one player's FPS drop can be entirely local to them.
Server tick is how the server keeps up with its own workload each cycle. When a resource takes too long inside a tick, the thread stalls and FiveM prints a hitch warning telling you how many milliseconds it lost. The two interact: a bad client script tanks FPS for everyone running it, and a stalled server makes the whole world feel rubber-bandy. But the fixes live in different places, so when someone reports lag, work out which one they mean before you touch anything.
One note on tick rate: you do not set a fixed server tickrate in a config. Client-side ticks are frame-bound, and the docs are explicit that the time between them is non-deterministic because it is frame bound. A player at 180 FPS gets a tick about every 5.5 ms; one at 60 FPS gets one every 16.6 ms. In practice, improving tick rate means reducing the work done per tick so the thread stays inside its budget. That is what the rest of this guide is about.
Step 1: profile before you touch anything
Do not optimize by vibes. Measure first. Open the console with F8 and run:
resmon 1The resource monitor shows CPU and memory usage per resource: a live table of every running resource with its CPU time in milliseconds. Here is how to read it.
- The number that matters is CPU time in milliseconds. A full frame at 60 FPS is only about 16.6 ms total, so a resource sitting at 1 ms is already a real slice, and several of them add up fast.
- Compare idle against active. Watch a resource with nothing happening, then while the relevant gameplay is running (driving, opening an inventory, standing in a crowd). Something cheap at idle that spikes under load is your real suspect.
- A working rule of thumb: anything sitting noticeably above idle, roughly 0.5 ms at rest or into the multiple-ms range under load, earns a closer look. These are not engine-enforced limits, just the right ballpark for triage.
Two related commands are worth knowing: strmem 1 for streaming memory, where texture and model bloat shows up, and netgraph 1 for live network metrics. Sort by CPU, screenshot it, and write down your top three to five offenders. That list is your work queue.
Step 2: fix the biggest script offenders
Loops that do not yield enough
This is the classic: a thread doing per-frame work it does not need to.
-- Bad: heavy work every single frame
Citizen.CreateThread(function()
while true do
Citizen.Wait(0)
local ped = PlayerPedId()
DoExpensiveCheck(ped) -- runs every tick for no reason
end
end)The hard rule from the docs: a while true do loop with no Wait will crash the client. Always yield. But yielding Wait(0) when you do not need per-frame precision is nearly as wasteful, since it still runs every tick. Split the work by how often it actually needs to run, and use adaptive waits.
-- Good: cheap most of the time, responsive when it matters
Citizen.CreateThread(function()
while true do
local nearby = GetNearbyThings()
if #nearby > 0 then
Citizen.Wait(1) -- responsive only when needed
else
Citizen.Wait(500) -- idle cheaply otherwise
end
end
end)A real example from a community optimization writeup: a marker resource went from 0.40 ms to between 0.01 and 0.05 ms by moving the expensive filtering into a slow 500 ms thread, keeping only the drawing on a fast loop, and filtering markers by zone first. Same feature, roughly 10 to 40 times cheaper.
Native calls and distance math
- Cache natives that rarely change.
PlayerPedId()andPlayerId()do not need re-fetching every frame. Cache them in a slower thread. - Do not use a native for simple math. Replace
GetDistanceBetweenCoords(...)with Lua vector math:#(coordsA - coordsB). In that same writeup, this one change shaved about 0.15 ms off a 500-marker loop. - Keep expensive natives off the per-frame path. Shape tests and line-of-sight probes should be throttled or event-driven, never run every tick if you can avoid it.
Events and net traffic
With a lot of players, server events get expensive fast, because every triggered event is work multiplied by who it touches. Do not broadcast to everyone when you mean one client, and do not fire a net event every frame when a state change would do. Validate and rate-limit anything a client can trigger. An unbounded client-triggerable event is both a performance hole and a security one.
Step 3: tame your database
Database calls are a top source of thread stalls, because a blocking query holds the thread while it waits on MySQL.
- Use async, non-blocking queries.
oxmysqlis async by default; its synchronous variants can block the main thread, so keep them out of hot paths. Moving off legacymysql-asyncusually lowers per-resource ms and tail latency on query-tied actions. - Index the columns you filter on, like
identifier,citizenidor plate. An unindexedWHEREon a large table becomes a full scan that gets slower as your player base grows. - Batch your writes. Do not run one INSERT or UPDATE per item in a loop; build a batched query, use prepared statements, and pool connections.
Step 4: fix vehicle and asset streaming
If your script CPU is clean but players still stutter and load slowly, especially driving into dense areas, it is almost always streamed content. On an RP server with a few hundred add-on vehicles, every connecting client can be streaming several gigabytes of texture data, and most of it is wasted.
Here is the part people get wrong, stated accurately: the engine does not refuse oversized textures. Many add-on cars ship 2K to 4K diffuse and normal maps inside their .ytd. The engine downscales them, but only after loading them at full resolution into VRAM during streaming. So you pay the bandwidth, the load time and the VRAM, and get nothing on screen for it. About 1024px is the practical ceiling for most vehicle textures; treat it as a target, not a hard cap the engine enforces.
- Resize textures to sane sizes. Around 1024px for vehicle body and diffuse; 512px is plenty for many secondary maps. Liveries that must stay legible can go higher on purpose.
- Use proper block compression with mipmaps: BC7 or DXT5/BC3 for diffuse, BC5 for normals. Keep textures power-of-two (512, 1024, 2048), which for RAGE assets is a safety rule, not a style choice, or you risk texture bleed.
- Inspect any heavy
.ytd. A texture dictionary climbing toward 16 MB is a well-known inspection trigger in the community, not an engine pass or fail rule, but a strong signal the asset is worth opening up. - Audit MLOs the same way. Custom interiors carry the same texture bloat.
This is grind work, but it is often the single biggest win for stutter and load times on an asset-heavy server.
Step 5: control entity load with OneSync
Beyond static assets, the live world generates load. Abandoned scripted entities, runaway prop spawns and high AI density all cost sync work every tick.
OneSync is the server-side entity synchronization system, and past 48 slots it is effectively mandatory (it is free up to 48 players; larger needs a subscription tier). Its core performance trick is culling: the server only syncs entities within a relevant range of each player, a focus zone documented at 424 units, so clients are not paying for entities across the map. That culling is exactly why OneSync scales to high player counts. Where it can hurt is dense scopes: scope events carry costs proportional to how many players are crowded together, so a 200-player nightclub on one spot is genuinely harder on the server than 200 players spread across the map.
- Clean up entities you create. Server-created entities that are never deleted pile up. Use server-authoritative creation and manage entity lifetime deliberately.
- Use routing buckets for instancing, so an instanced job or a separate game mode is not syncing against the main population.
- Do not over-spawn. Cap props and scripted peds, and thin AI density in busy zones.
Step 6: re-measure
Optimization is not done when you have made a change. It is done when you have proven the change helped. Go back to F8, run resmon 1 under the same conditions (same zone, same player load if you can), and compare against the numbers from Step 1. Confirm the offender dropped and that you did not just push the cost somewhere else.
The whole loop in one line: profile with resmon 1, fix the biggest offender, re-measure, repeat. Pick the worst thing each pass. Do not micro-optimize a 0.02 ms resource while a 6 ms one sits untouched.
The hardware reality, briefly
Once your resources are clean, hardware gives you headroom, in this order:
- Single-core CPU performance, meaning high clock speed and strong IPC. This is the one that actually moves FiveM.
- An NVMe SSD. Meaningfully faster asset reads than SATA, which helps load times and reduces sync hiccups during streaming.
- RAM, in adequate quantity and scaled with player count and content, but it is the least likely to be your bottleneck.
If you have done the script and asset work and still hit a wall at peak, then a faster single-core host is a real upgrade. Not before.
When to bring in help
You can get a long way with resmon 1, a methodical pass through your resources and a weekend of texture cleanup. There is a point where it pays to have someone who does this daily: resources you did not write, escrow assets you cannot fully audit, or a 300-plus player server where the interactions get genuinely complex. That is the work I do, with a profile-driven audit that shows you the before and after in resmon.
FAQ
Why does my FiveM server lag even with good hardware?
Because FiveM script execution is effectively single-threaded. Your performance is gated by one main thread, not by total cores. A single unoptimized resource (a loop with no proper wait, a blocking database query, a per-frame native) can stall that thread no matter how strong the CPU is. Profile with resmon in the F8 console; the lag almost always traces to a specific resource.
What is a good CPU ms reading in resmon?
There is no engine-enforced number, but as a triage rule: a resource consistently above about 0.5 ms at idle is worth a look, and anything spiking into the multiple-ms range under normal play is a prime hitch suspect. A full frame at 60 FPS is only about 16.6 ms, so a few resources at 1 to 2 ms each add up quickly. Always compare idle cost against cost while the relevant gameplay is active.
Are FiveM FPS drops the server's fault or the client's?
Often the client's. Client FPS depends on the player's GPU, their graphics settings, and how heavy your client-side scripts and streamed assets are, so a drop can be local to one player. A struggling server shows up differently: hitch warnings in the console and rubber-banding for everyone at once. Work out which one you are seeing before changing anything.
Do oversized vehicle textures really matter if the game downscales them anyway?
Yes. The engine downscales textures above about 1024px, but only after loading them into VRAM at full resolution during streaming. So a 4K car texture costs you full bandwidth, load time and VRAM for no on-screen benefit. On a server with hundreds of add-on vehicles, that is several gigabytes streamed to every client. Resizing to around 1024px with proper compression is one of the biggest single wins for stutter and load times.
References
- Cfx.re docs: OneSync — entity sync, culling and routing buckets.
- Cfx.re docs: Citizen.Wait — tick timing and the no-wait crash rule.
- oxmysql — async database handling for FiveM.