I remember sitting in a dark apartment three years ago, staring at a blinking cursor while my service silently vanished for the fifth time that hour. There was no stack trace, no error log, and no helpful message—just a sudden, hollow silence in the telemetry. Most people will tell you that you need a massive, expensive observability suite or a specialized distributed tracing tool to solve this, but that’s usually just a way to throw money at a symptom. If you’re trying to figure out how to debug a process that gets killed, the answer is rarely found in a fancy dashboard; it’s usually buried in the kernel logs or a misunderstood resource limit that you missed because you were too busy looking at the application layer.
I’m not here to sell you on a new framework or a premium monitoring subscription. Instead, I want to walk through the actual, unglamorous steps I’ve learned from getting it wrong in production enough times to know better. We’re going to look at OOM killers, signal handling, and the subtle ways the OS decides your code is no longer worth the effort. My goal is to give you a repeatable mental model so that the next time a process disappears, you aren’t staring at your screen in confusion, but actually finding the cause before the sun comes up.
Table of Contents
Dont Guess Look at the Linux Kernel Logs Dmesg

When a process disappears without a trace, your first instinct is usually to check your application logs. Don’t bother. If the process was killed by the OS, it didn’t have the chance to write a “goodbye” message to your log aggregator. Instead, you need to go straight to the source: the linux kernel logs dmesg. This is where the kernel records its decisions, and more importantly, where it leaves a paper trail when it decides to start executing a process.
I’ve lost count of how many times I’ve wasted an hour debugging a memory leak in my code, only to find out the kernel had already stepped in and terminated the process minutes earlier. If you’re doing any kind of out of memory killer troubleshooting, searching for the string “OOM” in your dmesg output is the fastest way to stop guessing. It will tell you exactly which process was targeted, how much memory it was hogging, and—crucially—which other processes were being squeezed at the time.
It’s important to distinguish between a standard process kill and a full-blown kernel panic. A panic takes the whole machine down; a process kill is just the kernel being a strict landlord. If you see those OOM signatures, you aren’t looking at a logic bug; you’re looking at a resource mismatch.
Decoding the Truth Hidden in Analyzing Exit Codes

If `dmesg` tells you that something happened, the exit code tells you how it died. It’s the first thing I check after I’ve had enough coffee to actually think. Most people see a non-zero exit code and just assume the application crashed because of a bug in the code, but that’s often a trap. If you see a `137`, you aren’t looking at a logic error; you’re looking at a process that was forcibly terminated by a `SIGKILL`. In my experience, that almost always points to out of memory killer troubleshooting scenarios. The OS saw your process hogging resources and decided it was the most efficient sacrifice to keep the rest of the system breathing.
You also need to distinguish between a standard crash and a signal-driven termination. If the exit code is `139`, you’re likely dealing with a segmentation fault—the classic “I tried to touch memory I don’t own” error. However, when working with modern orchestration, you have to be careful about systemd resource limits or container memory constraints masking the true cause. Sometimes the process doesn’t even get a chance to log a meaningful error because the environment itself pulled the rug out from under it. I’ve learned the hard way that a `137` in a container doesn’t always mean the host is out of RAM; it often means you hit a hard limit defined in your manifest.
Five things I learned from watching processes vanish into thin air
- Check your resource limits before you blame the code. I’ve spent way too many hours debugging a “random” crash only to realize the process was hitting a cgroup memory limit or a ulimit on file descriptors. If you aren’t monitoring your limits, you’re just guessing.
- Look for the silent killers in your orchestrator. If you’re running in Kubernetes, a process dying isn’t always a code issue; it’s often the Liveness probe failing or the OOMKiller stepping in because your pod exceeded its request. Check `kubectl describe pod` before you start tearing apart your logic.
- Audit your signals. If your process is receiving a SIGTERM or SIGINT, it’s being told to stop by something else—a deployment script, a scaling event, or a human. If it’s a SIGKILL, something is being much more aggressive, and that usually means the OS has lost patience.
- Don’t ignore the sidecars. In modern setups, the process might be fine, but the logging agent or the service mesh proxy next to it might be choking. If the environment around your process is unstable, your process is going to look unstable too.
- Trace the system calls if the logs are empty. When a process dies without leaving a single line of output, `strace` is your best friend. It’s heavy and you shouldn’t run it in production indefinitely, but seeing exactly which system call triggered the exit is the only way to stop the guessing game.
What I've learned from the wreckage
Stop trying to fix the code before you check the kernel; if the OS killed your process, no amount of refactoring will stop it from happening again until you address the resource constraints or the signal.
Exit codes aren’t just random numbers—they are the only breadcrumbs the system leaves behind, and if you aren’t looking for the difference between a clean exit and a signal-induced death, you’re just guessing.
The goal isn’t just to get the process running again, but to find the specific limit—be it memory, file descriptors, or a timeout—that was hit, so you don’t end up back in this same position three months from now.
The post-mortem mindset

