Set a CPU limit for a systemd service with a drop-in
You will finish with one service capped by a systemd CPU quota, verified through the manager and easy to remove. The examples use systemd 255.4-1ubuntu8.17 on Ubuntu 24.04, with the unified cgroup hierarchy enabled.
The route
Jump straight to the step you need, or tick off Done means at the end.
Allow about fifteen minutes. You need shell access, the exact name of an existing service, and sudo access to the system manager. Use a non-critical test service first. Restarting a service can interrupt work, so choose a maintenance window and check its current state before changing anything.
Checkpoint
This guide changes one unit through a local drop-in. It does not edit the vendor unit in /usr/lib, change the default for every service, or alter the system manager's global configuration.
1. Identify the service and record its current state
Replace example.service with the real unit name. The commands below are read-only and do not need elevated privileges:
$ UNIT=example.service
$ systemctl status "$UNIT" --no-pager
$ systemctl cat "$UNIT"
$ systemctl show "$UNIT" --property=CPUQuotaPerSecUSec --property=MemoryMax --property=Slice
The status output tells you whether the unit is active. The cat output shows the main unit and any existing drop-ins, so look for an existing [Service] section before adding another setting. The final command exposes the current CPU quota, memory limit and slice. An empty quota value means this unit has no explicit quota of its own.
Do not assume that the service name matches its executable. Ask systemctl list-unit-files or use shell completion if you are unsure. A typo can make you inspect one unit and edit another.
2. Choose a quota that the workload can survive
CPUQuota=50% means at most half of one CPU's total time. Values above 100% allow more than one CPU, so CPUQuota=200% allows up to two CPUs. This is a ceiling, not a promise that the service will receive that much time when the machine is busy.
Start with a conservative value such as 50% for a background worker, then observe its latency and logs. Do not use a CPU quota as a substitute for diagnosing a runaway process. If you also need a memory ceiling, treat it as a separate change: MemoryMax= is an absolute limit and can invoke the out-of-memory killer inside the unit. The manual recommends MemoryHigh= as the main memory control and MemoryMax= as a last line of defence.
Safety warning
Applying a quota to user.slice, system.slice or a parent slice affects multiple units. This guide targets the service itself. Slices are useful when you deliberately want a group of related units to share a limit, but that is a wider change.
3. Add a local drop-in
Use systemctl edit as root to create a drop-in. This is the only command in the guide that opens an editor:
$ sudo systemctl edit "$UNIT"
Enter the following, changing only the percentage if you have a measured reason to do so:
[Service]
CPUQuota=50%
Save the file and exit the editor. The resulting local file normally has a path like /etc/systemd/system/example.service.d/override.conf. Drop-ins under /etc/systemd/system take precedence over the packaged unit, so package upgrades can replace the vendor file without removing this local override.
Check what systemd now sees:
$ sudo systemctl cat "$UNIT"
### /usr/lib/systemd/system/example.service
...
### /etc/systemd/system/example.service.d/override.conf
[Service]
CPUQuota=50%
The first path and the surrounding unit content will vary. The important checkpoint is that the drop-in appears below the vendor unit and contains the intended section and setting.
4. Reload the manager and restart the unit
Tell PID 1 to reread unit files, then restart the service so its processes enter the new cgroup settings:
$ sudo systemctl daemon-reload
$ sudo systemctl restart "$UNIT"
$ systemctl is-active "$UNIT"
active
daemon-reload rereads configuration. It does not restart services. restart is the service-disrupting action, and its success only means the start job completed. If the service fails to start, stop here and inspect the unit rather than repeatedly restarting it:
$ systemctl status "$UNIT" --no-pager
$ journalctl -u "$UNIT" -b --no-pager -n 80
Those inspection commands are ordinary reads. Use sudo with journalctl if the journal permissions prevent you from seeing the service's messages.
5. Verify the effective quota
Confirm both the configured value and the cgroup location:
$ systemctl show "$UNIT" \
--property=CPUQuotaPerSecUSec \
--property=ControlGroup \
--value
The output is host-specific. A successful check should show a non-empty CPU quota property and a control-group path for the unit. You can also view the merged configuration again:
$ systemctl cat "$UNIT" | sed -n '/CPUQuota=/p'
CPUQuota=50%
Do not use an idle CPU reading as proof that the quota is active. A service that is doing little work will remain below its ceiling. The useful evidence is the manager's effective property, followed by normal workload monitoring.
6. Change or undo the limit
To change the quota, edit the same drop-in and replace the value:
$ sudo systemctl edit "$UNIT"
$ sudo systemctl daemon-reload
$ sudo systemctl restart "$UNIT"
To remove changes made by systemctl edit, inspect the drop-ins first, then use:
$ sudo systemctl revert "$UNIT"
$ sudo systemctl daemon-reload
$ systemctl show "$UNIT" --property=CPUQuotaPerSecUSec
systemctl revert removes local unit-file changes for that unit, not just this CPU setting. If the unit has other hand-written drop-ins, preserve or restore them first. The final command should again show no explicit quota from this override. If you changed the running service, restarting it after the revert makes the removal take effect for its processes.
7. Keep global and per-user settings separate
The system manager reads /etc/systemd/system.conf and its system.conf.d drop-ins. A user manager reads user.conf and user.conf.d instead. These are manager-wide settings, not the right place for a one-service quota.
User services are also arranged differently from ordinary system services. The system manager starts [email protected], creates /run/user/UID through [email protected], and groups the user's processes beneath user-UID.slice. If your target is a user unit, use systemctl --user for inspection and editing, and understand that a slice or manager setting may affect more than one user service.
The D-Bus manager API uses polkit for privileged operations. In practice, a failed sudo systemctl command is usually an authorisation or unit-name problem, not a reason to edit files under /usr/lib/systemd directly.
Done means
- The exact service name and its original drop-ins were recorded before editing.
- A local
[Service]drop-in contains the intendedCPUQuota=value. - The manager was reloaded and the service restarted during an acceptable maintenance window.
systemctl showreports the expected effective quota and control-group path.- You know whether undoing the change with
systemctl revertwould remove other local overrides.