What cgroups and systemd limits give you
cgroups limits let you restrict a service’s CPU, memory, I/O and process count without changing its code: the directives go into the systemd unit and the kernel enforces them. That way one runaway service cannot take resources from the rest. Creating the service itself is covered in the article how to create a systemd service.
| Directive | What it limits | Values |
|---|---|---|
| CPUQuota= | the CPU time ceiling | percent: 20% — no more than 20% of one core’s time, above 100% — time on several cores |
| CPUWeight= | the CPU share under contention | 1 to 10000, default 100 |
| MemoryHigh= | the soft memory threshold | bytes with suffixes K, M, G, T, a percentage or infinity |
| MemoryMax= | the hard memory limit | the same units |
| MemorySwapMax= | swap usage limit | bytes, a percentage of swap size or infinity |
| IOWeight= | the block I/O share | 1 to 10000, default 100 |
| TasksMax= | the number of tasks in the unit | a number, a percentage or infinity |
The values come from man systemd.resource-control. Check your systemd version with systemctl --version: the man page describes the latest release.
How to check that you have cgroup v2
There is one check: if the file /sys/fs/cgroup/cgroup.controllers exists, the system uses cgroup v2. According to the Kubernetes documentation and the rootlesscontaine.rs project, v2 is the default in Debian 11 and later, Ubuntu 21.10 and later (22.04 LTS and later is recommended), RHEL 9 and later, and Fedora 31 and later.
CPU: CPUQuota and CPUWeight
CPUQuota= sets a hard ceiling: 20% means the service’s processes will never get more than 20% of one core’s time, and a value above 100% gives time on several cores, for example 200% for two. CPUWeight= sets no ceiling and only matters under contention: a service gets an idle CPU entirely to itself.
CPUWeight= takes values from 1 to 10000, default 100, and divides the CPU among services of one slice in proportion to their weights.
Memory: MemoryHigh, MemoryMax and swap
MemoryMax= is the hard limit: if usage cannot be contained, the OOM killer is invoked inside the unit. MemoryHigh= is the soft threshold: processes are heavily slowed down and memory is taken away from them, but the OOM killer is not invoked. The systemd documentation recommends MemoryHigh= as the main control mechanism and MemoryMax= as the last line of defence.
In practice that means a MemoryHigh= set too low shows up as a slow service, and a MemoryMax= set too low as a killed one. How the OOM killer picks a victim is explained in the article the OOM killer in Linux.
MemorySwapMax= sets an absolute limit on a service’s swap usage: the value 0 forbids it swap entirely. On swap, see the article setting up swap in Linux.
I/O and the number of processes
IOWeight= divides block I/O bandwidth among services of one slice in proportion to the weight, from 1 to 10000, default 100. TasksMax= limits the number of tasks in the unit; the default comes from DefaultTasksMax= in systemd-system.conf. The effect of IOWeight= depends on the device and kernel settings, so verify it by measuring.
How to apply limits: a drop-in step by step
The best way is a drop-in: a small file in the directory /etc/systemd/system/<service>.service.d/ that extends the unit and leaves the original alone. The systemctl edit command creates it for you, and systemctl edit --full opens a full copy of the unit. The values below are examples: tune them to your own service.
- Create the file limits.conf in the drop-in directory or run
sudo systemctl edit myapp.service. - Put the directives in the [Service] section, as in the example below.
- Run
sudo systemctl daemon-reload. - Restart the service:
sudo systemctl restart myapp.service.
# /etc/systemd/system/myapp.service.d/limits.conf
[Service]
CPUQuota=50%
CPUWeight=50
MemoryAccounting=1
MemoryHigh=512M
MemoryMax=768M
MemorySwapMax=0
IOAccounting=1
IOWeight=50
TasksMax=200MemoryAccounting=1 and IOAccounting=1 switch accounting on for systemd-cgtop. MemoryHigh= sits below MemoryMax= so that slowing down kicks in first and killing only after it.
How to see the result
systemctl show prints the values in force, and systemd-cgtop shows load by group: it orders cgroups by CPU, memory or disk load. Check right after the restart and again under load. The data is incomplete until MemoryAccounting=1 and IOAccounting=1 are enabled for the services you care about.
sudo systemctl daemon-reload
sudo systemctl restart myapp.service
systemctl show myapp.service -p MemoryHigh -p MemoryMax -p MemorySwapMax -p TasksMax
systemctl show myapp.service | grep -i cpuquota
systemd-cgtopThe quota property name in systemctl show can differ from the directive name, so the example greps for cpuquota. If a service is still slow after the limits, move on to profiling: Linux performance profiling.
Limits for an ordinary user
For an ordinary, non-root user session only the memory and pids controllers are delegated by default; cpu and io have to be delegated separately. This comes from the rootlesscontaine.rs project documentation, not from man systemd, and the default depends on the distribution: look at /lib/systemd/system/user@.service.
# /etc/systemd/system/user@.service.d/delegate.conf
[Service]
Delegate=cpu cpuset io memory pidsAfter creating the file run systemctl daemon-reload and log in again or reboot the server. The cpuset controller requires systemd 244 or later. System services started by root need no delegation: the directives in the unit are enough.
Frequently asked questions
Is CPUQuota=100% the whole server?
No. 100% is the time of one core. A service with CPUQuota=100% will not take more than one core, and to give it two cores you set 200%.
Where do I start if I do not know how much memory a service needs?
With measuring: enable accounting and watch the service in systemd-cgtop under normal load. Then set MemoryHigh= with headroom above the typical value and MemoryMax= above that.
What should ZevsHost customers do?
On shared hosting the panel manages resource limits, so there is nothing to configure. On a VPS or dedicated server you manage the services: the owner writes the drop-in file with the limits.