At the end of the day, debugging a killed process isn’t about being a wizard; it’s about following the breadcrumbs left by the operating system. If you’ve checked your `dmesg` logs for OOM killer signatures and you’ve actually sat down to decode what those exit codes are telling you, you’re already ahead of 90% of the people panicking in the on-call rotation. You stop guessing whether it was a segfault or a resource limit and start acting on evidence. It’s a tedious process of elimination, but it’s the only way to move from “the server is acting weird” to “we need to adjust our cgroup limits.”
I’ve spent enough late nights staring at a terminal to know that these failures feel personal, but they rarely are. A process dying is just the system doing exactly what it was told to do—protecting itself from a runaway resource or a corrupted memory address. Instead of getting frustrated, I try to view every crash as a free lesson in system architecture. If you take the time to document what happened and why the fix worked, you aren’t just fixing a bug; you’re making sure you never have to solve this specific problem again. That’s how you actually get some sleep.
Frequently Asked Questions
What if I'm running in a container and dmesg is empty or restricted?
If you’re running in a container, `dmesg` is often a dead end because the kernel belongs to the host, not your namespace. If you can’t access the host logs, you’re flying blind. My first move is checking the orchestrator—if it’s Kubernetes, `kubectl describe pod` is your best friend; it’ll often show the OOMKill reason. If that fails, I look at the container runtime logs or, if I’m lucky, I have a sidecar monitoring resource usage.
If it's not an OOM kill, how do I distinguish between a segmentation fault and a deliberate SIGTERM from an orchestrator?
If `dmesg` didn’t scream about an OOM killer, you’re likely looking at a crash or a graceful shutdown. Check your application logs for the specific signal. A segmentation fault usually leaves a trail of “invalid memory access” or a core dump if you’ve enabled them. A `SIGTERM` is different—it’s polite. If you see your app logging “received shutdown signal,” that’s your orchestrator (like Kubernetes) deciding your pod has overstayed its welcome.
Are there specific patterns in the logs that point to hardware failure versus a software bug?
It’s usually a matter of pattern recognition at this point. Software bugs are predictable; they follow a logic, even if it’s a broken one. You’ll see a specific stack trace or a recurring memory leak pattern in your application logs right before the crash. Hardware failure is much more chaotic. If you’re seeing random I/O errors, filesystem corruption, or bit flips that don’t correlate with any specific workload, stop looking at your code and start looking at the disk.
How do I set up automated alerts so I don't have to manually check the exit codes after the fact?
I’ve learned the hard way that checking logs after the damage is done is a losing game. I usually set up a simple sidecar or a cron job that monitors the process status and pushes a metric to Prometheus. Once you have that, you can just trigger an Alertmanager notification to Slack. It’s not fancy, but getting a ping the second an exit code isn’t 0 saves me from waking up to a catastrophe.
