Your server usually crashed because it hit a limit. By far the most common is running out of memory, which makes the operating system kill your process. After that come CPU exhaustion under load, a full disk, and running out of connections. A crash is rarely random, it is a resource reaching its ceiling, and the metrics at the moment it died tell you which one. Here is what actually takes apps down and how to see it.

A crash is a limit reached

The useful mental model is that a server does not crash for no reason, it crashes because it ran out of something. Every machine has finite memory, CPU, disk, and connections, and when your app tries to use more of one than it is allowed, something has to give. Usually the operating system steps in and kills the process to protect the rest of the system.

This is the fourth golden signal of monitoring: saturation, meaning how full your resources are.1 A closely related classic, Brendan Gregg's USE method, says that for every resource you should watch its utilization, its saturation (how much work is queued because it is full), and its errors, precisely because saturation is what precedes this kind of failure.2 The practical version: the resource that hit its ceiling at the moment of the crash is your cause, and there are only a few candidates.

Why your app is using so much memory

Memory is the number one crash cause for small apps, so it is worth understanding why usage climbs. It comes down to three patterns, and the shape of the memory line tells them apart:

  • A memory leak. Memory is allocated and never released, so usage climbs slowly and steadily until it hits the limit, then the app dies and restarts, and the climb begins again. A sawtooth pattern on the memory graph is the classic signature of a leak.
  • Loading too much at once. A query that returns a million rows into memory, a large file read whole, an unbounded cache. Here memory spikes sharply on specific requests rather than climbing over time.
  • Genuinely too small a box. Sometimes the app just needs more memory than you gave it. This is the one case where adding capacity is the right fix, not a workaround, but rule out the first two before you assume it.

The distinction matters because the fixes are opposite. Buying a bigger instance to paper over a leak just delays the same crash; the memory line will still climb, it will just take longer to hit the new ceiling.

The OOM killer: how out-of-memory crashes work

When a process asks for more memory than it is allowed, the operating system does not politely decline, it kills the process. On Linux this is the out-of-memory (OOM) killer. In a containerized setup it is more explicit: Kubernetes enforces memory limits through the kernel with OOM kills, so a container that exceeds its memory limit gets terminated, and you see it marked OOMKilled.3

This is why "my server just disappeared with no error in my logs" is so often a memory crash: the process was killed from outside, so it never got to log anything. The evidence is not in your application logs, it is in the memory metric climbing to the limit and the OOMKilled status on the restart. If your app dies silently and comes back, suspect memory first.

The other limits: CPU, disk, connections

Memory is the most common, not the only one. The other resources fail in their own recognizable ways.

ResourceWhat it looks likeUsual fix
MemoryUsage climbs to the limit, then OOMKilled and restartFix the leak, load less, or size up
CPUCPU pins at 100%, requests slow then time outOptimize the hot path, cache, or scale out
DiskDisk fills (often logs), writes fail, app errorsRotate and cap logs, clean up, add space
DB connectionsPool exhausted, requests hang waiting for a connectionRaise the pool cautiously, fix connection leaks
File descriptorsToo many open files, new connections refusedClose what you open, raise the ulimit

Notice that several of these appear under load, which is why an app that is fine locally falls over in production. If your crashes line up with traffic spikes, that points at CPU or connection limits; if they happen regardless of load and climb over time, that points at a memory leak or disk filling. We cover the broader "works locally, breaks in production" gap in its own guide.

Catching the climb before the crash

The good news about saturation is that it is gradual: memory climbs, CPU rises, the disk fills. That means the crash is preventable if you are watching the resource lines, because you can catch the trend and act before the ceiling. The hard part is that this usually means keeping dashboards open and knowing what normal looks like, which nobody has time for.

Fixter watches the resource signals for you and connects the dots. Instead of a dashboard you have to remember to read, it alerts when memory or CPU is trending toward its limit, and when a crash does happen it tells you which resource caused it, tying the OOMKilled or the CPU spike back to what your app was doing at the time. You ask in plain language and it has already done the correlation.

claude code
> the api keeps restarting every few hours, why?
It is being OOMKilled. Memory climbs steadily from ~180MB after each restart to the 512MB limit over about 3 hours, then the container is killed and restarts. The classic sawtooth of a leak. It started after the 07-26 deploy that added the in-memory response cache with no eviction.

A crash feels random until you see the metric that caused it, and then it is obvious. For the wider set of signals worth watching, see what to monitor when you launch your first app.

Key takeaways

  • A server crashes because it hit a limit; the resource at its ceiling when it died is the cause
  • Out of memory is the most common; a steady climb is a leak, a spike is loading too much data
  • OOMKilled means the OS killed your process for exceeding its memory limit, often with no log
  • CPU, disk, and connection limits fail in their own recognizable ways, several only under load
  • Saturation is gradual, so watching the resource lines lets you catch the climb before the crash

Frequently asked questions

Why did my server crash?

Almost always because it hit a resource limit. The most common is running out of memory, which triggers the operating system to kill your process. After that come CPU exhaustion under load, a full disk, and running out of database connections or file descriptors. A crash is usually a limit reached, not random, and the resource metrics at the time of the crash tell you which one.

Why is my app using so much memory?

Usually one of three things: a memory leak where memory is allocated and never released so usage climbs over time, loading too much data into memory at once such as a huge query result, or genuinely needing more memory than the box has. Watching the memory line tells you which: a steady climb is a leak, a spike is loading too much.

What does OOMKilled mean?

OOMKilled means Out Of Memory Killed: your process asked for more memory than it was allowed and the operating system terminated it to protect the system. In Kubernetes, memory limits are enforced by the kernel with OOM kills, so a container that exceeds its memory limit gets killed and usually restarted. Seeing OOMKilled tells you memory, not CPU or code logic, was the cause.

How do I know if a crash was memory or CPU?

Look at the resource metrics right before the crash. If memory climbed to the limit and then the process died, it was out of memory, often shown as OOMKilled. If CPU pinned at 100% and requests timed out or the health check failed, it was CPU saturation. The metric that hit its ceiling at the moment of the crash is your answer.

How do I stop my server from crashing?

Find which limit it keeps hitting and address that specific one: fix the memory leak or load less data, add capacity, cache expensive work, or move it to a background job. Watching saturation, how full each resource is, lets you catch the climb and act before the crash instead of after.