# My containers keep getting OOMKilled—how do I set memory limits correctly for PHP-FPM and Go?

> Measure the working set instead of raising the limit; compute max_children against the cap in PHP-FPM and set GOMEMLIMIT plus GOMAXPROCS in Go.

- Asked: 2026-10-07
- Answered: 2026-10-13
- Asked by: Melis
- Tags: docker, kubernetes, performans
- Source: https://muhammetsafak.com/just-ask/containers-oomkilled-set-memory-limits-php-fpm-go/
- Language: en-US
- Author: Muhammet Şafak

---
**Question:** On Kubernetes we run both PHP-FPM (Laravel) and Go services. Running locally with no limits everything looks fine, but in production under load the pods keep getting OOMKilled and restarting.

How do I set the memory request/limit values correctly for PHP-FPM and Go containers? Beyond just raising the limit forever, which application-level knobs should I look at?


Short answer: OOMKilled isn't an application bug, it's the signal that the container exceeded its cgroup memory limit.

## Short answer

Locally there's no limit, so the kernel lets it grow; in production, limit + load means it hits the ceiling and gets killed. The fix isn't to blindly raise the limit but to measure the real peak and tune your app knobs to live under it.

## Why

1. **Measure first: what's the working set?** Look at `container_memory_working_set_bytes`. Set the limit above the observed peak with a reasonable margin. "Fine locally" is misleading because there's no limit there; the real question is peak usage under load.

2. **In PHP-FPM the killer is pm.max_children.** Total memory ≈ `max_children × average memory per request`. A giant `max_children` copied from a tutorial spawns all workers under load and blows the limit. The formula: `max_children = (container limit − margin) / worker peak`. Measure worker peak from a single process's actual RSS.

3. **PHP's `memory_limit` does not protect the container.** That setting is per-process; it gives no container-wide guarantee and can even let a single worker grow up to `memory_limit`. Your real shield against the container ceiling is `max_children`; keep `memory_limit` sane but don't mistake it for container protection.

4. **The Go runtime doesn't know the cgroup limit by default.** It paces GC by heap growth (`GOGC`), not by the container's limit; in a limited container that means [over-allocating and getting OOMed](/just-ask/go-gc-pressure-sync-pool-and-escape-analysis-under-load/). The fix: set `GOMEMLIMIT` (Go 1.19+) a bit below the limit — it's a soft limit that makes GC more aggressive as you approach it. Also set `GOMAXPROCS` to the CPU limit (`automaxprocs`) — before Go 1.25 the scheduler would otherwise run against every core on the node; since Go 1.25 (August 2025) the runtime auto-detects the cgroup's CPU quota and sets GOMAXPROCS accordingly.

   ```yaml
   # Go container
   resources:
     requests: { memory: "256Mi" }
     limits:   { memory: "512Mi" }
   env:
     - { name: GOMEMLIMIT, value: "450MiB" } # limit − margin (soft limit)
     - { name: GOMAXPROCS, value: "1" }      # match the CPU limit
   ```

## What to do

1. **In K8s, requests = typical, limits = peak + margin.** Always leave headroom for non-heap usage (stacks, buffers, off-heap, PHP extensions); if you set the limit too tight, even a normal burst kills the pod. If you want no surprises, consider request = limit for Guaranteed QoS.

2. **Distinguish a leak from a high peak.** If `working_set` plateaus at a high level under load, it's a sizing problem — tune the limit and knobs to fit. If it climbs monotonically and never plateaus, it's a memory leak; raising the limit only delays it, and you need to fix the code.

**Bottom line:** personally I'd measure the working set first and set the limit to observed peak + margin. In PHP-FPM I'd pin `max_children` against the limit via the formula; in Go I'd set `GOMEMLIMIT` + `GOMAXPROCS`. I'd always leave headroom for non-heap and watch the graph to tell a leak from a high peak. Raising the limit forever isn't a fix — it's just a way to postpone the real problem.

## Related Reading

- [Should I define the healthcheck in the Dockerfile or leave it to Kubernetes probes?](https://muhammetsafak.com/just-ask/should-i-define-the-healthcheck-in-the-dockerfile-or-leave-it/) — Just Ask
- [How do I keep reflection cost under control in an attribute-based mapping layer in production?](https://muhammetsafak.com/just-ask/keep-reflection-cost-under-control-mapping-layer-production/) — Just Ask
- [My Go worker in a container never receives SIGTERM and won't shut down gracefully—is this a PID 1 problem?](https://muhammetsafak.com/just-ask/go-worker-never-receives-sigterm-pid-1-problem/) — Just Ask
