1. Introduction

Linux offers a robust mechanism for setting hard and soft limits via the ulimit utility. Since this is the de facto standard for capping system resource usage, it’s also integrated with the systemd initialization system and its units.

In this tutorial, we describe how to set limits in systemd units. First, we map systemd to ulimit settings. Next, we check how to set limits in systemd unit files. Finally, we discuss defaults.

We tested the code in this tutorial on Debian 11 (Bullseye) with GNU Bash 5.1.4. It should work in most POSIX-compliant environments.

2. systemd to ulimit Equivalent Limits

To begin with, we can map systemd unit limit properties to their equivalent ulimit commands:

+------------------+-----------+--------------------------+------+
| systemd          | ulimit    | Description              | Used |
+------------------+-----------+--------------------------+------+
| LimitCORE=       | ulimit -c | core files size          | Yes  |
| LimitDATA=       | ulimit -d | data segment size        | No   |
| LimitNICE=       | ulimit -e | niceness level           | Yes  |
| LimitFSIZE=      | ulimit -f | file size                | No   |
| LimitSIGPENDING= | ulimit -i | queued signals           | Yes  |
| LimitMEMLOCK=    | ulimit -l | locked memory size       | Yes  |
| LimitRSS=        | ulimit -m | resident set size        | No   |
| LimitNOFILE=     | ulimit -n | open file descriptors    | No   |
| LimitMSGQUEUE=   | ulimit -q | POSIX message queue size | Yes  |
| LimitRTPRIO=     | ulimit -r | real-time priority       | Yes  |
| LimitRTTIME=     | ulimit -R | runtime before blocking  | Yes  |
| LimitSTACK=      | ulimit -s | stack size               | Yes  |
| LimitCPU=        | ulimit -t | CPU time                 | No   |
| LimitNPROC=      | ulimit -u | available processes      | -    |
| LimitAS=         | ulimit -v | process virtual memory   | No   |
| LimitLOCKS=      | ulimit -x | file lock count          | Yes  |
+------------------+-----------+--------------------------+------+

Importantly, the default unit of all size limits is bytes, LimitCPU uses seconds, while LimitRTTIME is in microseconds. The rest are unitless numbers and levels.

Of course, we can also append unit specifiers to change the default when assigning:

+----------+-------------------------+--------------+
| Quantity | Designation             | Unit         |
+----------+-------------------------+--------------+
| Size     | K                       | Kilobytes    |
| Size     | M                       | Megabytes    |
| Size     | G                       | Gigabytes    |
| Size     | T                       | Terabytes    |
| Size     | P                       | Petabytes    |
| Size     | E                       | Exabytes     |
+----------+-------------------------+--------------+
| Time     | usec, us, µs            | Microseconds |
| Time     | msec, ms                | Milliseconds |
| Time     | seconds, second, sec, s | Seconds      |
| Time     | minutes, minute, min, m | Minutes      |
| Time     | hours, hour, hr, h      | Hours        |
| Time     | days, day, d            | Days         |
| Time     | weeks, week, w          | Weeks        |
| Time     | months, month, M        | Months       |
| Time     | years, year, y          | Years        |
+----------+-------------------------+--------------+

Furthermore, some limits are better avoided or have no effect, so they are marked as No in the Used column. For example, MemoryMax= usually handles memory restrictions better than LimitDATA, LimitNOFILEor LimitAS, while LimitRSS doesn’t work on Linux. A better equivalent of LimitNPROC is TasksMax=.

3. Setting systemd Limits

For example, let’s see a SAMBA service unit file:

$ cat /usr/lib/systemd/system/smbd.service
[Unit]
Description=Samba SMB Daemon
Documentation=man:smbd(8) man:samba(7) man:smb.conf(5)
Wants=network-online.target
After=network.target network-online.target nmbd.service winbind.service

[Service]
Type=notify
PIDFile=/run/samba/smbd.pid
LimitNOFILE=16384
EnvironmentFile=-/etc/default/samba
ExecStartPre=/usr/share/samba/update-apparmor-samba-profile
ExecStart=/usr/sbin/smbd --foreground --no-process-group $SMBDOPTIONS
ExecReload=/bin/kill -HUP $MAINPID
LimitCORE=infinity

[Install]
WantedBy=multi-user.target

Here, we see two limits:

  • LimitNOFILE=16384, setting the maximum open file handles to 16384
  • LimitCORE=infinity, removing the limit over the maximum core file size

If we simply assign to a specific systemd option as in the smbd.service file, both the hard and soft limits are set:

LimitNOFILE=16384

On the other hand, we can set each separately by separating them with a colon like SOFT:HARD:

LimitNOFILE=8192:16384

Finally, we can remove limits in systemd via infinity in the same way that we can use unlimited with ulimit.

4. Defaults and systemd Limits

For limits that we don’t set explicitly, systemd-system.conf provides options to set defaults. In fact, the names of the default values in systemd-system.conf are equivalent to the Limit* options but have a Default prefix.

Specifically for systemd user services, Linux also has predefined kernel and per-user values. Of course, user services can’t set limits above those of their environmental context. In other words, a login process may set limits below those of a user service. In that case, the former generally overrides the latter.

So, we can mostly lower limits in user services. While raising them is possible, it requires privileges or special mechanics like encapsulating the actual manager that runs the user service.

5. Summary

In this article, we saw how to set limits within systemd units.

In conclusion, most ulimit flags are available as Limit* options when using systemd unit files, but they should be used with care.

Comments are open for 30 days after publishing a post. For any issues past this date, use the Contact form on the site.