Error reference

Exit Code 137: SIGKILL, OOMKilled, and How to Fix Both

Updated July 27, 2026

In short
Exit code 137 means the process was killed with SIGKILL (128 + 9), which cannot be caught, blocked, or handled. In containers this almost always means the kernel OOM killer enforced a memory limit (Kubernetes reports it as OOMKilled) or a SIGTERM grace period expired and the runtime escalated to SIGKILL. Confirm with kubectl describe pod <pod-name>: if Last State shows Reason: OOMKilled, raise the memory limit or fix the leak; if it shows Error right after a deploy, your shutdown handling is too slow.
start here
kubectl describe pod <pod-name> | grep -A 5 "Last State"
Diagnosis

Causes at a glance

CauseHow to recognize itFix
Container exceeded its memory limitkubectl describe pod shows Reason: OOMKilledRaise resources.limits.memory or fix the leak: see the oomkilled guide
Node ran out of memoryNode events show SystemOOM or MemoryPressure; several pods die at onceSet accurate memory requests so the scheduler stops overpacking nodes
Grace period expired after SIGTERMExit 137 with Reason: Error, timed with deploys or scale-downsHandle SIGTERM faster or raise terminationGracePeriodSeconds
Runtime heap bigger than the container limitJVM or Node app dies at the same memory ceiling under loadCap the heap below the limit (MaxRAMPercentage, --max-old-space-size)
docker stop timeoutExit 137 after docker stop on a slow-to-exit containerIncrease the grace period: docker stop -t 30 <container>

What exit code 137 actually means

Signal deaths are reported as 128 + signal number; SIGKILL is 9, so 137. SIGKILL never reaches the process: the kernel terminates it outright. That is why 137 crashes leave no goodbye message: no final log line, no flushed buffers, no cleanup. The evidence lives outside the process, in pod state and kernel logs.

kubectl get pod <pod-name> -o jsonpath='{.status.containerStatuses[0].lastState.terminated}'
# {"exitCode":137,"reason":"OOMKilled","startedAt":...,"finishedAt":...}
Step 1

Step 1: confirm whether it was an OOM kill

Kubernetes stamps the reason on the container status: Reason: OOMKilled means the kernel enforced the cgroup memory limit. Reason: Error with exit 137 means SIGKILL came from somewhere else, usually grace-period escalation. For node-level OOM (no limit set, node exhausted), the kernel log on the node has the verdict.

kubectl describe pod <pod-name>
# Last State:  Terminated
#   Reason:    OOMKilled
#   Exit Code: 137

# on the node — kernel OOM killer evidence:
journalctl -k | grep -i "out of memory"
Step 2

Step 2, fix an OOM kill: measure, then set real limits

Look at actual consumption before touching limits: kubectl top pod <pod-name> --containers for current usage, your metrics stack for the shape over time. If usage plateaus just above the limit, the limit is simply too small. If it climbs without bound, you have a leak and a bigger limit only postpones the kill.

For memory, setting requests equal to limits (Guaranteed QoS for that resource) makes behavior predictable: the pod is scheduled with exactly the memory it may use.

kubectl top pod <pod-name> --containers

# then, in the deployment:
resources:
  requests:
    memory: "1Gi"
  limits:
    memory: "1Gi"
Step 3

Step 3: align the language runtime heap with the limit

Runtimes size their heaps from what they think the machine has. Modern JVMs are container-aware but default to using only 25% of container memory for heap (MaxRAMPercentage), while an overridden -Xmx larger than the limit guarantees OOM kills under load. Node.js sizes its old space from available memory and can overshoot the cgroup cap on older versions.

Set the heap explicitly to roughly 75-80% of the container limit, leaving headroom for stacks, off-heap buffers, and the runtime itself.

# JVM (limit 2Gi -> heap up to ~1.5Gi)
JAVA_TOOL_OPTIONS="-XX:MaxRAMPercentage=75.0"

# Node.js (limit 2Gi -> heap 1536 MB)
NODE_OPTIONS="--max-old-space-size=1536"

When 137 is not an OOM kill

If Reason is Error and the deaths line up with deploys, scale-downs, or node drains, the SIGKILL is the grace-period escalation: SIGTERM was sent, the process did not exit within terminationGracePeriodSeconds (default 30s), and the kubelet force-killed it. The fix is on the shutdown path: handle SIGTERM promptly (and check the PID 1 signal-forwarding trap) or raise the grace period. Outside Kubernetes, docker stop does the same escalation after 10 seconds.

Beyond the one-off fix

Memory limits do not lower themselves: a human or, increasingly, an AI agent edits the manifest. If an agent "optimizes" resources and prod starts throwing 137s an hour later, you need to connect those dots fast. Nexus keeps an audit trail of every agent-made change and can block policy violations (like an agent cutting production memory limits) before they merge, not after the OOM kills start.

Explore Swfte Nexus

Common questions

What is the difference between exit code 137 and OOMKilled?
137 is the exit code (SIGKILL); OOMKilled is the reason Kubernetes attaches when the kill came from the kernel memory-limit enforcement. Every OOMKilled container exits 137, but not every 137 is an OOM kill, grace-period escalation after SIGTERM produces 137 with Reason: Error.
Can my application catch or handle SIGKILL?
No. SIGKILL is delivered by the kernel and terminates the process without it ever running a handler. Any cleanup you need must happen earlier: in a SIGTERM handler during the grace period, or continuously (checkpointing, write-ahead logs).
Why is my pod OOMKilled when the node has plenty of free memory?
Because the limit is enforced per container by its cgroup, not by node availability. The moment the container exceeds resources.limits.memory, the OOM killer fires regardless of how much memory the node has free.
Does exit code 137 always mean I need more memory?
No. It can mean a leak (more memory only delays the kill), a runtime heap configured larger than the container limit, or a SIGKILL that had nothing to do with memory: a grace period expiring after SIGTERM. Check the Reason field first.
How do I find which process the OOM killer chose on a node?
Read the kernel log on the node: journalctl -k | grep -i oom (or dmesg). The OOM killer logs the cgroup, the chosen process, its RSS, and the oom_score at the time of the kill.