Limit CPU, Memory, and Other Resources for a Service Using systemd

Occasionally, you might encounter a service that unexpectedly gobbles up your system's resources. For example, I once set up an SSH honeypot to capture malicious bot activity—but when random bots discovered it, that honeypot started using over 80% of my CPU and spiked to 12GB of RAM. As it turned out, I couldn't find a single resource that laid out all these systemd resource control options in one place, so I'll share what I've learned here.

Systemd provides built-in ways to control resources for your services by leveraging Linux Control Groups (cgroups). You can specify CPU, memory, and I/O limits to ensure that one service doesn't overwhelm your entire system.

Locating or Creating the Override File

Systemd unit files typically reside in /usr/lib/systemd/system/ or /lib/systemd/system/. If you created your own custom service from scratch, you can simply include resource-limiting options directly in that .service file.

However, for services that aren't managed by you directly—for example, services installed by your distribution or other third-party software—it's usually better to create a drop-in override rather than editing the original file. This way, your changes won't be overwritten by updates, and you avoid altering the vendor-provided defaults.

For instance, if you have a service named myservice.service, you can do:

sudo mkdir -p /etc/systemd/system/myservice.service.d
sudo nano /etc/systemd/system/myservice.service.d/override.conf

Then add your resource-limit configurations under the [Service] section in override.conf.

Common Resource Control Settings

Below are some useful settings for controlling CPU, memory, and disk I/O usage:

CPUWeight

What It Does: Assigns a relative weight to the service for CPU scheduling. If there's contention (i.e., multiple services need CPU at the same time), the CPU is shared in proportion to each service's CPU weight.

How It Works:

  • Defaults to 100
  • Accepts an integer between 1 and 10000
  • A lower value means "give me less CPU time compared to others," a higher value means "give me more CPU time."
  • If your service is the only thing running, it can still use all available CPU

Example:

[Service]
CPUWeight=20

In this example, if another service has a higher CPUWeight—say 100—my service will only get a fraction of the CPU time when contention occurs. If no other process needs the CPU, it still can use up to 100% of the CPU.

CPUQuota

What It Does:

Imposes an absolute limit on how much CPU time this service can consume, specified as a percentage of one full CPU core.

How It Works:

  • Syntax: CPUQuota=<percentage>%
  • If you have multiple cores and set CPUQuota=100%, that means it's capped at using 100% of one core. If you want to let the service use the equivalent of two full cores, you might specify 200%, and so on
  • Even if the system is otherwise idle, the service cannot exceed its specified quota

Example:

[Service]
CPUQuota=85%

This ensures the service never consumes more than 85% of a single CPU core. If you have four cores, effectively you're limiting this service to less than 85% of one core's time.

IOWeight

What It Does:

Assigns a relative weight to the service for disk I/O scheduling. When the service and other processes need to read or write from the disk simultaneously, the I/O bandwidth is distributed based on these weights.

How It Works:

  • Similar principle to CPUWeight, but this time for disk operations
  • Accepts an integer between 1 and 10000
  • The default is typically 100. If you lower it to 20, for instance, the service will only get a small portion of disk I/O bandwidth when the system is busy doing reads/writes

Example:

[Service]
IOWeight=20

In a scenario where your system is also running critical tasks that need disk access, your service will yield disk operations to them if there's contention, helping keep the system responsive.

MemoryHigh

What It Does:

Defines a soft limit on RAM usage. Once the service's processes exceed this threshold, the kernel heavily throttles their memory allocation. The processes may still allocate memory beyond this, but performance will degrade as the kernel pushes them back under the limit.

How It Works:

  • Syntax: MemoryHigh=<size>
  • <value> can be in bytes or can use suffixes like, kilobytes (K), megabytes (M), gigabytes (G), etc.
  • If the service stays below MemoryHigh, it experiences no throttling. Once above, the kernel actively reclaims memory from it.

Example:

[Service]
MemoryHigh=750M

If your service tries to go above 750 MB of RAM, it'll still run, but the kernel will try to slow it down and reclaim memory to bring it closer to 750 MB.

MemoryMax

What It Does:

Establishes a hard cap on RAM usage. If the service's processes exceed this cap and cannot be pushed back under it, the Out-Of-Memory (OOM) killer is triggered within this unit, terminating processes.

How It Works:

  • Syntax: MemoryMax=<size>
  • Like MemoryHigh, <value> can be bytes or use suffixes (K, M, G).
  • Often used alongside MemoryHigh—you set a softer throttle with MemoryHigh, and then have a strict final boundary with MemoryMax.

Example:

[Service]
MemoryMax=1G

If your service can't keep its usage below 1 GB, processes in the service cgroup will be killed to free memory.

MemoryLow

What It Does:

Sets a protection against memory reclaim: the system tries not to push the service below this usage in normal operation. This is softer than a hard limit—it's more about ensuring the service keeps a guaranteed minimum amount of memory before others can take it.

How It Works:

  • If memory is in high demand, the kernel tries to reclaim RAM from processes not protected by a MemoryLow setting (or with a lower one)
  • Great for guaranteeing a service always has enough memory to function, especially if it's critical

Example:

[Service]
MemoryLow=256M

Your service is somewhat protected for 256 MB of memory. Other services or processes get reclaimed first.

MemorySwapMax

What It Does:

Restricts or disables how much swap space the service can use.

How It Works:

  • Syntax: MemorySwapMax=<value>
  • If you set it to 0, the service will not be allowed to use swap at all
  • This can be very helpful for processes that degrade performance significantly if they start swapping
  • For some workloads, it's better to limit or disable swap usage than to let the process thrash the disk
[Service]
MemorySwapMax=0

This means the service can't allocate any swap. If it exceeds physical RAM limits (and MemoryMax or MemoryHigh are also set), it'll be throttled or killed rather than swapped out.

Putting It All Together and applying it to a service

Here's an example of a service that has all these resource control settings:

[Service]
# CPU resource controls
CPUWeight=20       # lower relative CPU priority; default is 100
CPUQuota=85%       # absolute CPU time limit (85% of one CPU core)
IOWeight=20        # lower relative disk I/O priority; default is 100

# Memory resource controls
MemoryLow=256M     # memory protection to help keep this much RAM available
MemoryHigh=750M    # soft limit above which the service is heavily throttled
MemoryMax=1G       # hard limit that triggers OOM kill if exceeded
MemorySwapMax=0    # do not allow any swap usage

You can save this configuration in a file like /etc/systemd/system/myservice.service.d/override.conf and then reload systemd with sudo systemctl daemon-reload to apply the changes. After that, you can restart the service with sudo systemctl restart myservice.service. Using a program like htop or top can help you monitor the service's resource usage and see if the limits are working as expected.

Conclusion

Even without 3rd-party tools, or complicated setups, you can use systemd quite easily to control the resources your services consume. By setting CPU, memory, and I/O limits, you can ensure that one service doesn't hog all the resources on your system — like an out-of-control SSH honeypot or a resource-hungry CI job. For more advanced cgroup tuning, you can explore additional settings like CPUQuotaPeriodSec=, AllowedCPUs=, TasksMax=, and more. Check out the systemd.resource-control Documentation for more details on these and other options.