Fine‑Grained Memory Limits with Linux cgroups v2: Hard, Soft, and Minimum Floors
When running containers on Linux, cgroups v2 lets you enforce precise memory limits. This guide shows how to set hard, soft, and minimum thresholds, demonstrates a practical setup, and warns against common pitfalls.
19 Feb 2024, 22:18 UTC

Problem: Unpredictable OOM Kills in Containers
When a container consumes more memory than the host can spare, the kernel’s Out‑of‑Memory (OOM) killer may terminate the container or even the host process. This can happen even if the container is only supposed to use a fraction of the node’s RAM. The root cause is that the default cgroup configuration does not enforce hard limits, nor does it provide a graceful reclamation window. The result is sudden crashes and unreliable service availability.
Takeaway: Use cgroups v2’s memory.max, memory.high, and memory.min to control memory use predictably.
cgroups v2 unifies resource controllers and introduces three memory knobs:
- memory.max – a hard cap that triggers the OOM killer inside the cgroup when exceeded.
- memory.high – a soft limit that starts background reclamation before hitting the hard cap.
- memory.min – a floor that guarantees the cgroup always retains at least this amount of memory.
Prerequisites
These steps assume a recent Linux kernel (≥5.5) that supports cgroups v2. Most modern distributions enable the unified hierarchy by default, but if you see /sys/fs/cgroup/cgroup.controllers missing, add the kernel parameter systemd.unified_cgroup_hierarchy=1 to your boot loader and reboot.
Step 1 – Verify the cgroup hierarchy
# cat /sys/fs/cgroup/cgroup.controllers | grep memory
memory
If memory appears, the memory controller is active. If not, enable it in /etc/systemd/cgroup.conf with DefaultContainerLimit=memory or reboot with the kernel flag mentioned above.
Step 2 – Create a dedicated cgroup for the container
# mkdir -p /sys/fs/cgroup/myapp
# echo memory > /sys/fs/cgroup/myapp/cgroup.controllers
These commands run as root. The echo line activates the memory controller for the new group.
Step 3 – Set hard, soft, and minimum limits
# echo 200M > /sys/fs/cgroup/myapp/memory.max
# echo 150M > /sys/fs/cgroup/myapp/memory.high
# echo 50M > /sys/fs/cgroup/myapp/memory.min
Replace the megabyte values with numbers that match your workload. The units can be K, M, G, or a raw byte count. The hard limit is 200 MiB; if the container tries to allocate more, the kernel will kill it. The soft limit of 150 MiB triggers background reclamation, allowing the process to continue while the kernel frees pages. The 50 MiB floor guarantees the container always keeps at least this amount.
Step 4 – Attach the container process to the cgroup
Assume you have a simple C program that allocates memory in a loop:
# cat > memalloc.c
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
int main() {
while (1) {
void *p = malloc(10 * 1024 * 1024); // 10 MiB
if (!p) { perror("malloc"); break; }
printf("Allocated 10 MiB\n");
sleep(1);
}
return 0;
}
# gcc -O2 memalloc.c -o memalloc
Run it under the cgroup:
# echo $$ > /sys/fs/cgroup/myapp/cgroup.procs
# ./memalloc
The process ID is written to cgroup.procs, which attaches it to the group. Observe the allocation pattern. When the total memory used approaches 150 MiB, the kernel will start reclaiming pages; once it reaches 200 MiB, the process will be killed by the OOM killer confined to the cgroup.
Step 5 – Verify OOM behavior
After the process is killed, check the kernel log:
# dmesg | tail -n 20
You should see an entry similar to:
Out of memory: Kill process 1234 (memalloc) score 42 or sacrifice child
Killed process 1234 (memalloc) total-vm: 210000kB, anon-rss: 200000kB
Notice the OOM message references the cgroup name myapp. This confirms that the hard limit worked as intended.
Common Mistakes and How to Avoid Them
- Setting memory.max too low – If you set the hard limit to a value that is lower than the memory needed for the container’s startup routine, the process will die immediately. Test the configuration with a short script before deploying to production.
- Ignoring memory.high – Without a soft limit, the kernel will not start reclaiming until it hits the hard limit, which can cause a sudden OOM kill. Always set memory.high to a value that gives the kernel breathing room.
- Not setting memory.min for critical services – If a container needs a baseline amount of RAM (e.g., a database cache), omit memory.min and you risk the kernel reclaiming memory that the service relies on, leading to performance degradation.
- Assuming cgroup.v2 is enabled by default – Some older distributions still use cgroups v1. Verify
/sys/fs/cgroup/cgroup.controllersbefore proceeding.
Limitations
cgroups v2’s memory controller does not provide per‑process accounting inside the group. If you need finer granularity, you must use cgroups v1 or a tool like systemd-run that manages separate groups per service. Additionally, the OOM killer will kill the entire process tree of the cgroup, which can be disruptive if the container runs multiple processes.
Practical Checklist for Production
- Confirm cgroups v2 is active.
- Define memory.max according to the peak memory usage of the workload.
- Set memory.high to roughly 75–80% of memory.max.
- Set memory.min to the minimal memory the service needs to stay responsive.
- Deploy a test container and monitor
dmesgfor OOM events. - Automate the cgroup configuration in your container runtime (Docker, systemd-nspawn, etc.) using the appropriate flags or unit files.
Conclusion
By leveraging cgroups v2’s memory.max, memory.high, and memory.min, you can enforce hard caps, allow graceful reclamation, and guarantee a memory floor for critical workloads. This reduces the risk of sudden OOM kills, improves stability, and gives you a clear tuning path for container memory management.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.