A FiveM server that crashes or hangs feels random when you are staring at a frozen console, but it almost never is. There is always a last instruction the server ran before it died, and there is almost always a single resource or query responsible. The job is to read the evidence in the right order instead of guessing. This guide gives you the method we use on real production servers: read the logs, classify the symptom, then bisect the resources until the cause is isolated.
Before anything else, separate two failure modes that people lump together. A crash is when the FXServer process exits, the players get dropped, and txAdmin shows the server as offline or restarting. A hang is when the process is still alive but stops doing useful work: the console stops scrolling, players rubber-band or time out, and you see thread hitch warnings climbing. They have different causes and different evidence, so name which one you have first.
Concept: the server runs one main thread
The FXServer game logic, including every server-side Lua script, runs on a single main thread that ticks roughly every frame. Each resource gets a slice of that tick. If one resource blocks, every other resource waits behind it. This is the single most important mental model for diagnosis, because it explains why one badly written script can freeze an entire server even though the machine has spare CPU and RAM.
When a resource holds the thread too long, the server prints a hitch warning that looks like this:
hitch warning: frame time of 312 milliseconds
server thread hitch warning: timer interval of 1200 milliseconds
A hitch is the server telling you it could not finish a tick on time. One isolated hitch during a restart is normal. Repeated hitches, or a single hitch in the multiple-second range, means a resource is doing blocking work on the main thread. The number is your severity gauge, and the resource that logs immediately around it is your first suspect.
Step 1: read the txAdmin console and the FXServer log
Open txAdmin and go to the live console, then also pull the saved log from the txData folder. The live console is great for catching the moment of failure, but it scrolls away, so the on-disk log is what you analyze after a crash. You are looking for three things, in order.
First, the last line printed before the freeze or exit. On a hang, the console simply stops; whatever resource printed last, or was about to tick next, is suspect. On a crash, look for a line beginning with Sys_Error, F: followed by a stack frame, or unhandled exception, which point at a native-level fault rather than a script logic problem.
Second, scan for the hitch warnings described above and note which resource name appears next to them. FiveM often attributes the hitch to a resource directly, for example [resourceName] script timeout or a Lua traceback inside that resource.
Third, look for script errors that repeat. A single Lua error is logged once and the script keeps running, but an error inside a tight loop floods the console and can itself cause a hang. The traceback names the file and line number. That is free diagnosis; do not skip past red text.
Step 2: classify the symptom before you touch anything
Resist the urge to reinstall things. Match what you see to a cause class.
A hang with climbing hitch warnings and no process exit means blocking work on the main thread: an infinite loop, a Citizen.Wait that is missing, or a synchronous database call. A clean process exit with Sys_Error or an exception usually means a native crash from a specific resource, a corrupt stream asset, or an out-of-memory condition. A crash that only happens when a particular player joins or uses a feature points at that feature's resource. A crash under player load that the empty server never shows points at memory growth or a query that gets slower as a table fills.
Writing down the class narrows your search from every resource to a handful.
Step 3: bisect resources with ensure and stop
This is the core technique and it always works because of the single-thread model. You are doing a binary search for the guilty resource.
Start from a known-good baseline. In your server.cfg, the order of ensure lines matters because dependencies must load first. To test, stop half your non-essential resources from the txAdmin console with stop resourceName, or comment out their ensure lines and restart. If the crash or hang disappears, the cause is in the half you removed; if it persists, it is in the half you kept. Re-add resources in groups, using ensure resourceName to start one cleanly, until the symptom returns. The last resource you added back is your cause.
For hangs specifically, restart resourceName is your friend. If restarting one resource makes the hitches stop for a while and then they return, that resource is leaking or looping. Keep frameworks like es_extended, ox_core, or qbx_core in the baseline since pulling them breaks everything; bisect the gameplay resources around them instead.
Step 4: read the actual crash with a dump
When FXServer crashes hard on Windows it writes a crash dump and usually shows a CfxCrashDumpUploader window with a crash ID. Save that ID. The dump and the lines just above the crash in the log are what distinguish a script fault from an engine fault.
If the dump and log point inside a specific resource's native call, you have your culprit. If the crash mentions streaming or an asset hash, you have a bad stream file: a malformed YDR, YTD, or YMAP that the engine cannot load. Pull custom vehicles, MLOs, and ped or weapon addons out of your stream folders and add them back in groups using the same bisection method, because a single corrupt model can take the process down on load.
Mechanism: why Lua loops and missing waits hang the server
Every server-side Citizen.CreateThread runs on that one main thread. A loop inside a thread must yield with Citizen.Wait(ms) so the scheduler can run other resources. A loop with no wait, or a wait of zero inside heavy work, never gives the thread back. Here is the classic bug:
Citizen.CreateThread(function()
while true do
doExpensiveWork()
-- no Citizen.Wait here, so this never yields
end
end)
The server enters this loop and never returns to the scheduler, so every other resource starves and the console freezes. The fix is a wait that matches how often the work truly needs to run. Polling every frame with Wait(0) is rarely necessary; most logic is fine at Wait(1000) or longer, and event-driven code with RegisterNetEvent is better than polling at all.
A subtler version is an unbounded for loop over a growing table, or recursion with no base case. These do not loop forever in theory, but they hold the thread for hundreds of milliseconds, which is exactly what the hitch warning measures.
Mechanism: oxmysql slow queries and connection exhaustion
Database calls are the other common hang. oxmysql is asynchronous by default, so MySQL.query with a callback or an awaited query does not block the main thread. The trouble starts when code does heavy synchronous work, fires hundreds of queries in a tight loop, or runs a query with no index that scans an entire table on every player save.
Watch for two patterns. First, a save loop that queries inside a for over all players with no batching, which floods the connection pool. Second, an unindexed query against a large table, for example a SELECT on a player identifier column that has no index, which gets slower as your database grows. Symptoms appear under load and look like creeping hitches and timeouts rather than an instant crash. oxmysql ships with slow-query logging on at a default threshold of 150 milliseconds, so any query over that prints a warning with its SQL text; you can tune the threshold with the mysql_slow_query_warning convar in your server.cfg, for example set mysql_slow_query_warning 100 to catch more, or raise it to cut noise. That warning often points straight at the offending resource and statement, after which adding an index or moving the work off the hot path fixes it.
Mechanism: memory leaks and OneSync limits
A server that runs fine empty but crashes hours into a busy session is usually leaking memory. Watch RAM in txAdmin over time. A leak shows as a slow, monotonic climb that never recovers after players leave. The leak is almost always a Lua table that gets appended to but never cleared, or entities created server-side and never deleted. Bisect by restarting resources one at a time and watching whether memory drops back; the resource whose restart frees a chunk of RAM is leaking.
Also confirm your OneSync setting matches your slot count. Running a high player count without onesync on in server.cfg, or exceeding entity limits, produces instability and desync that can present as crashes. This is configuration, not code, so check it early.
A note on sv_scriptHookAllowed
Do not enable sv_scriptHookAllowed to fix crashes. Allowing ScriptHook lets clients load arbitrary native mods and is a common source of client-side crashes, cheats, and instability that get blamed on the server. Keep it at the default of disabled unless you have a deliberate reason, and treat a server where it is enabled as a suspect during any crash investigation.
Putting the method together
The sequence is always the same. Reproduce the symptom and name it as a crash or a hang. Read the log and find the last line and any hitch warnings. Classify the cause from the evidence. Bisect resources with stop and ensure until one resource owns the symptom. Then read that resource's hot loops for missing waits, its queries for missing indexes, and its tables for unbounded growth. The single-thread model is what makes this reliable: there is one place the work happens, so there is one place the failure lives.