SYSTEMD.EXEC(5)
๐ SYSTEMD.EXEC(5) โ systemd.exec ๐
๐ธ NAME ๐ธ
systemd.exec โ execution environment configuration โจ
๐ฆ SYNOPSIS ๐ฆ
service.service, socket.socket, mount.mount, swap.swap
โจ DESCRIPTION โจ
okay so unit config files for services, sockets, mount points, and swap devices all share a little subset of config options that define the execution environment of spawned processes. it's giving shared DNA, bestie ๐งฌ
this man page lists the configuration options shared by these four unit types. for the common options of all unit config files, go see systemd.unit(5). for more info on the specific unit config files, check systemd.service(5), systemd.socket(5), systemd.swap(5), and systemd.mount(5). the execution-specific options live in the [Service], [Socket], [Mount], or [Swap] sections, depending on the unit type. pick your fighter ๐
plus, options that control resources through Linux Control Groups (cgroups) are listed in systemd.resource-control(5). those options complement the ones here. no cap. ๐
๐ IMPLICIT DEPENDENCIES ๐
a few execution parameters result in extra, automatic dependencies being added. it's lowkey magic but highkey documented:
- units with
WorkingDirectory=,RootDirectory=,RootImage=,RuntimeDirectory=,StateDirectory=,CacheDirectory=,LogsDirectory=orConfigurationDirectory=set automatically gain dependencies of typeRequires=andAfter=on all mount units required to access the specified paths. this is equivalent to having them listed explicitly inRequiresMountsFor=. โจ - units with
PrivateTmp=yesautomatically gain dependencies of typeWants=andAfter=on all mounts required to access/tmp/and/var/tmp/and an automaticAfter=dependency onsystemd-tmpfiles-setup.service(8), unlessDefaultDependencies=nois specified. ifDefaultDependencies=nois specified, and aRequiresMountsFor=/tmp/,WantsMountsFor=/tmp/,After=tmp.mount, orRootDirectory=/RootImage=are not specified,PrivateTmp=yesis converted toPrivateTmp=disconnected. ๐ - units with
PrivateTmp=disconnectedautomatically gain dependencies of typeWants=andAfter=on the mount required to access/var/, unlessDefaultDependencies=noand/orRootDirectory=/RootImage=are specified. ifDefaultDependencies=nois specified, and aRequiresMountsFor=/var/,WantsMountsFor=/var/,After=var.mount,RootDirectory=/RootImage=are not specified, the private mount on/tmp/is reused for/var/tmp/by setting$TMPDIRappropriately. ๐ - units whose standard output or error output is connected to journal or kmsg (or their combinations with console output, see below) automatically acquire dependencies of type
After=onsystemd-journald.socket. ๐ - units using
LogNamespace=will automatically gain ordering and requirement dependencies on the two socket units associated withsystemd-journald@.serviceinstances. ๐
๐ PATHS ๐
the following settings may be used to change a service's view of the filesystem. please note that the paths must be absolute and must not contain a ".." path component. no sneaky parent-directory shenanigans, bestie ๐ซ
โจ ExecSearchPath= โจ
takes a colon separated list of absolute paths relative to which the executable used by the Exec*= (e.g. ExecStart=, ExecStop=, etc.) properties can be found. ExecSearchPath= overrides $PATH if $PATH is not supplied by the user through Environment=, EnvironmentFile= or PassEnvironment=. assigning an empty string removes previous assignments and setting ExecSearchPath= to a value multiple times will append to the previous setting. ๐
added in version 250. โญ
๐ WorkingDirectory= ๐
takes a directory path relative to the service's root directory specified by RootDirectory=, or the special value "~". sets the working directory for executed processes. if set to "~", the home directory of the user specified in User= is used. if not set, defaults to the root directory when systemd is running as a system instance and the respective user's home directory if run as user. if the setting is prefixed with the - character, a missing working directory is not considered fatal. if RootDirectory=/RootImage= is not set, then WorkingDirectory= is relative to the root of the system running the service manager. note that setting this parameter might result in additional dependencies to be added to the unit (see above). ๐ก
๐ณ RootDirectory= ๐ณ
takes a directory path relative to the host's root directory (i.e. the root of the system running the service manager). sets the root directory for executed processes, with the pivot_root(2) or chroot(2) system call. if this is used, it must be ensured that the process binary and all its auxiliary files are available in the new root. note that setting this parameter might result in additional dependencies to be added to the unit (see above). ๐ฑ
the MountAPIVFS= and PrivateUsers= settings are particularly useful in conjunction with RootDirectory=. for details, see below. ๐ง
if RootDirectory=/RootImage= are used together with NotifyAccess= the notification socket is automatically mounted from the host into the root environment, to ensure the notification interface can work correctly. ๐ฌ
note that services using RootDirectory=/RootImage= will not be able to log via the syslog or journal protocols to the host logging infrastructure, unless the relevant sockets are mounted from the host, specifically: ๐ข
the host's os-release(5) file will be made available for the service (read-only) as /run/host/os-release. it will be updated automatically on soft reboot (see: systemd-soft-reboot.service(8)), in case the service is configured to survive it. ๐
Example 1. Mounting logging sockets into root environment
BindReadOnlyPaths=/dev/log /run/systemd/journal/socket /run/systemd/journal/stdout
in place of the directory path a ".v/" versioned directory may be specified, see systemd.v(7) for details. ๐
this option is only available for system services, or for services running in per-user instances of the service manager in which case PrivateUsers= is implicitly enabled (requires unprivileged user namespaces support to be enabled in the kernel via the "kernel.unprivileged_userns_clone=" sysctl). ๐
๐ฝ RootImage= ๐ฝ
takes a path to a block device node or regular file as argument. this call is similar to RootDirectory= however mounts a file system hierarchy from a block device node or loopback file instead of a directory. the device node or file system image file needs to contain a file system without a partition table, or a file system within an MBR/MS-DOS or GPT partition table with only a single Linux-compatible partition, or a set of file systems within a GPT partition table that follows the UAPI.2 Discoverable Partitions Specification[1]. ๐ผ๏ธ
when DevicePolicy= is set to "closed" or "strict", or set to "auto" and DeviceAllow= is set, then this setting adds /dev/loop-control with rw mode, "block-loop" and "block-blkext" with rwm mode to DeviceAllow=. see systemd.resource-control(5) for the details about DevicePolicy= or DeviceAllow=. also, see PrivateDevices= below, as it may change the setting of DevicePolicy=. ๐๏ธ
units making use of RootImage= automatically gain an After= dependency on systemd-udevd.service. โ๏ธ
the host's os-release(5) file will be made available for the service (read-only) as /run/host/os-release. it will be updated automatically on soft reboot (see: systemd-soft-reboot.service(8)), in case the service is configured to survive it. ๐
in place of the image path a ".v/" versioned directory may be specified, see systemd.v(7) for details. ๐ฆ
when enabled for services running in per-user instances of the service manager this option implicitly enables PrivateUsers= (requires unprivileged user namespaces support to be enabled in the kernel via the "kernel.unprivileged_userns_clone=" sysctl) and also relies on systemd-mountfsd.service(8). ๐งโ๐ป
added in version 233. โญ
๐๏ธ RootImageOptions= ๐๏ธ
takes a comma-separated list of mount options that will be used on disk images specified by RootImage=. optionally a partition name can be prefixed, followed by colon, in case the image has multiple partitions, otherwise partition name "root" is implied. options for multiple partitions can be specified in a single line with space separators. assigning an empty string removes previous assignments. for a list of valid mount options, please refer to mount(8). ๐งพ
valid partition names follow the Discoverable Partitions Specification[1]: root, usr, home, srv, esp, xbootldr, tmp, var. ๐
this option is only available for system services and is not supported for services running in per-user instances of the service manager. ๐ซ
when enabled for services running in per-user instances of the service manager using mount options is disabled by default, due to the security implications. it is possible to use a polkit[2] policy to allow specific mount options, for example: ๐ก๏ธ
Example 3. A polkit policy that allows mounting the root partition with nosuid /etc/polkit-1/rules.d/mountoptions.rules:
polkit.addRule(function(action, subject) {
if (action.id == "io.systemd.mount-file-system.mount-untrusted-image-privately" &&
action.lookup("mount_options") == "root:nosuid") {
return polkit.Result.YES;
}
});
added in version 247. โญ
๐ RootEphemeral= ๐
takes a boolean argument. if enabled, executed processes will run in an ephemeral copy of the root directory or root image. the ephemeral copy is placed in /var/lib/systemd/ephemeral-trees/ while the service is active and is cleaned up when the service is stopped or restarted. if RootDirectory= is used and the root directory is a subvolume, the ephemeral copy will be created by making a snapshot of the subvolume. โจ
to make sure making ephemeral copies can be made efficiently, the root directory or root image should be located on the same filesystem as /var/lib/systemd/ephemeral-trees/. when using RootEphemeral= with root directories, btrfs(5) should be used as the filesystem and the root directory should ideally be a subvolume which systemd can snapshot to make the ephemeral copy. for root images, a filesystem with support for reflinks should be used to ensure an efficient ephemeral copy. ๐ฟ
this option is only available for system services and is not supported for services running in per-user instances of the service manager. ๐ซ
(quick note on the option right above this โ Added in version 254. โจ)
RootHash=โ bestie, this one takes a data integrity (dm-verity) root hash, either written out in hexadecimal or as a path to a file that contains the root hash in ASCII hexadecimal format. It's what turns on data integrity checking with dm-verity โ but only if the image you're using actually carries the appropriate integrity data (peep above for that ๐) or if you're usingRootVerity=. The hash you hand over has to match the root hash of the integrity data, and it's usually at least 256 bits long (so like 64 formatted hexadecimal characters) โ that's the case for SHA256, for example.
If you don't specify this option at all, but the image file is carrying the "user.verity.roothash" extended file attribute (go see xattr(7)), then the root hash gets read straight from that attribute, also as formatted hexadecimal characters. And if the extended file attribute isn't there (or the underlying file system just doesn't support it), but there's a file with the .roothash suffix sitting next to the image file with otherwise the same name โ the root hash is read from that and used automatically, again as formatted hexadecimal characters. One little exception tho: if the image has the .raw suffix, then the root hash file must not have it in its name. ๐
Now, if the disk image has a separate /usr/ partition, that can be Verity protected too! In that case the root hash may be configured via an extended attribute "user.verity.usrhash" or a .usrhash file adjacent to the disk image. Heads up tho: there's currently no option to configure the root hash for the /usr/ file system via the unit file directly. ๐ญ
When this is enabled for services running in per-user instances of the service manager, it implicitly enables PrivateUsers= โ which requires unprivileged user namespaces support to be enabled in the kernel via the "kernel.unprivileged_userns_clone=" sysctl โ and it also relies on systemd-mountfsd.service(8). ๐
Added in version 246.
RootHashSignature=โ this one takes a PKCS7 signature of theRootHash=option, either as a path to a DER-encoded signature file, or as an ASCII base64 string encoding of a DER-encoded signature with"base64:"stuck on the front. The dm-verity volume only gets opened if the signature of the root hash is valid and signed by a public key present in the kernel keyring. No valid sig, no volume, periodt. ๐
If you don't specify this option, but there's a file with the .roothash.p7s suffix next to the image file with otherwise the same name, the signature gets read from it and used automatically. (Same .raw suffix rule as above: if the image has the .raw suffix, the signature file must not have it in its name.) ๐ซถ
If the disk image contains a separate /usr/ partition, it may also be Verity protected, in which case the signature for the root hash may be configured via a .usrhash.p7s file adjacent to the disk image. And again: there's currently no option to configure the root hash signature for the /usr/ via the unit file directly. ๐ญ
When enabled for services running in per-user instances of the service manager, this implicitly enables PrivateUsers= (requires unprivileged user namespaces support enabled in the kernel via the "kernel.unprivileged_userns_clone=" sysctl) and also relies on systemd-mountfsd.service(8). โจ
Added in version 246.
RootVerity=โ takes the path to a data integrity (dm-verity) file. This option enables data integrity checks using dm-verity ifRootImage=is used and a root-hash is passed and the image you're using doesn't itself contain the integrity data. The integrity data has to be matched by the root hash โ no matching, no vibe. ๐ง
If this option isn't specified, but a file with the .verity suffix is found next to the image file with otherwise the same name, the verity data gets read from it and used automatically. (You already know the .raw drill: if the image has the .raw suffix, the verity data file must not have it in its name.) ๐
โ ๏ธ This option is supported only for disk images that contain a single file system, without an enveloping partition table. Images that contain a GPT partition table should instead include both the root file system and matching Verity data in the same image, implementing the Discoverable Partitions Specification[1]. So like, don't try to force it, bestie.
When enabled for services running in per-user instances of the service manager, this implicitly enables PrivateUsers= (requires unprivileged user namespaces support enabled in the kernel via the "kernel.unprivileged_userns_clone=" sysctl) and also relies on systemd-mountfsd.service(8). ๐
Added in version 246.
RootImagePolicy=,MountImagePolicy=,ExtensionImagePolicy=โ these take an image policy string as persystemd.image-policy(7), used when mounting the disk images (DDI) specified inRootImage=,MountImage=, andExtensionImage=, respectively. If you don't specify anything, here's the default policy string forRootImagePolicy=andMountImagePolicy=:
root=verity+signed+encrypted+unprotected+absent: \
usr=verity+signed+encrypted+unprotected+absent: \
home=encrypted+unprotected+absent: \
srv=encrypted+unprotected+absent: \
tmp=encrypted+unprotected+absent: \
var=encrypted+unprotected+absent
And the default policy for ExtensionImagePolicy= is:
root=verity+signed+encrypted+unprotected+absent: \
usr=verity+signed+encrypted+unprotected+absent
Added in version 254. ๐
RootMStack=โ takes a path to asystemd.mstack(7)directory that encapsulates a mount stack made up of layers and bind mounts. Just likeRootDirectory=andRootImage=, this runs the service off a distinct root file system โ except in this case it's set up via"overlayfs". Stacked, layered, iconic. ๐
Since .mstack/ directories may reference disk images (DDIs), similar device policy extensions and dependencies are in effect when RootMStack= is used as they are if RootImage= is used. Same energy, different option.
In place of the image path, a ".v/" versioned directory may be specified โ see systemd.v(7) for the deets. ๐ฆ
When enabled for services running in per-user instances of the service manager, this implicitly enables PrivateUsers= (requires unprivileged user namespaces support enabled in the kernel via the "kernel.unprivileged_userns_clone=" sysctl) and also relies on systemd-mountfsd.service(8). ๐
Added in version 260. (yes, the future is now โจ)
MountAPIVFS=โ takes a boolean argument. If it's on, a private mount namespace gets created for the unit's processes, and the API file systems/proc/,/sys/,/dev/and/run/(as an empty"tmpfs") are mounted inside of it โ unless they're already mounted. ๐
โ ๏ธ Real talk: this option has no effect unless you use it in conjunction withRootDirectory=/RootImage=, because those four mounts are generally mounted in the host anyway โ and if the root directory isn't changed, the private mount namespace will just be a 1:1 copy of the host's and include these four mounts. No notes.
Also note: the /dev/ file system of the host is bind mounted if you use this option without PrivateDevices=. So if you wanna run the service with a private, minimal version of /dev/, combine this option with PrivateDevices=. ๐
So that mounts can be propagated at runtime in a safe manner, /run/systemd/propagate/ on the host will be used to set up new mounts, and /run/host/incoming/ in the private namespace will be used as an intermediate step to store them before they get moved to the final mount point. Little pit stop, then home. ๐
Added in version 233.
BindLogSockets=โ takes a boolean argument. If true, sockets fromsystemd-journald.socket(8)get bind mounted into the mount namespace. This is especially useful when a different instance of/run/is employed, so you can make sure processes running in the namespace can still make use ofsd-journal(3). ๐ซถ
This option is implied when LogNamespace= is used, when MountAPIVFS=yes, or when PrivateDevices=yes is used in conjunction with either RootDirectory= or RootImage=. Automatic slay.
Added in version 257.
ProtectProc=โ takes one of"noaccess","invisible","ptraceable"or"default"(which it defaults to). When set, this controls the"hidepid="mount option of the"procfs"instance for the unit, which is what controls which directories with process metainformation (/proc/PID) are visible and accessible: ๐"noaccess"โ the ability to access most of other users' process metadata in/proc/is taken away for the processes of the service."invisible"โ processes owned by other users are hidden from/proc/."ptraceable"โ all processes that cannot beptrace()'ed by a process are hidden to it."default"โ no restrictions on/proc/access or visibility are made.
For further details see The /proc Filesystem[3]. It's generally recommended to run most system services with this option set to "invisible". ๐
This option is implemented via file system namespacing, so it cannot be used with services that shall be able to install mount points in the host file system hierarchy. Also, the root user is unaffected by this option โ so to actually be effective it has to be used together with User= or DynamicUser=yes, and also without the "CAP_SYS_PTRACE" capability, which also allows a process to bypass this feature. It cannot be used for services that need to access metainformation about other users' processes. This option implies MountAPIVFS=. ๐ค
If the kernel doesn't support per-mount point hidepid= mount options, this setting remains without effect, and the unit's processes will be able to access and see other processes as if the option wasn't used at all. ๐
This option is only available for system services and is not supported for services running in per-user instances of the service manager.
Added in version 247.
ProcSubset=โ takes one of"all"(the default) and"pid". If you set it to"pid", all files and directories not directly associated with process management and introspection are made invisible in the/proc/file system configured for the unit's processes. This controls the"subset="mount option of the"procfs"instance for the unit. For further details see The/procFilesystem[3]. ๐ง
โ ๏ธ Heads up bestie: Linux exposes various kernel APIs via /proc/, which get made unavailable with this setting. Since these APIs are used frequently, this option is useful only in a few, specific cases, and is not suitable for most non-trivial programs. So likeโฆ know before you go. ๐
Lowkey</p> this is the same vibe as ProtectProc= up there: it's all done with file system mount namespacing, so the exact same restrictions apply. It's only for system services, it disables mount propagation to the host mount table, and it implies MountAPIVFS=. And just like ProtectProc=, this setting gets gracefully disabled if your kernel doesn't support the "subset=" mount option of "procfs". No cap. ๐ซถ
Added in version 247.
โจ BindPaths=, BindReadOnlyPaths= โจ
Okay bestie, these two set up unit-specific bind mounts. A bind mount makes a particular file or directory show up at an extra spot in the unit's view of the file system. Every bind mount you make with this option belongs to that unit only โ it is not visible in the host's mount table. Slay, your own private little mirror. ๐ช
This option wants a whitespace-separated list of bind mount definitions. Each definition is a colon-separated triple:
- source path
- destination path
- option string
...where the last two are optional. If you only give a source path, the source and destination are taken to be the same. The option string is either "rbind" or "norbind" โ that's for a recursive or a non-recursive bind mount. And if you omit the destination path, you gotta omit the option string too. Also, each definition can be prefixed with "-", and then it just gets ignored when its source path doesn't exist or isn't accessible. ๐
BindPaths=makes regular writable bind mounts (unless the source file system mount is already marked read-only).BindReadOnlyPaths=makes read-only bind mounts.
You can use these settings more than once โ every usage appends to the unit's list of bind mounts. And if you assign the empty string to either of these two options, the whole list of bind mounts defined before that point gets reset. Heads up though: when that happens, both read-only and regular bind mounts are reset, no matter which of the two settings you used to do it. ๐
Using this option implies that a mount namespace gets allocated for the unit, i.e. it implies the effect of PrivateMounts= (see below).
This is especially iconic when you're using RootDirectory=/RootImage=. In that case the source path points at a path on the host file system, while the destination path points at a path below the root directory of the unit.
โ ๏ธ The destination directory has to exist, or systemd must be able to create it. So you cannot use these options for mount points nested underneath paths listed inInaccessiblePaths=, or under/home/and other protected directories whenProtectHome=yesis set. UseTemporaryFileSystem=with":ro"orProtectHome=tmpfsinstead.
Added in version 233.
๐ธ MountImages= ๐ธ
This one is like RootImage=, in that it mounts a file system hierarchy from a block device node or a loopback file โ but plot twist: here you can specify the destination directory too, plus mount options. โจ
This option expects a whitespace-separated list of mount definitions. Each definition is a colon-separated tuple of source path and destination definitions, optionally followed by another colon and a list of mount options.
Mount options can be defined either as:
- a single comma-separated list of options โ in which case they get implicitly applied to the root partition on the image, or
- a series of colon-separated tuples of partition name and mount options.
Valid partition names and mount options are the same as for the RootImageOptions= setting described above. ๐
Each mount definition may be prefixed with "-", in which case it will be ignored when its source path does not exist. The source argument is a path to a block device node or a regular file. If source or destination contain a ":", it needs to be escaped as "\:". The device node or file system image file has to follow the same rules as specified for RootImage=. And any mounts created with this option are specific to the unit โ not visible in the host's mount table. ๐ซ
These settings may be used more than once, each usage appends to the unit's list of mount paths. If the empty string is assigned, the entire list of mount paths defined prior to this is reset.
โ ๏ธ Again, the destination directory must exist or systemd must be able to create it. So it is not possible to use those options for mount points nested underneath paths specified inInaccessiblePaths=, or under/home/and other protected directories ifProtectHome=yesis specified.
When DevicePolicy= is set to "closed" or "strict", or set to "auto" and DeviceAllow= is set, then this setting adds /dev/loop-control with rw mode, and "block-loop" and "block-blkext" with rwm mode to DeviceAllow=. See systemd.resource-control(5) for the details about DevicePolicy= or DeviceAllow=. Also, see PrivateDevices= below, because it may change the setting of DevicePolicy=. ๐
When enabled for services running in per-user instances of the service manager, this option implicitly enables PrivateUsers= (which requires unprivileged user namespaces support to be enabled in the kernel via the "kernel.unprivileged_userns_clone=" sysctl) and also relies on systemd-mountfsd.service(8).
When enabled for services running in per-user instances of the service manager, using mount options is disabled by default, due to the security implications. It is possible to use a polkit[2] policy to allow specific mount options, for example:
Example 5. A polkit policy that allows mounting the root partition with nosuid โ /etc/polkit-1/rules.d/mountoptions.rules:
polkit.addRule(function(action, subject) {
if (action.id == "io.systemd.mount-file-system.mount-untrusted-image-privately" &&
action.lookup("mount_options") == "root:nosuid") {
return polkit.Result.YES;
}
});
Added in version 247.
๐ฆ ExtensionImages= ๐ฆ
Sibling of MountImages=, same whole deal of mounting a file system hierarchy from a block device node or a loopback file โ except instead of giving a destination path, an overlay gets set up. So chic. ๐
This option expects a whitespace-separated list of mount definitions. Each definition is a source path, optionally followed by a colon and a list of mount options.
A read-only OverlayFS will be set up on top of the /usr/ and /opt/ hierarchies for sysext images, and on top of the /etc/ hierarchy for confext images. The order the images are listed in decides the order the overlay is laid down: images specified first to last result in overlayfs layers bottom to top. Think of it like stacking your aesthetic on top, layer by layer. ๐ง
Mount options may be defined as a single comma-separated list of options, in which case they will be implicitly applied to the root partition on the image, or a series of colon-separated tuples of partition name and mount options. Valid partition names and mount options are the same as for the RootImageOptions= setting described above.
Each mount definition may be prefixed with "-", in which case it will be ignored when its source path does not exist. The source argument is a path to a block device node or a regular file. If the source path contains a ":", it needs to be escaped as "\:". The device node or file system image file needs to follow the same rules as specified for RootImage=. Any mounts created with this option are specific to the unit, and are not visible in the host's mount table.
These settings may be used more than once, each usage appends to the unit's list of image paths. If the empty string is assigned, the entire list of mount paths defined prior to this is reset.
Every sysext image must carry a /usr/lib/extension-release.d/extension-release.IMAGE file, while every confext image must carry a /etc/extension-release.d/extension-release.IMAGE file, with the appropriate metadata that matches RootImage=/RootDirectory= or the host. See: os-release(5). And if you want to disable the safety check that the extension-release file name matches the image file name, you can append the x-systemd.relax-extension-release-check mount option. โญ
If a service employs this option with systemd.v(7), and has RefreshOnReload=extensions enabled (which is the default, bestie), the confexts will be refreshed to pick up any changes on service reload. This only applies to confext extensions. Note that if a service has this configuration enabled at first, and then it is subsequently removed in an update followed by a daemon-reload operation, reloading the confexts will be a no-op, and a full service restart is required instead. See systemd.service(5) also for details.
When DevicePolicy= is set to "closed" or "strict", or set to "auto" and DeviceAllow= is set, then this setting adds /dev/loop-control with rw mode, and "block-loop" and "block-blkext" with rwm mode to DeviceAllow=. See systemd.resource-control(5) for the details about DevicePolicy= or DeviceAllow=. Also, see PrivateDevices= below, because it may change the setting of DevicePolicy=.
In place of the image path a ".v/" versioned directory may be specified, see systemd.v(7) for details.
When enabled for services running in per-user instances of the service manager, this option implicitly enables PrivateUsers= (which requires unprivileged user namespaces support to be enabled in the kernel via the "kernel.unprivileged_userns_clone=" sysctl) and also relies on systemd-mountfsd.service(8).
When enabled for services running in per-user instances of the service manager, using mount options is disabled by default, due to the security implications. It is possible to use a polkit[2] policy to allow specific mount options, for example:
Example 7. A polkit policy that allows mounting the root partition with nosuid โ /etc/polkit-1/rules.d/mountoptions.rules:
polkit.addRule(function(action, subject) {
if (action.id == "io.systemd.mount-file-system.mount-untrusted-image-privately" &&
action.lookup("mount_options") == "root:nosuid") {
return polkit.Result.YES;
}
});
Added in version 248.
๐ ExtensionDirectories= ๐
This setting is similar to BindReadOnlyPaths= in that it mounts a file system hierarchy from a directory โ but instead of providing a destination path, an overlay will be set up. This option expects a whitespace separated list of source directories.
Okay so picture this: when you use this, a read-only OverlayFS gets set up on top of the /usr/ and /opt/ hierarchies for sysext images, and on top of the /etc/ hierarchy for confext images. ๐
And the order you list those directories in? That's literally the order the overlay gets laid down โ first directory listed = bottom layer, last one = top layer. So the vibes stack, bestie. ๐ฅ
Every directory you list in ExtensionDirectories= can be prefixed with -, and if you do that, it just gets ignored whenever its source path doesn't exist. No drama, no error. ๐ And heads up: any mounts created with this option are specific to the unit โ they will not show up in the host's mount table. It's giving private, exclusive, main character energy. ๐
You can use these settings more than once, fr fr โ each usage just appends to the unit's list of directory paths. And if you assign the empty string, boom, the entire list of mount paths defined before that point gets reset. Wiped. Clean slate. โจ
Now the requirements, and ngl these matter: each sysext directory must contain a
/usr/lib/extension-release.d/extension-release.IMAGE
file, while each confext directory must carry a
/etc/extension-release.d/extension-release.IMAGE
file, with the appropriate metadata that matches RootImage=/RootDirectory= or the host. Peep os-release(5) for the deets. ๐
If a service employs this option with systemd.v(7), and has RefreshOnReload=extensions enabled (which is the default, bestie), then the confexts get refreshed to pick up any changes on service reload. Note that this only applies to confext extensions โ sysexts, sorry babe, not included. ๐
Also lowkey important: if a service has this configuration enabled at first, and then it gets removed in an update followed by adaemon-reloadoperation, reloading the confexts will be a no-op, and a full service restart is required instead. ๐ญ Seesystemd.service(5)too for the details.
One more thing if you're running this from user units: it requires overlayfs support in unprivileged user namespaces, which was first introduced in kernel v5.11. Old kernel? Not the vibe. ๐ธ
And in place of the directory path, a .v/ versioned directory may be specified โ check systemd.v(7) for the full tea. ๐ซ
Also this option is only available for system services, or for services running in per-user instances of the service manager โ and in that case PrivateUsers= is implicitly enabled (which requires unprivileged user namespaces support to be enabled in the kernel via the "kernel.unprivileged_userns_clone=" sysctl). ๐ง
Added in version 251. ๐
๐ USER/GROUP IDENTITY ๐
okay so before we even get into these โ bestie, listen โ these options are only for system services. They are not supported for services running in per-user instances of the service manager. So if you're in your lil systemd --user era, some of this just does not apply to you. noted? good. ๐
User=,Group=โ sets the UNIX user or group that the processes are executed as, respectively. Takes a single user or group name, or a numeric ID, as its argument. ๐
So for system services (services run by the system service manager, i.e. the ones managed by PID 1) and for user services of the root user (services managed by root's instance of systemd --user), the default is "root" โ but you can use User= to specify a different user. For user services of any other user, switching user identity is literally not permitted, so the only valid setting is the same user the user's service manager is running as. If no group is set, the default group of the user is used. Also: this setting does not affect commands whose command line is prefixed with +. ๐ซถ
Now, heads up, because this is the part people get wrong: this only enforces weak restrictions on the user/group name syntax, but it will generate warnings in many cases where user/group names don't adhere to the following rules: the specified name should consist only of the characters a-z, A-Z, 0-9, "_" and "-", except for the first character which must be one of a-z, A-Z and "_" (i.e. digits and "-" are not permitted as the first character). The user/group name must have at least one character, and at most 31. These restrictions exist to avoid ambiguities and to ensure user/group names and unit files remain portable among Linux systems. For further details on the names accepted and the names warned about, see User/Group Name Syntax[4]. ๐
When used in conjunction with DynamicUser=, the user/group name you specify is dynamically allocated at the time the service is started, and released at the time the service is stopped โ unless it is already allocated statically (see below). If DynamicUser= is not used, the specified user and group must have been created statically in the user database no later than the moment the service is started, for example using the sysusers.d(5) facility, which is applied at boot or package install time. If the user does not exist by then, program invocation will fail. ๐ญ
If the User= setting is used, the supplementary group list is initialized from the specified user's default group list, as defined in the system's user and group database. Additional groups may be configured through the SupplementaryGroups= setting (see below).
DynamicUser=โ takes a boolean parameter. If set, a UNIX user and group pair is allocated dynamically when the unit is started, and released as soon as it is stopped. โจ
The user and group will not be added to /etc/passwd or /etc/group, but are managed transiently during runtime. The nss-systemd(8) glibc NSS module provides integration of these dynamic users/groups into the system's user and group databases. The user and group name to use may be configured via User= and Group= (see above). If these options are not used and dynamic user/group allocation is enabled for a unit, the name of the dynamic user/group is implicitly derived from the unit name. If the unit name without the type suffix qualifies as a valid user name, it is used directly; otherwise a name incorporating a hash of it is used.
If a statically allocated user or group of the configured name already exists, it is used and no dynamic user/group is allocated. Note that if User= is specified and the static group with the name exists, then it is required that the static user with the name already exists. Similarly, if Group= is specified and the static user with the name exists, then it is required that the static group with the name already exists. ๐
Dynamic users/groups are allocated from the UID/GID range 61184...65519. It is recommended to avoid this range for regular system or login users. At any point in time each UID/GID from this range is only assigned to zero or one dynamically allocated users/groups in use. However, UID/GIDs are recycled after a unit is terminated. So care should be taken that any processes running as part of a unit for which dynamic users/groups are enabled do not leave files or directories owned by these users/groups around โ because a different unit might get the same UID/GID assigned later on, and thus gain access to these files or directories. ๐ (yes, that's the scary one, bestie.)
If DynamicUser= is enabled, RemoveIPC= is implied (and cannot be turned off). This ensures that the lifetime of IPC objects and temporary files created by the executed processes is bound to the runtime of the service, and hence the lifetime of the dynamic user/group. Since /tmp/ and /var/tmp/ are usually the only world-writable directories on a system, unless PrivateTmp= is manually set to "true", "disconnected" would be implied. This ensures that a unit making use of dynamic user/group allocation cannot leave files around after unit termination. Furthermore NoNewPrivileges= and RestrictSUIDSGID= are implicitly enabled (and cannot be disabled), to ensure that processes invoked cannot take benefit or create SUID/SGID files or directories. Moreover, ProtectSystem=strict and ProtectHome=read-only are implied, thus prohibiting the service to write to arbitrary file system locations. In order to allow the service to write to certain directories, they have to be allow-listed using ReadWritePaths=, but care must be taken so that UID/GID recycling does not create security issues involving files created by the service. Use RuntimeDirectory= (see below) in order to assign a writable runtime directory to a service, owned by the dynamic user/group and removed automatically when the unit is terminated. Use StateDirectory=, CacheDirectory= and LogsDirectory= in order to assign a set of writable directories for specific purposes to the service in a way that they are protected from vulnerabilities due to UID reuse (see below).
If this option is enabled, care should be taken that the unit's processes do not get access to directories outside of these explicitly configured and managed ones. Specifically, do not use BindPaths= and be careful with AF_UNIX file descriptor passing for directory file descriptors, as this would permit processes to create files or directories owned by the dynamic user/group that are not subject to the lifecycle and access guarantees of the service. ๐ซ
Note that this option is currently incompatible with D-Bus policies, thus a service using this option may currently not allocate a D-Bus service name (note that this does not affect calling into other D-Bus services). Defaults to off.
Added in version 232. ๐
SupplementaryGroups=โ sets the supplementary Unix groups the processes are executed as. This takes a space-separated list of group names or IDs. This option may be specified more than once, in which case all listed groups are set as supplementary groups. When the empty string is assigned, the list of supplementary groups is reset, and all assignments prior to this one will have no effect. In any way, this option does not override, but extends the list of supplementary groups configured in the system group database for the user. This does not affect commands prefixed with+. ๐SetLoginEnvironment=โ takes a boolean parameter that controls whether to set the$HOME,$LOGNAME, and$SHELLenvironment variables. If not set, this defaults to true ifUser=,DynamicUser=orPAMName=are set, false otherwise. If set to true, the variables will always be set for system services, i.e. even when the default user"root"is used. If set to false, the mentioned variables are not set by the service manager, no matter whetherUser=,DynamicUser=, orPAMName=are used or not. This option normally has no effect on services of the per-user service manager, since in that case these variables are typically inherited from the user manager's own environment anyway. ๐ธ
Added in version 255.
PAMName=โ sets the PAM service name to set up a session as. If set, the executed process will be registered as a PAM session under the specified service name. This is only useful in conjunction with theUser=setting, and is otherwise ignored. If not set, no PAM session will be opened for the executed processes. Seepam(8)for details. ๐ง
Note that for each unit making use of this option a PAM session handler process will be maintained as part of the unit and stays around as long as the unit is active, to ensure that appropriate actions can be taken when the unit and hence the PAM session terminates. This process is named "(sd-pam)" and is an immediate child process of the unit's main process. ๐
Note that when this option is used for a unit it is very likely (depending on PAM configuration) that the main unit process will be migrated to its own session scope unit when it is activated. This process will hence be associated with two units: the unit it was originally started from (and for which PAMName= was configured), and the session scope unit. Any child processes of that process will however be associated with the session scope unit only. This has implications when used in combination with NotifyAccess=all, as these child processes will not be able to affect changes in the original unit through notification messages. These messages will be considered belonging to the session scope unit and not the original unit. It is hence not recommended to use PAMName= in combination with NotifyAccess=all. ๐ญ
If a PAM module interactively requests input (a password or suchlike) it will be attempted to be read from a service credential (as configured via SetCredential=, ImportCredential= and related calls) under the name pam.authtok.pamservice, where pamservice is replaced by the PAM service name as configured with PAMName=. (Note that the credential remains accessible for the runtime of the service!) If no matching credential is set, the user is prompted for it interactively via the Password Agent[5] logic. ๐
โจ CAPABILITIES โจ
Okay bestie, heads up before we even start: these options are only available for system services, or for services running in per-user instances of the service manager โ and in that second case PrivateUsers= is implicitly enabled (which needs unprivileged user namespaces support turned on in the kernel via the kernel.unprivileged_userns_clone= sysctl). So yeah, iykyk ๐
๐ CapabilityBoundingSet=
This one controls which capabilities go in the capability bounding set for the executed process. Details live in capabilities(7), go look if you want the lore.
It takes a whitespace-separated list of capability names, like CAP_SYS_ADMIN, CAP_DAC_OVERRIDE, CAP_SYS_PTRACE. Whatever you list gets included in the bounding set, and everything else gets removed. If you prefix the whole list with "~", it's inverted โ so all but the listed capabilities are included instead. The way that works is genuinely iconic.
Heads up: this option also affects the respective capabilities in the effective, permitted and inheritable capability sets.
If you never use this option, the capability bounding set just... isn't modified on process execution, so no limits on the process's capabilities are enforced. Feral behavior, honestly.
This option may appear more than once, and then the bounding sets get merged by OR, or by AND if the lines are prefixed with "~" (peep the example below). If the empty string is assigned to this option, the bounding set is reset to the empty capability set and all prior settings have no effect. If set to "~" (no further argument), the bounding set is reset to the full set of available capabilities, also undoing any previous settings. This does not affect commands prefixed with "+".
Use systemd-analyze(1)'s capability command to retrieve a list of capabilities defined on the local system. So helpful of it ๐ซถ
Example time โ if a unit has this:
CapabilityBoundingSet=CAP_A CAP_B
CapabilityBoundingSet=CAP_B CAP_C
then CAP_A, CAP_B, and CAP_C are set. Slay.
But if the second line is prefixed with "~", like:
CapabilityBoundingSet=CAP_A CAP_B
CapabilityBoundingSet=~CAP_B CAP_C
then, only CAP_A is set. That's the whole vibe โจ
๐ธ AmbientCapabilities=
Controls which capabilities to include in the ambient capability set for the executed process. Takes a whitespace-separated list of capability names, e.g. CAP_SYS_ADMIN, CAP_DAC_OVERRIDE, CAP_SYS_PTRACE.
This option may appear more than once, and then the ambient capability sets are merged (see the examples above in CapabilityBoundingSet=). If the list of capabilities is prefixed with "~", all but the listed capabilities will be included โ effect inverted, as always. If the empty string is assigned to this option, the ambient capability set is reset to the empty capability set and all prior settings have no effect. If set to "~" (without any further argument), the ambient capability set is reset to the full set of available capabilities, also undoing any previous settings.
Note that adding capabilities to the ambient capability set adds them to the process's inherited capability set.
Ambient capability sets are useful if you want to execute a process as a non-privileged user but still want to give it some capabilities. Lowkey a game-changer. Note that, in this case, the option keep-caps is automatically added to SecureBits= to retain the capabilities over the user change. AmbientCapabilities= does not affect commands prefixed with "+".
Added in version 229.
๐ SECURITY ๐
๐ NoNewPrivileges=
Takes a boolean argument. If true, it ensures that the service process and all its children can never gain new privileges through execve() (e.g. via setuid or setgid bits, or filesystem capabilities). This is the simplest and most effective way to make sure a process and its children can never elevate privileges again. Defaults to false.
In case the service will be run in a new mount namespace anyway and SELinux is disabled, all file systems are mounted with the MS_NOSUID flag. Also see No New Privileges Flag[6].
Note that this setting only has an effect on the unit's processes themselves (or any processes directly or indirectly forked off them). It has no effect on processes potentially invoked on request of them through tools such asat(1),crontab(1),systemd-run(1), or arbitrary IPC services. Not me warning you twice ๐
Added in version 187.
๐ SecureBits=
Controls the secure bits set for the executed process. Takes a space-separated combination of options from this list, and it's a long one so buckle up:
keep-capskeep-caps-lockedno-setuid-fixupno-setuid-fixup-lockednorootnoroot-lockedno-cap-ambient-raiseno-cap-ambient-raise-lockedexec-restrict-fileexec-restrict-file-lockedexec-deny-interactiveexec-deny-interactive-locked
This option may appear more than once, in which case the secure bits are ORed. If the empty string is assigned to this option, the bits are reset to 0. This does not affect commands prefixed with "+". See capabilities(7) and Executability check[7] for details.
๐ก๏ธ MANDATORY ACCESS CONTROL ๐ก๏ธ
๐ SELinuxContext=
Set the SELinux security context of the executed process. If set, this will override the automated domain transition โ but the policy still needs to authorize the transition, so don't get cocky. This directive is ignored if SELinux is disabled.
If prefixed by "-", failing to set the SELinux security context will be ignored, but it is still possible that the subsequent execve() may fail if the policy does not allow the transition for the non-overridden context. This does not affect commands prefixed with "+". See setexeccon(3) for details.
Added in version 209.
๐ AppArmorProfile=
Takes a profile name as argument. The process executed by the unit will switch to this profile when started. Profiles must already be loaded in the kernel, or the unit will fail โ no improvising โ
If prefixed by "-", all errors will be ignored. This setting has no effect if AppArmor is not enabled. This setting does not affect commands prefixed with "+".
This option is only available for system services and is not supported for services running in per-user instances of the service manager.
Added in version 210.
๐ฆ SmackProcessLabel=
Takes a SMACK64 security label as argument. The process executed by the unit will be started under this label, and SMACK will decide whether the process is allowed to run or not, based on it. The process will continue to run under the label specified here unless the executable has its own SMACK64EXEC label โ in which case the process will transition to run under that label. When not specified, the label that systemd is running under is used. This directive is ignored if SMACK is disabled.
The value may be prefixed by "-", in which case all errors will be ignored. An empty value may be specified to unset previous assignments. This does not affect commands prefixed with "+".
This option is only available for system services and is not supported for services running in per-user instances of the service manager.
Added in version 218.
โ๏ธ PROCESS PROPERTIES โ๏ธ
๐ LimitCPU=, LimitFSIZE=, LimitDATA=, LimitSTACK=, LimitCORE=, LimitRSS=, LimitNOFILE=, LimitAS=, LimitNPROC=, LimitMEMLOCK=, LimitLOCKS=, LimitSIGPENDING=, LimitMSGQUEUE=, LimitNICE=, LimitRTPRIO=, LimitRTTIME=
Set soft and hard limits on various resources for executed processes. See setrlimit(2) for details on the process resource limit concept โ the whole family, one big group chat ๐ซถ
Process resource limits may be specified in two formats: either as a single value to set a specific soft and hard limit to the same value, or as a colon-separated pair soft:hard to set both limits individually (e.g. "LimitAS=4G:16G").
Use the string infinity to configure no limit on a specific resource. The multiplicative suffixes K, M, G, T, P and E (to the base 1024) may be used for resource limits measured in bytes (e.g. "LimitAS=16G"). For the limits referring to time values, the usual time units ms, s, min, h and so on may be used (see systemd.time(7) for details).
Note that if no time unit is specified forLimitCPU=the default unit of seconds is implied, while forLimitRTTIME=the default unit of microseconds is implied. Sneaky, right?
Also, note that the effective granularity of the limits might influence their enforcement. For example, time limits specified for LimitCPU= will be rounded up implicitly to multiples of 1s.
For LimitNICE= the value may be specified in two syntaxes: if prefixed with "+" or "-", the value is understood as a regular Linux nice value in the range -20...19. If not prefixed like this, the value is understood as a raw resource limit parameter in the range 0...40 (with 0 being equivalent to 1).
Note that most process resource limits configured with these options are per-process, and processes may fork in order to acquire a new set of resources that are accounted independently of the original process, and may thus escape limits set. Also note that LimitRSS= is not implemented on Linux, and setting it has no effect. Not me crying over an option that literally does nothing ๐ญ
Often it is advisable to prefer the resource controls listed in systemd.resource-control(5) over these per-process limits, as they apply to services as a whole, may be altered dynamically at runtime, and are generally more expressive. For example, MemoryMax= is a more powerful (and working) replacement for LimitRSS=.
Note that LimitNPROC= will limit the number of processes from one (real) UID and not the number of processes started (forked) by the service. Therefore the limit is cumulative for all processes running under the same UID. Please also note that the LimitNPROC= will not be enforced if the service is running as root (and not dropping privileges). Due to these limitations, TasksMax= (see systemd.resource-control(5)) is typically a better choice than LimitNPROC=.
These settings, including the defaults described below, do not affect commands prefixed with "+". Such commands inherit the resource limits of the service manager itself, which may be different. Main character behavior fr ๐
Resource limits not configured explicitly for a unit default to the value configured in the various DefaultLimitCPU=, DefaultLimitFSIZE=, ... options available in systemd-system.conf(5), and โ if not configured there โ the kernel or per-user defaults, as defined by the OS (the latter only for user services, see below).
So here's the tea on resource limits, bestie ๐ โ for system units you can set these however you want, literally go wild, no cap. Pick any number, the manager trusts you. โจ
But user services (that's a service run by the per-user instance of the service manager, iykyk) are a whole different vibe. When you configure resource limits there, you cannot use them to push the limits above whatever was set for the user manager itself when it was first invoked. Why? Because the user's service manager generally does not have the privileges to do that. It's giving "you can't sit with us" energy ๐ .
So in user context, these config options are lowkey only useful for two things:
- lowering the limits that got passed in, or
- raising the soft limit up to the max of the hard limit as configured for the user. ๐ซถ
Want to go higher than that? ๐ญ Then the configuration mechanisms available differ between operating systems, but they typically require privileges. It's not a vibe you can just manifest, sorry babe.
In most cases though, it is possible to configure higher per-user resource limits. Two iconic routes:
- via PAM ๐
- by setting limits on the system service encapsulating the user's service manager โ that is, the user's instance of
user@.service๐
And after you make those changes? Make sure to restart the user's service manager. Like, actually do it. Don't skip this step, it's not optional. ๐ฅ
okay so before we get into the actual limits, here's the vibe: Table 1 is basically a cheat sheet mapping every Limit*= directive to its ulimit shell equivalent and the unit it's measured in. Bestie, screenshot this, you'll want it. ๐
โจ Table 1. Resource limit directives, their equivalent ulimit shell commands and the unit used โจ
LimitCPU=โ the shell twin isulimit -t, and it's measured in Seconds. No notes, it just vibes. โจLimitFSIZE=โ matchesulimit -f, measured in Bytes. Nothing extra to say. ๐LimitDATA=โ matchesulimit -d, measured in Bytes. Do not use this one, seriously. ๐ญ It limits the allowed address range, not memory use! It defaults to unlimited and you should not lower it. If you actually wanna limit memory use, go seeMemoryMax=insystemd.resource-control(5). ๐ซถLimitSTACK=โ matchesulimit -s, measured in Bytes. No notes. ๐ธLimitCORE=โ matchesulimit -c, measured in Bytes. No notes. ๐ธLimitRSS=โ matchesulimit -m, measured in Bytes. Do not use. It has no effect on Linux. ๐LimitNOFILE=โ matchesulimit -n, measured in Number of File Descriptors. Do not use. Be careful when raising the soft limit above1024, becauseselect(2)cannot function with file descriptors above1023on Linux. ๐ Nowadays the hard limit defaults to524288, which is a very high value compared to historical defaults. Typically applications should increase their soft limit to the hard limit on their own if they're okay working with file descriptors above1023โ i.e. if they do not useselect(2). Note that file descriptors are nowadays accounted like any other form of memory, so there should not be any need to lower the hard limit. UseMemoryMax=to control overall service memory use, including file descriptor memory. ๐งLimitAS=โ matchesulimit -v, measured in Bytes. Do not use. This limits the allowed address range, not memory use! It defaults to unlimited and should not be lowered. To actually limit memory use, seeMemoryMax=insystemd.resource-control(5). ๐ญLimitNPROC=โ matchesulimit -u, measured in Number of Processes. Heads up: this limit is enforced based on the number of processes belonging to the user. Typically it's better to track processes per service instead, i.e. useTasksMax=, seesystemd.resource-control(5). ๐ฆLimitMEMLOCK=โ matchesulimit -l, measured in Bytes. No notes. ๐LimitLOCKS=โ matchesulimit -x, measured in Number of Locks. No notes. โญLimitSIGPENDING=โ matchesulimit -i, measured in Number of Queued โฆ (and the table carries on into the next bit, don't worry bestie, we're not done yet ๐)LimitMSGQUEUE=โ same energy asulimit -q, measured in Bytes, and the Signals column is literally just-๐LimitNICE=โ matchesulimit -e, unit is Nice Level, Signals column:-LimitRTPRIO=โ matchesulimit -r, unit is Realtime Priority, Signals column:-LimitRTTIME=โ matchesulimit -R, unit is Microseconds, Signals column:-
๐ UMask=
okay so this one controls the file mode creation mask, bestie. It takes an access mode in octal notation. Go peep umask(2) for the deets. ๐
Defaults to 0022 for system units. For user units the default value is inherited from the per-user service manager (and its default is in turn inherited from the system service manager, so it's typically also 0022 โ unless some PAM module overrode it, rude).
If you wanna change the per-user mask for all user services, lowkey consider setting the UMask= setting of the user's user@.service system service instance. The per-user umask may also be set via the umask field of a user's JSON User Record[8] (for users managed by systemd-homed.service(8) this field may be controlled via homectl --umask=). It may also be set via a PAM module, such as pam_umask(8). so many doors, pick one ๐
๐ฆ CoredumpFilter=
Controls which types of memory mappings will be saved if the process dumps core โ and it does this via the /proc/pid/coredump_filter file, no cap.
It takes a whitespace-separated combination of mapping type names or numbers (with the default base 16, fyi). The mapping type names are:
private-anonymousshared-anonymousprivate-file-backedshared-file-backedelf-headersprivate-hugeshared-hugeprivate-daxshared-dax
...plus the special values all (all types) and default (which is the kernel default of "private-anonymous shared-anonymous elf-headers private-huge"). See core(5) for what each mapping type actually means. ๐
When specified multiple times, all specified masks are ORed. When not set, or if the empty value is assigned, the inherited value is not changed.
๐พ Example 8. Add DAX pages to the dump filter
CoredumpFilter=default private-dax shared-dax
Added in version 246.
๐ KeyringMode=
Controls how the kernel session keyring is set up for the service (see session-keyring(7) for all the details on the session keyring). It takes one of inherit, private, shared. Let's break it down:
inheritโ no special keyring setup is done at all, the kernel's default behaviour just applies. simple, unbothered.privateโ a new session keyring is allocated when a service process is invoked, and it is not linked up with any user keyring. This is the recommended setting for system services, because it ensures that multiple services running under the same system user ID (in particular therootuser) do not share their key material among each other. protective queen behavior ๐ซถsharedโ a new session keyring is allocated just like forprivate, but the user keyring of the user configured withUser=is linked into it, so that keys assigned to the user may be requested by the unit's processes. In this mode multiple units running processes under the same user ID may share key material. sharing is caring ig ๐ญ
Unless inherit is selected, the unique invocation ID for the unit (see below) is added as a protected key by the name "invocation_id" to the newly created session keyring. โจ
Defaults to private for services of the system service manager, and to inherit for non-service units and for services of the user service manager.
Added in version 235.
๐ OOMScoreAdjust=
Sets the adjustment value for the Linux kernel's Out-Of-Memory (OOM) killer score for executed processes. It takes an integer between -1000 (to disable OOM killing of processes of this unit โ the "leave me alone" setting) and 1000 (to make killing of processes of this unit under memory pressure very likely โ the "choose me first" setting). See The /proc Filesystem[9] for details.
If not specified, it defaults to the OOM score adjustment level of the service manager itself, which is normally at 0.
Use theOOMPolicy=setting of service units to configure how the service manager shall react to the kernel OOM killer orsystemd-oomdterminating a process of the service. Seesystemd.service(5)for details. ๐
โฑ๏ธ TimerSlackNSec=
Sets the timer slack in nanoseconds for the executed processes. Timer slack controls the accuracy of wake-ups triggered by timers. See prctl(2) for more info. ๐ง
Heads up bestie: in contrast to most other time span definitions, this parameter takes an integer value in nano-seconds if no unit is specified. The usual time units are understood too, don't worry.
๐ Personality=
Controls which kernel architecture uname(2) shall report when it's invoked by unit processes. It takes one of these architecture identifiers:
arm64, arm64-be, arm, arm-be, x86, x86-64, ppc, ppc-le, ppc64, ppc64-le, s390 or s390x.
Which personality architectures are supported depends on the kernel's native architecture. Usually the 64-bit versions of the various system architectures support their immediate 32-bit personality architecture counterpart, but no others. For example, x86-64 systems support the x86-64 and x86 personalities but no others. it's giving exclusive ๐
The personality feature is useful when running 32-bit services on a 64-bit host system. If not specified, the personality is left unmodified and thus reflects the personality of the host system's kernel.
This option is not useful on architectures for which only one native word width was ever available, such asm68k(32-bit only) oralpha(64-bit only). nothing to switch to, babes ๐ญ
Added in version 209.
๐ IgnoreSIGPIPE=
Takes a boolean argument. If true, SIGPIPE is ignored in the executed process. Defaults to true, since SIGPIPE is generally only useful in shell pipelines. periodt. โจ
๐ SCHEDULING ๐
okay bestie, this is where we decide how much the CPU and the disk actually like your processes. priority politics, basically. ๐
โจ Nice=
sets the default nice level (aka scheduling priority) for the processes you execute. takes an integer between -20 (highest priority) and 19 (lowest priority).
the vibe check: when there's resource contention, smaller values mean more resources get handed to the unit's processes, and larger values mean less. so like, small number = main character, big number = background extra. ๐ซถ see setpriority(2) for the deets.
โจ CPUSchedulingPolicy=
sets the CPU scheduling policy for executed processes. takes one of other, batch, idle, fifo, rr or ext. see sched_setscheduler(2) for details. ๐ฆ
โจ CPUSchedulingPriority=
sets the CPU scheduling priority for executed processes. the range you can use depends on which CPU scheduling policy you picked above (yeah, they're a package deal ๐). for real-time scheduling policies you can use an integer between 1 (lowest priority) and 99 (highest priority).
in case of CPU resource contention: smaller values mean less CPU time for the service, larger values mean more. see sched_setscheduler(2) for details.
โจ CPUSchedulingResetOnFork=
takes a boolean argument. if true, elevated CPU scheduling priorities and policies get reset the moment the executed processes call fork(2), so they literally cannot leak into child processes. no generational trauma for your scheduler settings, we love that for them. ๐ see sched_setscheduler(2) for details.
defaults to false.
โจ CPUAffinity=
controls the CPU affinity of the executed processes. takes a list of CPU indices or ranges, separated by either whitespace or commas.
alternatively, it takes the special value "numa" โ and in that case systemd automatically derives the allowed CPU range based on whatever you set in NUMAMask=.
CPU ranges are specified by the lower and upper CPU indices separated by a dash.
this option may be specified more than once, and if you do that, the specified CPU affinity masks get merged. if the empty string is assigned, the mask is reset โ and all assignments prior to this will have no effect, so like, they're cancelled, bye. ๐
see sched_setaffinity(2) for details.
โจ NUMAPolicy=
controls the NUMA memory policy of the executed processes. takes a policy type, one of:
defaultpreferredbindinterleavelocalpreferred-many(requires Linux 5.15 or newer)weighted-interleave(requires Linux 6.9 or newer, weights are configured via/sys/kernel/mm/mempolicy/weighted_interleave/)
a list of NUMA nodes that should be associated with the policy must be specified in NUMAMask=. for more details on each policy please see set_mempolicy(2). for an overall overview of NUMA support in Linux see numa(7).
if the kernel does not support the requested policy, a warning is logged and the setting is ignored. (not me getting ghosted by the kernel ๐ญ)
Added in version 243.
โจ NUMAMask=
controls the NUMA node list which will be applied alongside the selected NUMA policy. takes a list of NUMA nodes and has the same syntax as a list of CPUs for the CPUAffinity= option โ or the special value "all", which will include all available NUMA nodes in the mask. ๐
note that the list of NUMA nodes is not required for default and local policies, and for the preferred policy we expect a single NUMA node.
Added in version 243.
โจ IOSchedulingClass=
sets the I/O scheduling class for executed processes. takes one of the strings realtime, best-effort or idle.
heads up: the kernel's default scheduling class is best-effort at a priority of 4.
if the empty string is assigned to this option, all prior assignments to both IOSchedulingClass= and IOSchedulingPriority= have no effect. poof. gone. โจ
see ioprio_set(2) for details.
โจ IOSchedulingPriority=
sets the I/O scheduling priority for executed processes. takes an integer between 0 (highest priority) and 7 (lowest priority).
in case of I/O contention, smaller values mean more I/O bandwidth is made available to the unit's processes, larger values mean less bandwidth. the available priorities depend on the selected I/O scheduling class (see above, bestie, they're linked ๐).
if the empty string is assigned to this option, all prior assignments to both IOSchedulingClass= and IOSchedulingPriority= have no effect. (yes it's the same dramatic clause as above, they're a matching set, periodt ๐
)
for the kernel's default scheduling class (best-effort) this defaults to 4. see ioprio_set(2) for details.
๐งธ SANDBOXING ๐งธ
okay this is the part where we put your unit's processes in a lil playpen so they can't go feral on the rest of the system. ๐ผ
the following sandboxing options are a genuinely effective way to limit the exposure of the system towards the unit's processes. the recommendation is: turn on as many of these options for each unit as is possible without negatively affecting the process' ability to operate. (balance, babe. it's giving healthy boundaries. ๐ซถ)
note that many of these sandboxing features are gracefully turned off on systems where the underlying security mechanism isn't available. for example, ProtectSystem= has no effect if the kernel is built without file system namespacing, or if the service manager runs in a container manager that makes file system namespacing unavailable to its payload. similarly, RestrictRealtime= has no effect on systems that lack support for SECCOMP system call filtering, or in containers where support for this is turned off.
also note that some sandboxing functionality is generally not available in user services (i.e. services run by the per-user service manager). specifically, the various settings requiring file system namespacing support (such as ProtectSystem=) are not available, because the underlying kernel functionality is only accessible to privileged processes. however โ and this is the fun part โ most namespacing settings that won't work on their own in user services will work when used in conjunction with PrivateUsers=true. team effort, slay. ๐
โ ๏ธ note that the various options that turn directories read-only (such asProtectSystem=,ReadOnlyPaths=, ...) do not affect the ability for programs to connect to and communicate withAF_UNIXsockets in these directories. so these options cannot be used to lock down access to IPC services. not me finding out the loophole the hard way ๐ญ
๐ฆ ProtectSystem=
takes a boolean argument or the special values "full" or "strict".
- if
true, mounts the/usr/and the boot loader directories (/bootand/efi) read-only for processes invoked by this unit. - if set to
"full", the/etc/directory is mounted read-only too. - if set to
"strict"the entire file system hierarchy is mounted read-only, except for the API file system subtrees/dev/,/proc/and/sys/(protect these directories usingPrivateDevices=,ProtectKernelTunables=,ProtectControlGroups=).
this setting ensures that any modification of the vendor-supplied operating system (and optionally its configuration, and local mounts) is prohibited for the service. it is recommended to enable this setting for all long-running services, unless they are involved with system updates or need to modify the operating system in other ways.
if this option is used, ReadWritePaths= may be used to exclude specific directories from being made read-only. similar, StateDirectory=, LogsDirectory=, ... and related directory settings (see below) also exclude the specific directories from the effect of ProtectSystem=.
this setting is implied if DynamicUser= is set.
this setting cannot ensure protection in all cases. in general it has the same limitations as ReadOnlyPaths=, see below. defaults to off.
note that if ProtectSystem= is set to "strict" and PrivateTmp= is enabled, then /tmp/ and /var/tmp/ will be writable. ๐ซ
Added in version 214.
๐ฆ ProtectHome=
takes a boolean argument or the special values "read-only" or "tmpfs".
- if
true, the directories/home/,/root, and/run/userare made inaccessible and empty for processes invoked by this unit. - if set to
"read-only", the three directories are made read-only instead. - if set to
"tmpfs", temporary file systems are mounted on the three directories in read-only mode.
the value "tmpfs" is useful to hide home directories not relevant to the processes invoked by the unit, while still allowing necessary directories to be made visible when listed in BindPaths= or BindReadOnlyPaths=.
setting this to "yes" is mostly equivalent to setting the three directories in InaccessiblePaths=. similarly, "read-only" is mostly equivalent to ReadOnlyPaths=, and "tmpfs" is mostly equivalent to TemporaryFileSystem= with ":ro".
it is recommended to enable this setting for all long-running services (in particular network-facing ones), to ensure they cannot get access to private user data โ unless the services actually require access to the user's private data. this setting is implied if DynamicUser= is set.
this setting cannot ensure protection in all cases. in general it has the same limitations as ReadOnlyPaths=, see below.
๐ note that this setting provides no protection if home directories are placed at a non-standard location, i.e. outside of the hierarchies listed above. so if you moved your home dir somewhere quirky, this whole thing is just vibes and no substance.
this option is only available for system services, or for services running in per-user instances of the service manager โ in which case PrivateUsers= is implicitly enabled (requires unprivileged user namespaces support to be enabled in the kernel via the "kernel.unprivileged_userns_clone=" sysctl).
Added in version 214.
๐ธ RuntimeDirectory=, StateDirectory=, CacheDirectory=, LogsDirectory=, ConfigurationDirectory=
these options take a whitespace-separated list of directory names. the specified directory names must be relative, and may not include "..". (no sneaking out the back door, bestie ๐ช)
if set, when the unit is started, one or more directories by the specified names will be created (including their parents) below the locations defined in the following table. also, the corresponding environment variable will be defined with the full paths of the directories. if multiple directories are set, then in the environment variable the paths are concatenated with colon (":").
if DynamicUser= is used, and if the kernel version supports id-mapped mounts[10], the specified directories will be owned by "nobody" in the host namespace and will be mapped to (and will be owned by) the service's UID/GID in its own namespace. for backward compatibility, existing directories created without id-mapped mounts will be kept untouched. ๐
๐ Table 2. Automatic directory creation and environment variables
okay so here's the โจreceiptsโจ for which directory option lives where and what env var it hands you:
RuntimeDirectory=โ below path for system units:/run/ยท below path for user units:$XDG_RUNTIME_DIRยท environment variable set:$RUNTIME_DIRECTORYStateDirectory=โ below path for system units:/var/lib/ยท below path for user units:$XDG_STATE_HOMEยท environment variable set:$STATE_DIRECTORYCacheDirectory=โ below path for system units:/var/cache/ยท below path for user units:$XDG_CACHE_HOMEยท environment variable set:$CACHE_DIRECTORYLogsDirectory=โ below path for system units:/var/log/ยท below path for user units:$XDG_STATE_HOME/log/ยท environment variable set:$LOGS_DIRECTORYConfigurationDirectory=โ below path for system units:/etc/ยท below path for user units:$XDG_CONFIG_HOMEยท environment variable set:$CONFIGURATION_DIRECTORY
In case of RuntimeDirectory= the innermost subdirectories are removed when the unit is stopped. ๐ Bestie, they're GONE. But you can preserve the specified directories in this case if RuntimeDirectoryPreserve= is configured to restart or yes (see below). The directories specified with StateDirectory=, CacheDirectory=, LogsDirectory=, ConfigurationDirectory= are not removed when the unit is stopped โ those ones are permanent residents, they live there now. ๐ก
Except in case of ConfigurationDirectory=, the innermost specified directories will be owned by the user and group specified in User= and Group=. And here's the tea: if the specified directories already exist and their owning user or group do not match the configured ones, all files and directories below the specified directories as well as the directories themselves will have their file ownership recursively changed to match what is configured. ๐ณ Recursive. As an optimization, if the specified directories are already owned by the right user and group, files and directories below of them are left as-is, even if they do not match what is requested. The innermost specified directories will have their access mode adjusted to the what is specified in RuntimeDirectoryMode=, StateDirectoryMode=, CacheDirectoryMode=, LogsDirectoryMode= and ConfigurationDirectoryMode=.
These options imply BindPaths= for the specified paths. And when you combine them with RootDirectory= or RootImage=, these paths always reside on the host and are mounted from there into the unit's file system namespace. ๐
If DynamicUser= is used, the logic for CacheDirectory=, LogsDirectory= and StateDirectory= is slightly altered: the directories are created below /var/cache/private, /var/log/private and /var/lib/private, respectively, which are host directories made inaccessible to unprivileged users, which ensures that access to these directories cannot be gained through dynamic user ID recycling. ๐ Symbolic links are created to hide this difference in behaviour. Both from perspective of the host and from inside the unit, the relevant directories hence always appear directly below /var/cache, /var/log and /var/lib. Sneaky? No. Iconic? Yes. ๐
Use RuntimeDirectory= to manage one or more runtime directories for the unit and bind their lifetime to the daemon runtime. This is particularly useful for unprivileged daemons that cannot create runtime directories in /run/ due to lack of privileges, and to make sure the runtime directory is cleaned up automatically after use. ๐งน For runtime directories that require more complex or different configuration or lifetime guarantees, please consider using tmpfiles.d(5).
RuntimeDirectory=, StateDirectory=, CacheDirectory= and LogsDirectory= optionally support two more parameters, separated by ":". The second parameter will be interpreted as a destination path that will be created as a symlink to the directory. The symlinks will be created after any BindPaths= or TemporaryFileSystem= options have been set up, to make ephemeral symlinking possible. The same source can have multiple symlinks, by using the same first parameter, but a different second parameter. ๐ The third parameter is a flags field, and since v257 can take a value of ro to make the directory read only for the service. This is also supported for ConfigurationDirectory=. If multiple symlinks are set up, the directory will be read only if at least one is configured to be read only. To pass a flag without a destination symlink, the second parameter can be empty, for example:
ConfigurationDirectory=foo::ro
The directories defined by these options are always created under the standard paths used by systemd (/var/, /run/, /etc/, ...). If the service needs directories in a different location, a different mechanism has to be used to create them. No shortcuts, sorry bestie. ๐
tmpfiles.d(5) provides functionality that overlaps with these options. Using these options is recommended, because the lifetime of the directories is tied directly to the lifetime of the unit, and it is not necessary to ensure that the tmpfiles.d configuration is executed before the unit is started. โจ
To remove any of the directories created by these settings, use the systemctl clean ... command on the relevant units, see systemctl(1) for details.
Example: if a system service unit has the following,
RuntimeDirectory=foo/bar baz
the service manager creates /run/foo (if it does not exist), /run/foo/bar, and /run/baz. The directories /run/foo/bar and /run/baz except /run/foo are owned by the user and group specified in User= and Group=, and removed when the service is stopped.
Example: if a system service unit has the following,
RuntimeDirectory=foo/bar
StateDirectory=aaa/bbb ccc
then the environment variable "RUNTIME_DIRECTORY" is set with "/run/foo/bar", and "STATE_DIRECTORY" is set with "/var/lib/aaa/bbb:/var/lib/ccc".
Example: if a system service unit has the following,
RuntimeDirectory=foo:bar foo:baz
the service manager creates /run/foo (if it does not exist), and /run/bar plus /run/baz as symlinks to /run/foo. ๐
Added in version 211.
โจ OPTIONS โจ (continued)
๐ฏ RuntimeDirectoryMode=, StateDirectoryMode=, CacheDirectoryMode=, LogsDirectoryMode=, ConfigurationDirectoryMode=
Specifies the access mode of the directories specified in RuntimeDirectory=, StateDirectory=, CacheDirectory=, LogsDirectory=, or ConfigurationDirectory=, respectively, as an octal number. Defaults to 0755. See "Permissions" in path_resolution(7) for a discussion of the meaning of permission bits. ๐
Added in version 234.
๐พ StateDirectoryQuota=, CacheDirectoryQuota=, LogsDirectoryQuota=
Specifies the storage limits for the directories specified in StateDirectory=, CacheDirectory=, or LogsDirectory= respectively.
The storage quota is defined in terms of disk blocks and inodes, as per quotactl[11]. Takes an absolute size limit in bytes. If the value is suffixed with K, M, G or T, the specified size is parsed as Kilobytes, Megabytes, Gigabytes, or Terabytes (with the base 1024), respectively. If an absolute size limit is specified, only the block quota is set (rounded up to the nearest block). Alternatively, a percentage value may be specified, which applies the same percent quota to both blocks and inodes. Defaults to off, in which case no storage limits will be set.
Only hard limits are set, not soft limits. ๐ And if the underlying filesystem for the specified directories does not support project quotas, the specified storage limits will not be set. In addition to enabling per-unit quotas with these settings, it is necessary to enableprjquotaon the file system level as well (i.e.tune2fs -Q prjquota). Quotas must also be turned on withquotaon.[12]
Added in version 258.
๐ StateDirectoryAccounting=, CacheDirectoryAccounting=, LogsDirectoryAccounting=
Takes a boolean argument. If true, a project ID is assigned to the directories specified in StateDirectory=, CacheDirectory=, or LogsDirectory= respectively, which is used for tracking disk usage when disk quotas are turned on (see repquota[13]). Defaults to false.
To set and enforce disk quotas, StateDirectoryQuota=, CacheDirectoryQuota=, or LogsDirectoryQuota= must be specified. ๐
Added in version 258.
RuntimeDirectoryPreserve=โ takes a boolean argument, or the special valuerestart. Set it tono(which is the default, bestie) and the directories you named inRuntimeDirectory=get yeeted every single time the service stops. Set it torestartand those dirs are preserved whenever the service is restarted both automatically and manually โ and just so we're clear on the vocab: automatic restart means the operation specified inRestart=, while manual restart means the one you triggered yourself withsystemctl restart foo.service. Set it toyesand the directories are simply not removed when the service is stopped. โ ๏ธ One thing though: since the runtime directory/run/is a mount point of"tmpfs", for system services the directories specified inRuntimeDirectory=are removed when the system is rebooted. Them's the rules. ๐
If DynamicUser= is used together with RuntimeDirectoryPreserve= set to values other than no, the logic gets slightly altered: the RuntimeDirectory= directories are created below /run/private/, which is a host directory made inaccessible to unprivileged users, which ensures that access to these directories cannot be gained through dynamic user ID recycling. Symbolic links are created to hide this difference in behaviour. Both from the perspective of the host and from inside the unit, the relevant directories hence always appear directly below /run/. โจ
Added in version 235.
TimeoutCleanSec=โ configures a timeout on the clean-up operation you request throughsystemctl clean ..., seesystemctl(1)for the deets. It takes the usual time values and defaults toinfinity, i.e. by default no timeout is applied. If you do configure a timeout, the clean operation will be aborted forcibly when the timeout is reached โ potentially leaving resources on disk ๐. So like, choose wisely.
Added in version 244.
ReadWritePaths=,ReadOnlyPaths=,InaccessiblePaths=,ExecPaths=,NoExecPaths=โ these five set up a whole new file system namespace for the executed processes, and you use them to limit how much of the file system a process can touch. Each setting takes a space-separated list of paths relative to the host's root directory (i.e. the system running the service manager). Note that if paths contain symlinks, they are resolved relative to the root directory set withRootDirectory=/RootImage=.
Paths listed in ReadWritePaths= are accessible from within the namespace with the same access modes as from outside of it. Paths listed in ReadOnlyPaths= are accessible for reading only โ writing will be refused even if the usual file access controls would permit this. Nest ReadWritePaths= inside of ReadOnlyPaths= in order to provide writable subdirectories within read-only directories. Use ReadWritePaths= in order to allow-list specific paths for write access if ProtectSystem=strict is used. โ ๏ธ Note that ReadWritePaths= cannot be used to gain write access to a file system whose superblock is mounted read-only. On Linux, for each mount point write access is granted only if the mount point itself and the file system superblock backing it are not marked read-only. ReadWritePaths= only controls the former, not the latter, hence a read-only file system superblock remains protected. Periodt.
Paths listed in InaccessiblePaths= will be made inaccessible for processes inside the namespace along with everything below them in the file system hierarchy. This may be more restrictive than desired, because it is not possible to nest ReadWritePaths=, ReadOnlyPaths=, BindPaths=, or BindReadOnlyPaths= inside it. For a more flexible option, see TemporaryFileSystem=. ๐
Content in paths listed in NoExecPaths= are not executable even if the usual file access controls would permit this. Nest ExecPaths= inside of NoExecPaths= in order to provide executable content within non-executable directories.
Non-directory paths may be specified as well. These options may be specified more than once, in which case all paths listed will have limited access from within the namespace. If the empty string is assigned to this option, the specific list is reset, and all prior assignments have no effect.
Paths in ReadWritePaths=, ReadOnlyPaths=, InaccessiblePaths=, ExecPaths= and NoExecPaths= may be prefixed with "-", in which case they will be ignored when they do not exist. If prefixed with "+" the paths are taken relative to the root directory of the unit, as configured with RootDirectory=/RootImage=, instead of relative to the root directory of the host (see above). When combining "-" and "+" on the same path make sure to specify "-" first, and "+" second. Order matters, girlies. โจ
Note that these settings will disconnect propagation of mounts from the unit's processes to the host. This means that this setting may not be used for services which shall be able to install mount points in the main mount namespace.
For ReadWritePaths= and ReadOnlyPaths=, propagation in the other direction is not affected, i.e. mounts created on the host generally appear in the unit processes' namespace, and mounts removed on the host also disappear there too. In particular, note that mount propagation from host to unit will result in unmodified mounts to be created in the unit's namespace, i.e. writable mounts appearing on the host will be writable in the unit's namespace too, even when propagated below a path marked with ReadOnlyPaths=! ๐ญ Restricting access with these options hence does not extend to submounts of a directory that are created later on. This means the lock-down offered by that setting is not complete, and does not offer full protection. Like, it's giving security theatre a lil bit. ๐
Note that the effect of these settings may be undone by privileged processes. In order to set up an effective sandboxed environment for a unit it is thus recommended to combine these settings with either CapabilityBoundingSet=~CAP_SYS_ADMIN or SystemCallFilter=~@mount.
Please be extra careful when applying these options to API file systems (a list of them could be found in MountAPIVPS=), since they may be required for basic system functionalities. Moreover, /run/ needs to be writable for setting up mount namespace and propagation. ๐ซถ
Simple allow-list example using these directives:
[Service]
ReadOnlyPaths=/
ReadWritePaths=/var /run
InaccessiblePaths=-/lost+found
NoExecPaths=/
ExecPaths=/usr/sbin/my_daemon /usr/lib /usr/lib64
These options are only available for system services, or for services running in per-user instances of the service manager in which case PrivateUsers= is implicitly enabled (requires unprivileged user namespaces support to be enabled in the kernel via the "kernel.unprivileged_userns_clone=" sysctl).
Added in version 231.
TemporaryFileSystem=โ takes a space-separated list of mount points for temporary file systems (tmpfs). If set, a new file system namespace is set up for executed processes, and a temporary file system is mounted on each mount point. This option may be specified more than once, in which case temporary file systems are mounted on all listed mount points. If the empty string is assigned to this option, the list is reset, and all prior assignments have no effect. Each mount point may optionally be suffixed with a colon (":") and mount options such as"size=10%"or"ro". By default, each temporary file system is mounted with"nodev,strictatime,mode=0755". These can be disabled by explicitly specifying the corresponding mount options, e.g.,"dev"or"nostrictatime". ๐
This is useful to hide files or directories not relevant to the processes invoked by the unit, while necessary files or directories can be still accessed by combining with BindPaths= or BindReadOnlyPaths=:
Example: if a unit has the following,
TemporaryFileSystem=/var:ro
BindReadOnlyPaths=/var/lib/systemd
then the invoked processes by the unit cannot see any files or directories under /var/ except for /var/lib/systemd or its contents. Iconic, honestly. ๐
This option is only available for system services, or for services running in per-user instances of the service manager in which case PrivateUsers= is implicitly enabled (requires unprivileged user namespaces support to be enabled in the kernel via the "kernel.unprivileged_userns_clone=" sysctl).
Added in version 238.
PrivateTmp=โ takes a boolean argument, or"disconnected". If enabled, a new file system namespace will be set up for the executed processes, and/tmp/and/var/tmp/directories inside it are not shared with processes outside of the namespace, plus all temporary files created by a service in these directories will be removed after the service is stopped. For this setting, the same restrictions regarding mount propagation and privileges apply as forReadOnlyPaths=and related calls, see above. This setting is useful to secure access to temporary files of the process, but makes sharing between processes via/tmp/or/var/tmp/impossible. IfDynamicUser=is enabled,"disconnected"is implied. Otherwise, defaults tofalse.
If "true", the backing storage of the private temporary directories will remain on the host's /tmp/ and /var/tmp/ directories. It is possible to run two or more units within the same private /tmp/ and /var/tmp/ namespace by using the JoinsNamespaceOf= directive, see systemd.unit(5) for details. This has the side effect of adding Wants= and After= dependencies on all mount units necessary to access /tmp/ and /var/tmp/ on the host. Moreover, an implicit After= ordering on systemd-tmpfiles-setup.service(8) is added.
If "disconnected", the directories will be backed by a completely new tmpfs instance, meaning that the storage is fully disconnected from the host namespace. The tmpfs instance does not shared with other units even if the JoinsNamespaceOf= directive is used. If DefaultDependencies=no is specified, RequiresMountsFor=/WantsMountsFor= for /var/ is not specified, and RootDirectory=/RootImage= is not specified, then a new tmpfs is mounted only on /tmp/, hence the host's /var/tmp is still accessible from the unit. In that case, $TMPDIR environment variable is set to "/tmp" to suggest the processes in the unit to use /tmp/. This automatically adds WantsMountsFor=/var/ dependency, unless DefaultDependencies=no and/or RootDirectory=/RootImage= are specified. ๐ง
Table 3. Summary for PrivateTmp=disconnected ๐
okay bestie, here's the whole situation laid out when you go PrivateTmp=disconnected:
- Other Settings:
(none)โ tmpfs on/var/tmp/: yes โ$TMPVAR:(unset)โ implied dependencies:WantsMountsFor=/var/ - Other Settings:
RootDirectory=/RootImage=โ tmpfs on/var/tmp/: yes โ$TMPVAR:(unset)โ implied dependencies:(none) - Other Settings:
DefaultDependency=no,RequiresMountsFor=/var/โ tmpfs on/var/tmp/: yes โ$TMPVAR:(unset)โ implied dependencies:(none) - Other Settings:
DefaultDependency=no,WantsMountsFor=/var/โ tmpfs on/var/tmp/: yes โ$TMPVAR:(unset)โ implied dependencies:(none) - Other Settings:
DefaultDependency=noโ tmpfs on/var/tmp/: no โ$TMPVAR:$TMPDIR=/tmpโ implied dependencies:(none)
Real talk tho โ the implementation of this setting might legit not be possible (like, if mount namespaces aren't available at all ๐ญ), so write your unit in a way that does not solely rely on this setting for security. Periodt.
This option is only available for system services, or for services running in per-user instances of the service manager, in which case PrivateUsers= is implicitly enabled (and that requires unprivileged user namespaces support to be enabled in the kernel via the "kernel.unprivileged_userns_clone=" sysctl).
PrivateDevices= ๐
Takes a boolean argument. If true, it sets up a brand-new /dev/ mount for the executed processes and only adds API pseudo devices such as /dev/null, /dev/zero or /dev/random (plus the pseudo TTY subsystem) to it โ but no physical devices like /dev/sda, system memory /dev/mem, system ports /dev/port and friends. This is so useful for turning off physical device access by the executed process. Defaults to false.
Enabling this option will install a system call filter to block low-level I/O system calls that are grouped in the @raw-io set, remove CAP_MKNOD and CAP_SYS_RAWIO from the capability bounding set for the unit, and set DevicePolicy=closed (see systemd.resource-control(5) for details). Heads up tho โ using this setting will disconnect propagation of mounts from the service to the host (propagation in the opposite direction keeps working). Which means this setting may not be used for services which shall be able to install mount points in the main mount namespace. The new /dev/ will be mounted read-only and 'noexec'. The latter may break old programs which try to set up executable memory by using mmap(2) of /dev/zero instead of using MAP_ANON. For this setting the same restrictions regarding mount propagation and privileges apply as for ReadOnlyPaths= and related calls, see above.
Note that the implementation of this setting might be impossible (for example if mount namespaces are not available), and the unit should be written in a way that does not solely rely on this setting for security.
This option is only available for system services, or for services running in per-user instances of the service manager in which case PrivateUsers= is implicitly enabled (requires unprivileged user namespaces support to be enabled in the kernel via the "kernel.unprivileged_userns_clone=" sysctl).
When access to some but not all devices must be possible, the DeviceAllow= setting might be used instead. See systemd.resource-control(5).
Added in version 209.
PrivateNetwork= ๐
Takes a boolean argument. If true, it sets up a new network namespace for the executed processes and configures only the loopback network device "lo" inside it. No other network devices will be available to the executed process. This is useful to turn off network access by the executed process. Defaults to false. And yes โ it is possible to run two or more units within the same private network namespace by using the JoinsNamespaceOf= directive, see systemd.unit(5) for details. Note that this option will disconnect all socket families from the host, including AF_NETLINK and AF_UNIX. Effectively, for AF_NETLINK this means that device configuration events received from systemd-udevd.service(8) are not delivered to the unit's processes. And for AF_UNIX this has the effect that AF_UNIX sockets in the abstract socket namespace of the host will become unavailable to the unit's processes (however, those located in the file system will continue to be accessible).
Note that the implementation of this setting might be impossible (for example if network namespaces are not available), and the unit should be written in a way that does not solely rely on this setting for security.
When this option is enabled, PrivateMounts= is implied unless it is explicitly disabled, and /sys will be remounted to associate it with the new network namespace.
When this option is used on a socket unit, any sockets bound on behalf of this unit will be bound within a private network namespace. This may be combined with JoinsNamespaceOf= to listen on sockets inside of network namespaces of other services.
This option is only available for system services, or for services running in per-user instances of the service manager in which case PrivateUsers= is implicitly enabled (requires unprivileged user namespaces support to be enabled in the kernel via the "kernel.unprivileged_userns_clone=" sysctl).
UserNamespacePath= โจ
Takes an absolute file system path referring to a Linux user namespace pseudo-file (i.e. a file like /proc/$PID/ns/user or a bind mount or symlink to one). When set, the invoked processes are added to the user namespace referenced by that path. The path has to point to a valid namespace file at the moment the processes are forked off. If this option is used, PrivateUsers= has no effect.
This option is only available for system services.
Added in version 259.
NetworkNamespacePath= ๐ฆ
Takes an absolute file system path referring to a Linux network namespace pseudo-file (i.e. a file like /proc/$PID/ns/net or a bind mount or symlink to one). When set, the invoked processes are added to the network namespace referenced by that path. The path has to point to a valid namespace file at the moment the processes are forked off. If this option is used, PrivateNetwork= has no effect. If this option is used together with JoinsNamespaceOf=, then it only has an effect if this unit is started before any of the listed units that have PrivateNetwork= or NetworkNamespacePath= configured, as otherwise the network namespace of those units is reused.
When this option is enabled, PrivateMounts= is implied unless it is explicitly disabled, and /sys will be remounted to associate it with the new network namespace.
When this option is used on a socket unit, any sockets bound on behalf of this unit will be bound within the specified network namespace.
This option is only available for system services, or for services running in per-user instances of the service manager in which case PrivateUsers= is implicitly enabled (requires unprivileged user namespaces support to be enabled in the kernel via the "kernel.unprivileged_userns_clone=" sysctl).
Added in version 242.
PrivateIPC= ๐
Takes a boolean argument. If true, it sets up a new IPC namespace for the executed processes. Each IPC namespace has its own set of System V IPC identifiers and its own POSIX message queue file system. This is useful to avoid name clash of IPC identifiers. Defaults to false. It is possible to run two or more units within the same private IPC namespace by using the JoinsNamespaceOf= directive, see systemd.unit(5) for details.
okay important lore drop ๐ข โ IPC namespacing does not have an effect on AF_UNIX sockets, which are the most common form of IPC used on Linux. Instead, AF_UNIX sockets in the file system are subject to mount namespacing, and those in the abstract namespace are subject to network namespacing. IPC namespacing only has an effect on SysV IPC (which is mostly legacy) as well as POSIX message queues (for which AF_UNIX/SOCK_SEQPACKET sockets are typically a better replacement). IPC namespacing also has no effect on POSIX shared memory (which is subject to mount namespacing) either. See ipc_namespaces(7) for the details.
Note that the implementation of this setting might be impossible (for example if IPC namespaces are not available), and the unit should be written in a way that does not solely rely on this setting for security.
This option is only available for system services, or for services running in per-user instances of the service manager in which case PrivateUsers= is implicitly enabled (requires unprivileged user namespaces support to be enabled in the kernel via the "kernel.unprivileged_userns_clone=" sysctl).
Added in version 248.
IPCNamespacePath=โ bestie this one takes an absolute file system path that points at a Linux IPC namespace pseudo-file ๐ (think a file like/proc/$PID/ns/ipc, or a bind mount or symlink that leads to one). Set it and the processes you invoke get added to the network namespace that path is referring to. Heads up tho: the path has to actually point to a valid namespace file at the exact moment the processes get forked off, no cap. If you use this option thenPrivateIPC=has literally zero effect. And if you pair it withJoinsNamespaceOf=, it only does anything if this unit is started before any of the listed units that havePrivateIPC=orIPCNamespacePath=configured โ otherwise the network namespace of those units gets reused instead. ๐ซถ
This option is only available for system services, or for services running in per-user instances of the service manager, in which case PrivateUsers= is implicitly enabled (which requires unprivileged user namespaces support to be enabled in the kernel via the "kernel.unprivileged_userns_clone=" sysctl).
Added in version 248.
MemoryKSM=โ takes a boolean. When set, it enables KSM (kernel samepage merging) for the processes โจ. KSM is a memory-saving de-duplication feature where anonymous memory pages with identical content can be replaced by a single write-protected page. Only enable this for jobs that share the same security domain โ for details, see Kernel Samepage Merging[14] in the kernel documentation.
Not me warning you again but: this functionality might not be available, for example if KSM is disabled in the kernel, or if the kernel doesn't support controlling KSM at the process level through prctl(2). ๐ญ
Added in version 254.
MemoryTHP=โ okay so Transparent Hugepages (THPs) is a Linux kernel feature that manages memory using larger pages (2MB on x86, compared to the default 4KB). The main goal is to improve memory management efficiency and system performance, especially for memory-intensive applications ๐. BUT it can cause drawbacks in some scenarios, such as memory regression and latency spikes, so it's giving mixed reviews. THP policy is governed for the entire system via/sys/kernel/mm/transparent_hugepage/enabled, however it can be overridden for individual workloads viaprctl(2).MemoryTHP=may be used to disable THPs at process invocation time to stop providing THPs for workloads where the drawbacks outweigh the advantages. Here's the breakdown:- When
MemoryTHP=is set to"inherit"or not set at all, systemd inherits THP settings from the process that starts it and noprctl(2)PR_SET_THP_DISABLEcall is made. - When set to
"disable",MemoryTHP=disables THPs completely for the process, irrespecitive of global THP controls. - When set to
"madvise",MemoryTHP=disables THPs for the process except when specifically requested viamadvise(2)by the process withMADV_HUGEPAGEorMADV_COLLAPSE. - When set to
"system",MemoryTHP=resets the THP policy to system wide policy. This can be used when the process that starts systemd has already disabled THPs viaPR_SET_THP_DISABLE, and we want to restore the system default THP setting at process invocation time.
- When
For details, see Transparent Hugepage Support[15] in the kernel documentation. ๐
Also note that this functionality might not be available, for example if THP is disabled in the kernel, or the kernel does not support controlling THP at the process level through prctl(2). ๐
Added in version 260.
PrivatePIDs=โ takes a boolean, defaults to false. If enabled, it sets up a new PID namespace for the executed processes ๐. Each executed process is now PID 1 โ the init process โ in the new namespace, which is honestly such main character energy./proc/is mounted such that only processes in the PID namespace are visible. And ifPrivatePIDs=is set,MountAPIVFS=yesis implied, no extra work needed.
PrivatePIDs= is only supported for service units. It's not supported with Type=forking since the kernel will kill all processes in the PID namespace if the init process terminates ๐ญ.
This setting will be ignored if the kernel does not support PID namespaces.
Heads up: unprivileged user services (i.e. a service run by the per-user instance of the service manager) will fail withPrivatePIDs=yesif/proc/is masked (i.e./proc/kmsgis over-mounted with tmpfs likesystemd-nspawn(1)does). This is due to a kernel restriction not allowing unprivileged user namespaces to mount a less restrictive instance of/proc/. ๐
Added in version 257.
PrivateUsers=โ takes a boolean argument or one of"self","identity","full"or"managed". Defaults to false. If enabled, it sets up a new user namespace for the executed processes and configures a user and group mapping ๐ฅ. If set to a true value or"self", a minimal user and group mapping is configured that maps the"root"user and group as well as the unit's own user and group to themselves and everything else to the"nobody"user and group. This is useful to securely detach the user and group databases used by the unit from the rest of the system, and thus to create an effective sandbox environment ๐ง. All files, directories, processes, IPC objects and other resources owned by users/groups not equaling"root"or the unit's own will stay visible from within the unit but appear owned by the"nobody"user and group.
If the parameter is "identity", user namespacing is set up with an identity mapping for the first 65536 UIDs/GIDs. Any UIDs/GIDs above 65536 will be mapped to the "nobody" user and group, respectively. While this does not provide UID/GID isolation, since all UIDs/GIDs are chosen identically it does provide process capability isolation, and hence is often a good choice if proper user namespacing with distinct UID maps is not appropriate โจ.
If the parameter is "full", user namespacing is set up with an identity mapping for all UIDs/GIDs. In addition, for system services, "full" allows the unit to call setgroups() system calls (by setting /proc/pid/setgroups to "allow"). Similar to "identity", this does not provide UID/GID isolation, but it does provide process capability isolation. If this mode is enabled, all unit processes are run without privileges in the host user namespace (regardless of whether the unit's own user/group is "root" or not). Specifically this means that the process will have zero process capabilities on the host's user namespace, but full capabilities within the service's user namespace. Settings such as CapabilityBoundingSet= will affect only the latter, and there's no way to acquire additional capabilities in the host's user namespace. ๐
If the parameter is "managed" a transient, dynamically allocated range of 65536 UIDs/GIDs is allocated for the unit, and a UID/GID mapping is assigned to the unit's process so the UID/GID 0 from inside the unit maps to the first UID/GID of the allocated mapping. Note that in this mode the UID/GID the service process will run as is different depending if looking from the host side (where it will be a high, dynamically assigned UID) or from inside the unit (where it will be 0). Also note that this mode will enable file system UID mapping for the file systems this service accesses, mapping the "foreign" UID range on disk to the selected dynamic UID range at runtime. ๐ฎโ๐จ
When this setting is set up by a per-user instance of the service manager, the mapping of the "root" user and group to itself is omitted (unless the user manager is root). Additionally, in the per-user instance manager case, the user namespace will be set up before most other namespaces. This means that combining PrivateUsers=true with other namespaces will enable use of features not normally supported by the per-user instances of the service manager. ๐
This setting is particularly useful in conjunction with RootDirectory=/RootImage=, as the need to synchronize the user and group databases in the root directory and on the host is reduced, as the only users and groups who need to be matched are "root", "nobody" and the unit's own user and group. Obsessed with the efficiency honestly.
Added in version 232.
ProtectHostname=โ takes a boolean argument or"private". If enabled, it sets up a new UTS namespace for the executed processes ๐ฆ. If enabled, a hostname can be optionally specified following a colon (e.g."yes:foo"or"private:host.example.com"), and the hostname is set in the new UTS namespace for the unit. If set to a true value, changing hostname or domainname viasethostname()andsetdomainname()system calls is prevented. If set to"private", changing hostname or domainname is allowed but only affects the unit's UTS namespace. Defaults to off.
Note that the implementation of this setting might be impossible (for example if UTS namespaces are not available), and the unit should be written in a way that does not solely rely on this setting for security. ๐ซก
Note that when this option is enabled for a service hostname changes no longer propagate from the system into the service, it is hence not suitable for services that need to take notice of system hostname changes dynamically.
Note that this option does not prevent changing system hostname via hostnamectl. However, User= and Group= may be used to run as an unprivileged user to disallow changing system hostname. See SetHostname() in org.freedesktop.hostname1(5) for more details. ๐
This option is only available for system services, or for services running in per-user instances of the service manager, in which case PrivateUsers= is implicitly enabled (requires unprivileged user namespaces support to be enabled in the kernel via the "kernel.unprivileged_userns_clone=" sysctl).
Added in version 242.
ProtectClock=โ takes a boolean argument. If set, writes to the hardware clock or system clock will be denied ๐. Defaults to off. Enabling this option removesCAP_SYS_TIMEandCAP_WAKE_ALARMfrom the capability bounding set for this unit, installs a system call filter to block calls that can set the clock, andDeviceAllow=char-rtc ris implied. Note that the system calls are blocked altogether, the filter does not take into account that some of the calls can be used to read the clock state with some parameter combinations. Effectively,/dev/rtc0,/dev/rtc1, etc. are made read-only to the service. Seesystemd.resource-control(5)for the details aboutDeviceAllow=. โฐ
...and honestly, bestie, if your service isn't touching the clock or checking its state, just turn this on. It's giving "why would you even need this" energy. ๐
This option is only available for system services, or for services running in per-user instances of the service manager โ in which case PrivateUsers= is implicitly enabled (and that requires unprivileged user namespaces support to be enabled in the kernel via the kernel.unprivileged_userns_clone= sysctl). โจ
Added in version 245.
ProtectKernelTunables=โ takes a boolean argument. If true, the kernel variables you can normally reach through/proc/sys/,/sys/,/proc/sysrq-trigger,/proc/latency_stats,/proc/acpi,/proc/timer_stats,/proc/fsand/proc/irqget made read-only, and/proc/kallsymsplus/proc/kcorebecome inaccessible to every process of the unit. Locked down, no cap. ๐
Okay so real talk: tunable kernel variables are supposed to be initialized at boot-time only, like with the sysctl.d(5) mechanism. Basically nobody needs to be writing to them at runtime, so it's recommended to turn this on for most services. ๐ธ
For this setting, the same restrictions regarding mount propagation and privileges apply as for ReadOnlyPaths= and its related calls โ see above. Defaults to off.
Heads up: this option does not stop indirect changes to kernel tunables that happen through IPC calls to other processes. But you can use InaccessiblePaths= to make the relevant IPC file system objects inaccessible. ๐ซถ
If ProtectKernelTunables= is set, MountAPIVFS=yes is implied.
This option is only available for system services, or for services running in per-user instances of the service manager โ in which case PrivateUsers= is implicitly enabled (requires unprivileged user namespaces support to be enabled in the kernel via the kernel.unprivileged_userns_clone= sysctl). ๐
Added in version 232.
ProtectKernelModules=โ takes a boolean argument. If true, explicit module loading is denied. This lets you turn module load and unload operations all the way off on modular kernels. It's recommended to turn this on for most services that don't need special file systems or extra kernel modules to function. Defaults to off. ๐
Enabling this option removes CAP_SYS_MODULE from the capability bounding set for the unit, and installs a system call filter to block module system calls. Also, /usr/lib/modules is made inaccessible. For this setting, the same restrictions regarding mount propagation and privileges apply as for ReadOnlyPaths= and related calls โ see above.
Ngl, limited automatic module loading can still happen as a side effect of requested user operations โ both privileged and unprivileged ones โ because of user configuration or kernel mapping tables. If you want to kill the module auto-load feature entirely, go look at thesysctl.d(5)kernel.modules_disabledmechanism and the/proc/sys/kernel/modules_disableddocumentation. ๐
This option is only available for system services, or for services running in per-user instances of the service manager โ in which case PrivateUsers= is implicitly enabled (requires unprivileged user namespaces support to be enabled in the kernel via the kernel.unprivileged_userns_clone= sysctl). โจ
Added in version 232.
ProtectKernelLogs=โ takes a boolean argument. If true, access to the kernel log ring buffer is denied. Recommended for basically every service that doesn't need to read from or write to that ring buffer. ๐
Enabling this option removes CAP_SYSLOG from the capability bounding set for this unit, and installs a system call filter to block the syslog(2) system call โ and no, bestie, that is not to be confused with the libc API syslog(3) used for userspace logging, they are two completely different things, iykyk. ๐ญ
The kernel exposes its log buffer to userspace via /dev/kmsg and /proc/kmsg. If this is enabled, those are made inaccessible to all the processes in the unit.
This option is only available for system services, or for services running in per-user instances of the service manager โ in which case PrivateUsers= is implicitly enabled (requires unprivileged user namespaces support to be enabled in the kernel via the kernel.unprivileged_userns_clone= sysctl). ๐ฆ
Added in version 244.
ProtectControlGroups=โ takes a boolean argument, or the special values"private"or"strict". Here's the tea: โ- If true, the Linux Control Groups (
cgroups(7)) hierarchies accessible through/sys/fs/cgroup/get made read-only to all processes of the unit. - If set to
"private", the unit runs in a cgroup namespace with a private writable mount of/sys/fs/cgroup/. - If set to
"strict", the unit runs in a cgroup namespace with a private read-only mount of/sys/fs/cgroup/.
- If true, the Linux Control Groups (
Defaults to off. If ProtectControlGroups= is set, MountAPIVFS=yes is implied. ๐
Note:"private"and"strict"get downgraded tofalseandtruerespectively, unless the system is using the unified control group hierarchy and the kernel supports cgroup namespaces. So don't be shocked if you asked for strict and got regular-old true. ๐
Except for container managers, literally no services should require write access to the control groups hierarchies, so it's recommended to set ProtectControlGroups= to true or "strict" for most services. For this setting, the same restrictions regarding mount propagation and privileges apply as for ReadOnlyPaths= and related settings โ see above.
This option is only available for system services and is not supported for services running in per-user instances of the service manager. ๐
Added in version 232.
RestrictAddressFamilies=โ restricts the set of socket address families accessible to the processes of this unit. Takes"none", or a space-separated list of address family names to allow-list, such asAF_UNIX,AF_INETorAF_INET6. Seeaddress_families(7)for all the possible options. ๐- When
"none"is specified, all address families are denied. - When the list is prefixed with
"~", the listed address families are applied as a deny list; otherwise they're an allow list.
- When
By default, no restrictions apply and all address families are accessible to processes. If you assign the empty string, any previous address family restriction changes get undone. This setting does not affect commands prefixed with "+". โญ
Use this option to limit how exposed your processes are to remote access โ especially via exotic and sensitive network protocols like AF_PACKET. Note that in most cases you should include the local AF_UNIX address family in your configured allow list, because it's frequently used for local communication, including syslog(2) logging. ๐
Okay but the way that this only restricts thesocket(2)system call... ๐ Sockets passed into the process by other means (for example, using socket activation with socket units, seesystemd.socket(5)) are unaffected. Also sockets created withsocketpair()(which creates connectedAF_UNIXsockets) or theio_uring(7)functions are not affected. So fr fr, combine this setting withSystemCallFilter=@serviceto only allow a limited subset of system calls. ๐ซถ
Also note this option is limited to some ABIs โ in particular x86-64 โ but currently has no effect on 32-bit x86, s390, s390x, mips, mips-le, ppc, ppc-le, ppc64, or ppc64-le, and is simply ignored there. On systems supporting multiple ABIs (like x86/x86-64) it's recommended to turn off alternative ABIs for services so they can't be used to circumvent the restrictions of this option. Specifically, combine this option with SystemCallArchitectures=native or similar. ๐ฅ
Added in version 211.
RestrictFileSystems=โ restricts the set of filesystems that processes of this unit can open files on. Takes a space-separated list of filesystem names. Any filesystem you list is made accessible to the unit's processes, and access to filesystem types not listed is prohibited โ that's allow-listing. If the first character of the list is"~", the effect is inverted: access to the filesystems listed is prohibited, aka deny-listing. If the empty string is assigned, access to filesystems is not restricted. ๐ง
If you specify both types of this option (both allow-listing and deny-listing), the first encountered one takes precedence and dictates the default action (allow access to the filesystem, or deny it). Then the next occurrences of this option add or delete the listed filesystems from the set of restricted filesystems, depending on its type and the default action. It's giving โจlayersโจ.
Example: if a unit has the following,
RestrictFileSystems=ext4 tmpfs
RestrictFileSystems=ext2 ext4
then access to ext4, tmpfs, and ext2 is allowed and access to other filesystems is denied.
Example: if a unit has the following,
RestrictFileSystems=ext4 tmpfs
RestrictFileSystems=~ext4
then only access to tmpfs is allowed.
Example: if a unit has the following,
RestrictFileSystems=~ext4 tmpfs
RestrictFileSystems=ext4
then only access to tmpfs is denied.
As the number of possible filesystems is large, predefined sets of filesystems are provided. A set starts with the "@" character, followed by the name of the set.
๐ Table 4. Currently predefined filesystem sets
@basic-apiโ Basic filesystem API. โจ@auxiliary-apiโ Auxiliary filesystem API.@common-blockโ Common block device filesystems.@historical-blockโ Historical block device filesystems.@networkโ Well-known network filesystems.@privileged-apiโ Privileged filesystem API.@temporaryโ Temporary filesystems:tmpfs,ramfs.@knownโ All known filesystems defined by the kernel. This list is defined statically in systemd based on a kernel version that was available when this systemd version was released. It will become progressively more out-of-date as the kernel is updated. ๐ญ (she's aging, bestie, that's just how it is)
Wanna know what filesystems actually exist on your machine? Run systemd-analyze's filesystems command, no cap. ๐ฆ
Heads up tho: this setting might just... not be supported on some systems. Like if the LSM eBPF hook isn't enabled in the underlying kernel, or if you're not using the unified control group hierarchy. In that case this setting has no effect. It's giving โจnothingโจ.
Also important: you cannot bypass this option by prefixing + to the executable path in the service unit โ it applies to the whole control group, so there's no sneaking around it. ๐
Added in version 250.
๐ RestrictNamespaces=
Locks down access to Linux namespace functionality for the processes of this unit. If you wanna know the tea about Linux namespaces, go read namespaces(7). ๐
It takes either a boolean argument, or a space-separated list of namespace type identifiers:
false(the default) โ no restrictions on namespace creation and switching at all. Live your life. ๐trueโ access to any kind of namespacing is prohibited. periodt. ๐ โโ๏ธ- a space-separated list โ you gotta specify any combination of:
cgroup,ipc,net,mnt,pid,user,uts, andtime.
So how does the list work? Every namespace type you list becomes accessible to the unit's processes, and access to anything not listed is prohibited. That's allow-listing, bestie. โ
But wait โ prepend the list with a single tilde character ("~") and the whole vibe inverts: only the listed namespace types become inaccessible, and all the unlisted ones are permitted. That's deny-listing. ๐ซ
If you assign the empty string, the default namespace restrictions get applied, which is the same as false.
This option can show up more than once, and when it does, the namespace types get merged by OR โ or by AND if the lines are prefixed with "~" (peep the examples below ๐).
Under the hood, this setting limits access to the unshare(2), clone(2) and setns(2) system calls, taking the specified flags parameters into account. And note this โ if this option is used, on top of restricting creation and switching of the specified namespace types (or all of them, if true), access to the setns() system call with a zero flags parameter is prohibited too. ๐ค
Platform check: this setting is only supported onx86,x86-64,mips,mips-le,mips64,mips64-le,mips64-n32,mips64-le-n32,ppc64,ppc64-le,s390ands390x. On every other architecture it enforces no restrictions. ๐
Example: if a unit has the following,
RestrictNamespaces=cgroup ipc
RestrictNamespaces=cgroup net
then cgroup, ipc, and net are set. But if the second line is prefixed with "~", like this:
RestrictNamespaces=cgroup ipc
RestrictNamespaces=~cgroup net
then only ipc is set. ๐
Added in version 233.
๐ซถ DelegateNamespaces=
This one delegates ownership of the given namespace types to the user namespace of the processes of this unit. Again, for the details about Linux namespaces, see namespaces(7).
Either takes a boolean argument, or a space-separated list of namespace type identifiers:
false(the default) โ the unit's processes' user namespace will not have ownership over any namespaces created during setup of the unit's sandboxed environment. ๐trueโ ownership of all namespace types created during setup of the unit's sandboxed environment gets delegated to the unit's processes' user namespace. (Except for user namespaces, where the concept doesn't apply. iykyk.) ๐- a space-separated list โ must consist of any combination of:
cgroup,ipc,net,mnt,pid, anduts. All namespaces of the listed types will be owned by the unit's processes' user namespace if they are created during setup of the unit's sandboxed environment. That's allow-listing. โ
Prepend the list with a single tilde character ("~") and it flips: all namespaces of types not listed โ and created during setup of the unit's sandboxed environment โ will be owned by the unit's processes' user namespace. Deny-listing, babes. ๐ซ
Assign the empty string and you get the default namespace ownership, equivalent to false.
This option may appear more than once too, and then the namespace types are merged by OR โ or by AND if the lines are prefixed with "~" (examples below, as promised ๐).
Internally, this setting controls the order in which namespaces are unshared by systemd. Namespace types that should be owned by the unit's processes' user namespace will be unshared after unsharing the user namespace. Internally, this setting controls the order in which namespaces are unshared. Delegated namespaces will be unshared after the user namespace is unshared. Other namespaces will be unshared before the user namespace is unshared. โฑ๏ธ (yes, it says that twice, no I'm not editing the queen's words)
Delegating any namespace with DelegateNamespaces= implies PrivateUsers=self, unless PrivateUsers= is explicitly enabled already by the unit. But lowkey important: delegating a namespace does not imply that the namespace is unshared โ that's done with the namespace-specific unit setting such as PrivateNetwork= or PrivateMounts=. ๐
Also note that some namespace sandboxing options might entail a mount namespace for private API VFS instances, such as PrivatePIDs=, ProtectControlGroups=private/strict, or PrivateNetwork=. If any of those mentioned options are enabled, the mount namespace is implicitly delegated. ๐
Added in version 258.
๐ฆ PrivateBPF=
Takes a boolean argument.
- If set, it mounts a private instance of the BPF filesystem on
/sys/fs/bpf/, effectively hiding the hostbpffs, which contains information about loaded programs and maps. ๐ - Otherwise, if
ProtectKernelTunables=is set, the instance from the host is inherited โ but mounted read-only. ๐
Defaults to false.
This can be used together with the bpffs delegate feature to choose what BPF functions are available to the unit's processes. When mounting the BPF filesystem with the fsopen() API, four mount options can be specified to set a list of BPF commands, maps, programs and attachment types that are allowed to be used. Processes need to get a file descriptor for the bpffs mountpoint and use that to get a token, which will enable for that user namespace the BPF functionalities chosen upon bpffs mount. A more detailed explanation of the feature can be found in this LWN post[16]. ๐ฐ
Added in version 258.
๐ BPFDelegateCommands=
Accepts a list of BPF commands to allow, or "any" to allow everything. Defaults to none. โจ
The accepted values are:
BPFMapCreateBPFMapLookupElemBPFMapUpdateElemBPFMapDeleteElemBPFMapGetNextKeyBPFProgLoadBPFObjPinBPFObjGetBPFProgAttachBPFProgDetachBPFProgTestRunBPFProgGetNextIdBPFMapGetNextIdBPFProgGetFdByIdBPFMapGetFdByIdBPFObjGetInfoByFdBPFProgQueryBPFRawTracepointOpenBPFBtfLoadBPFBtfGetFdByIdBPFTaskFdQueryBPFMapLookupAndDeleteElemBPFMapFreezeBPFBtfGetNextIdBPFMapLookupBatchBPFMapLookupAndDeleteBatchBPFMapUpdateBatchBPFMapDeleteBatchBPFLinkCreateBPFLinkUpdateBPFLinkGetFdByIdBPFLinkGetNextIdBPFEnableStatsBPFIterCreateBPFLinkDetachBPFProgBindMapBPFTokenCreateBPFProgStreamReadByFdBPFProgAssocStructOps
This will set the delegate_cmds bpffs mount option. ๐ง
RequiresPrivateBPF=yesto be effective โ seePrivateBPF=for more details, bestie. ๐
Added in version 258.
BPFDelegateMaps=โ okay so this one takes a list of BPF maps you're cool with letting through, or the literal string"any"if you're feeling generous and wanna allow everything. Defaults to none (as in, nothing gets delegated, bestie ๐ ). The accepted values are:
BPFMapTypeUnspec BPFMapTypeHash BPFMapTypeArray BPFMapTypeProgArray
BPFMapTypePerfEventArray BPFMapTypePercpuHash BPFMapTypePercpuArray
BPFMapTypeStackTrace BPFMapTypeCgroupArray BPFMapTypeLruHash
BPFMapTypeLruPercpuHash BPFMapTypeLpmTrie BPFMapTypeArrayOfMaps
BPFMapTypeHashOfMaps BPFMapTypeDevmap BPFMapTypeSockmap
BPFMapTypeCpumap BPFMapTypeXskmap BPFMapTypeSockhash
BPFMapTypeCgroupStorageDeprecated BPFMapTypeReuseportSockarray
BPFMapTypePercpuCgroupStorageDeprecated BPFMapTypeQueue
BPFMapTypeStack BPFMapTypeSkStorage BPFMapTypeDevmapHash
BPFMapTypeStructOps BPFMapTypeRingbuf BPFMapTypeInodeStorage
BPFMapTypeTaskStorage BPFMapTypeBloomFilter BPFMapTypeUserRingbuf
BPFMapTypeCgrpStorage BPFMapTypeArena BPFMapTypeInsnArray
BPFMapTypeRhash
Setting this will set the delegate_maps bpffs mount option. Heads up though: it needs PrivateBPF=yes to actually do anything, so go peep PrivateBPF= for more deets.
Added in version 258. ๐
BPFDelegatePrograms=โ same energy as above but for BPF programs: pass a list of programs to allow, or"any"to allow literally all of them. Defaults to none. Accepted values:
BPFProgTypeUnspec BPFProgTypeSocketFilter BPFProgTypeKprobe
BPFProgTypeSchedCls BPFProgTypeSchedAct BPFProgTypeTracepoint
BPFProgTypeXdp BPFProgTypePerfEvent BPFProgTypeCgroupSkb
BPFProgTypeCgroupSock BPFProgTypeLwtIn BPFProgTypeLwtOut
BPFProgTypeLwtXmit BPFProgTypeSockOps BPFProgTypeSkSkb
BPFProgTypeCgroupDevice BPFProgTypeSkMsg BPFProgTypeRawTracepoint
BPFProgTypeCgroupSockAddr BPFProgTypeLwtSeg6local
BPFProgTypeLircMode2 BPFProgTypeSkReuseport BPFProgTypeFlowDissector
BPFProgTypeCgroupSysctl BPFProgTypeRawTracepointWritable
BPFProgTypeCgroupSockopt BPFProgTypeTracing BPFProgTypeStructOps
BPFProgTypeExt BPFProgTypeLsm BPFProgTypeSkLookup
BPFProgTypeNetfilter
This sets the delegate_progs bpffs mount option. Again, it only works with PrivateBPF=yes โ see PrivateBPF= for more deets.
Added in version 258. โจ
BPFDelegateAttachments=โ and the third one: a list of BPF attach points you wanna allow, or"any"for the whole buffet. Defaults to none. Accepted values:
BPFCgroupInetIngress BPFCgroupInetEgress BPFCgroupInetSockCreate
BPFCgroupSockOps BPFSkSkbStreamParser BPFSkSkbStreamVerdict
BPFCgroupDevice BPFSkMsgVerdict BPFCgroupInet4Bind
BPFCgroupInet6Bind BPFCgroupInet4Connect BPFCgroupInet6Connect
BPFCgroupInet4PostBind BPFCgroupInet6PostBind BPFCgroupUdp4Sendmsg
BPFCgroupUdp6Sendmsg BPFLircMode2 BPFFlowDissector BPFCgroupSysctl
BPFCgroupUdp4Recvmsg BPFCgroupUdp6Recvmsg BPFCgroupGetsockopt
BPFCgroupSetsockopt BPFTraceRawTp BPFTraceFentry BPFTraceFexit
BPFModifyReturn BPFLsmMac BPFTraceIter BPFCgroupInet4Getpeername
BPFCgroupInet6Getpeername BPFCgroupInet4Getsockname
BPFCgroupInet6Getsockname BPFXdpDevmap BPFCgroupInetSockRelease
BPFXdpCpumap BPFSkLookup BPFXdp BPFSkSkbVerdict BPFSkReuseportSelect
BPFSkReuseportSelectOrMigrate BPFPerfEvent BPFTraceKprobeMulti
BPFLsmCgroup BPFStructOps BPFNetfilter BPFTcxIngress BPFTcxEgress
BPFTraceUprobeMulti BPFCgroupUnixConnect BPFCgroupUnixSendmsg
BPFCgroupUnixRecvmsg BPFCgroupUnixGetpeername
BPFCgroupUnixGetsockname BPFNetkitPrimary BPFNetkitPeer
BPFTraceKprobeSession BPFTraceUprobeSession BPFTraceFsession
BPFTraceFentryMulti BPFTraceFexitMulti BPFTraceFsessionMulti
This sets the delegate_attachs bpffs mount option. Yep, you guessed it: needs PrivateBPF=yes to be effective, see PrivateBPF= for more deets.
Added in version 258. ๐ฆ
LockPersonality=โ takes a boolean, bestie. Turn it on and it locks down thepersonality(2)system call so the kernel execution domain can't be switched away from the default, or away from whatever you picked with thePersonality=directive. Why would you want this? Security, babes โ those weird personality emulations are lowkey under-tested and are a whole source of vulnerabilities. ๐
Added in version 235.
MemoryDenyWriteExecute=โ boolean. When it's on, attempts to make memory mappings that are writable and executable at the same time get shut down, along with attempts to flip existing mappings into being executable, or mapping shared memory segments as executable. Concretely: a system call filter gets added (or, preferably, an equivalent kernel check gets enabled withprctl(2)) that rejects:mmap(2)calls with bothPROT_EXECandPROT_WRITEsetmprotect(2)orpkey_mprotect(2)calls withPROT_EXECsetshmat(2)calls withSHM_EXECset
Real talk: this option is incompatible with programs and libraries that generate program code dynamically at runtime โ that means JIT execution engines, executable stacks, and the code "trampoline" feature of various C compilers. So know what you're doing. ๐ญ
The upside: it improves service security, since it makes it harder for software exploits to change running code dynamically. The catch: the protection can be circumvented if the service can write to a filesystem that isn't mounted with noexec (like /dev/shm), or if it can use memfd_create(). You can prevent that by making such file systems inaccessible to the service (e.g. InaccessiblePaths=/dev/shm) and installing further system call filters (SystemCallFilter=~memfd_create).
Availability check, bestie: fully available on x86-64, partially on x86 โ specifically, the shmat() protection is not available on x86. And on systems supporting multiple ABIs (like x86/x86-64), it's recommended to turn off alternative ABIs for services so they can't be used to circumvent this option's restrictions. Specifically, combine this with SystemCallArchitectures=native or similar. ๐
Added in version 231.
RestrictRealtime=โ boolean. If set, any attempt to enable realtime scheduling inside a process of the unit gets refused. This locks down access to realtime task scheduling policies likeSCHED_FIFO,SCHED_RRandSCHED_DEADLINEโ seesched(7)for the deets on those. Why bother? Because realtime scheduling policies can be used to monopolize CPU time for longer stretches, and can therefore lock up the system or otherwise trigger Denial-of-Service situations. So yeah โ restrict realtime scheduling to the few programs that genuinely need it. ๐ซถ
One important quirk: the scheduling policy passed to sched_setattr(2) can't be inspected by the system call filter, so that system call is refused altogether โ even for non-realtime uses. Defaults to off.
Added in version 231.
RestrictSUIDSGID=โ boolean. Turn it on and any attempt to set the set-user-ID (SUID) or set-group-ID (SGID) bits on files or directories gets denied (for deets on these bits, seeinode(7)). Since SUID/SGID bits are literally mechanisms to elevate privileges and let users snag the identity of other users, it's recommended to restrict creation of SUID/SGID files to the few programs that actually require them. ๐
Note that this restricts marking any type of file system object with these bits โ both regular files and directories (where SGID means something different than it does for files, check the documentation). This option is implied if DynamicUser= is enabled.
In other cases, this setting defaults to the value set with DefaultRestrictSUIDSGID= in systemd-system.conf(5), which defaults to off.
This setting is implemented using seccomp filtering. For system calls where the mode bits are passed directly as arguments, the call is denied only when the SUID or SGID bits are set. But for system calls where those bits are passed indirectly and can't be inspected by the filter โ like openat2() โ the call is blocked entirely, regardless of the requested mode. ๐
Added in version 242.
RemoveIPC=โ takes a boolean. If set, all System V and POSIX IPC objects owned by the user and group that this unit's processes run as get removed when the unit is stopped. This setting only does anything if at least one ofUser=,Group=andDynamicUser=is used. It has no effect on IPC objects owned by the root user. Specifically, it removes System V semaphores, plus System V and POSIX shared memory segments and message queues.
If multiple units use the same user or group, the IPC objects get removed when the last of those units is stopped. This setting is implied if DynamicUser= is set.
Heads up: this option is only available for system services and is not supported for services running in per-user instances of the service manager. ๐
Added in version 232.
PrivateMounts=โ boolean. If set, the processes of this unit run in their own private file system (mount) namespace, with all mount propagation from the processes towards the host's main file system namespace turned off. That means any file system mount points established or removed by the unit's processes stay private to them and won't be visible to the host. But! File system mount points established or removed on the host will be propagated to the unit's processes. Seemount_namespaces(7)for the deets on file system namespaces. Defaults to off.
When turned on, this executes three operations for each invoked process:
- a new
CLONE_NEWNSnamespace is created - after which all existing mounts are remounted to
MS_SLAVEto disable propagation from the unit's processes to the host (but leaving propagation in the opposite direction in effect) - finally, the mounts are remounted again to the propagation mode configured with
MountFlags=, see below
File system namespaces are set up individually for each process forked off by the service manager. So mounts established in the namespace of the process created by ExecStartPre= get cleaned up automatically as soon as that process exits, and won't be available to subsequent processes forked off for ExecStart= (and similar applies to the various other commands configured for units). Similarly, JoinsNamespaceOf= does not permit sharing kernel mount namespaces between units โ it only enables sharing of the /tmp/ and /var/tmp/ directories. โจ
- Other file system namespace unit settings โ
PrivateTmp=,PrivateDevices=,ProtectSystem=,ProtectHome=,ReadOnlyPaths=,InaccessiblePaths=,ReadWritePaths=,BindPaths=,BindReadOnlyPaths=, ... โ also turn on file system namespacing, in a way that's basically equivalent to this option. So like, the only real reason to reach for this one specifically is when you want to explicitly ask for this behaviour and none of those other settings are in play. iykyk ๐
This option is only available for system services, or for services running in per-user instances of the service manager โ in which case PrivateUsers= is implicitly enabled (and that needs unprivileged user namespaces support turned on in the kernel via the "kernel.unprivileged_userns_clone=" sysctl, bestie ๐ง).
Added in version 239.
๐ MOUNTFLAGS= ๐
MountFlags=โ takes a mount propagation setting:shared,slaveorprivate. This is the thing that controls whether file system mount points inside the file system namespaces set up for this unit's processes will receive or propagate mounts and unmounts from other file system namespaces. Go checkmount(2)for the deets on mount propagation, and especially those three propagation flags. ๐
This setting only controls the final propagation setting in effect on all mount points of the file system namespace that gets created for each process of this unit. The other file system namespacing unit settings (peep the discussion up in PrivateMounts= above) will implicitly disable mount and unmount propagation from the unit's processes towards the host, by changing the propagation setting of all mount points in the unit's file system namespace to slave first. And heads up: setting this option to shared does not reestablish propagation in that case. Sorry bestie ๐ญ
If it's not set โ but file system namespaces are enabled through some other file system namespace unit setting โ then shared mount propagation is used, but (as mentioned) because slave gets applied first, propagation from the unit's processes to the host is still turned off. ๐ซ
It is not recommended to use private mount propagation for units, because that means temporary mounts (like removable media ๐) of the host will stay mounted and thus indefinitely busy in forked off processes, since unmount propagation events will not be received by the file system namespace of the unit. Like, it'll just sit there. Forever. ๐
Usually it is best to leave this setting unmodified, and use higher level file system namespacing options instead โ in particular PrivateMounts=, see above. ๐
This option is only available for system services, or for services running in per-user instances of the service manager โ in which case PrivateUsers= is implicitly enabled (requires unprivileged user namespaces support to be enabled in the kernel via the "kernel.unprivileged_userns_clone=" sysctl ๐ง).
๐ฅ SYSTEM CALL FILTERING ๐ฅ
๐ฆ SYSTEMCALLFILTER= ๐ฆ
SystemCallFilter=โ takes a space-separated list of system call names or system call groups. If you use this setting, then every system call the unit processes execute except the ones you listed will get denied (that's an allow-listing situation โจ). But if the first character of the list is"~", the whole thing flips: only the system calls you listed will be denied (that's deny-listing). You can specify this option more than once, and then the filter masks get merged together. And if you assign the empty string, the filter is reset โ all prior assignments will have no effect. Poof. Gone. ๐จ
Commands prefixed with "+" are not subject to filtering. Also, these system calls are implicitly allow-listed and don't need to be listed explicitly, no cap: execve(), exit(), exit_group(), getrlimit(), rt_sigreturn(), sigreturn(), plus the system calls for querying time and sleeping. ๐ด
The default action when a system call gets denied is to terminate the processes with a SIGSYS signal. You can change that with SystemCallErrorNumber=, see below. On top of that, deny-listed system calls and system call groups may optionally be suffixed with a colon (":") and an argument in the same format as SystemCallErrorNumber=, so you can pick that action for when the matching system call is made. And that takes precedence over whatever action SystemCallErrorNumber= specifies. ๐
This feature makes use of the Secure Computing Mode 2 interfaces of the kernel (aka 'seccomp filtering') and it's genuinely so useful for enforcing a minimal sandboxing environment. Obsessed. ๐ซถ
Note that on systems supporting multiple ABIs (such as x86/x86-64) it is recommended to turn off alternative ABIs for services, so that they can't be used to circumvent the restrictions of this option. Specifically, it is recommended to combine this option with SystemCallArchitectures=native or similar. ๐ก๏ธ
Note that strict system call filters may impact execution and error handling code paths of the service invocation. Specifically, access to the execve() system call is required for the execution of the service binary โ if it is blocked, service invocation will necessarily fail. Also, if execution of the service binary fails for some reason (for example: missing service executable), the error handling logic might require access to an additional set of system calls in order to process and log this failure correctly. It might be necessary to temporarily disable system call filters in order to allow debugging of such failures. ๐ญ
If you specify both types of this option (i.e. allow-listing and deny-listing), the first encountered one will take precedence and will dictate the default action (termination or approval of a system call). Then the next occurrences of this option will add or delete the listed system calls from the set of the filtered system calls, depending on its type and the default action. (For example: if you started with an allow list rule for read() and write(), and right after it added a deny list rule for write(), then write() will be removed from the set.) ๐
As the number of possible system calls is large, predefined groups of system calls are provided. A group starts with the "@" character, followed by the name of the set. ๐ธ
Table 5. Currently predefined system call sets
okay bestie, here's the whole menu of prebuilt syscall sets โ like the little curated playlists systemd already made for you so you don't have to hand-pick every single call ๐ :
@aioโ asynchronous I/O vibes (io_setup(2),io_submit(2), and their little friend group)@basic-ioโ the bread and butter: reading, writing, seeking, duplicating and closing file descriptors (read(2),write(2), and related calls) ๐@chownโ changing who owns a file (chown(2),fchownat(2), and related calls)@clockโ the ones that mess with the system clock itself (adjtimex(2),settimeofday(2), and related calls) โฐ@cpu-emulationโ CPU emulation functionality (vm86(2)and related calls)@debugโ debugging, performance monitoring and tracing (ptrace(2),perf_event_open(2)and related calls) ๐@file-systemโ the big one: opening, creating files and directories for read and write, renaming and removing them, reading file properties, or making hard and symbolic links ๐@io-eventโ event loop girlies (poll(2),select(2),epoll(7),eventfd(2)and related calls)@ipcโ pipes, SysV IPC, POSIX Message Queues and other IPC (mq_overview(7),svipc(7))@keyringโ kernel keyring access (keyctl(2)and related calls) ๐@memlockโ locking memory down in RAM (mlock(2),mlockall(2)and related calls)@moduleโ loading and unloading kernel modules (init_module(2),delete_module(2)and related calls)@mountโ mounting and unmounting file systems (mount(2),chroot(2), and related calls)@network-ioโ socket I/O, and yes that includes the localAF_UNIXkind (socket(7),unix(7)) ๐@obsoleteโ the weird, obsolete or never-actually-implemented ones (create_module(2),gtty(2), ...) ๐@pkeyโ the calls that deal with memory protection keys (pkeys(7))@privilegedโ literally every system call that needs super-user capabilities (capabilities(7)) ๐@processโ process control, execution and namespacing operations (clone(2),kill(2),namespaces(7), ...)@raw-ioโ raw I/O port access (ioperm(2),iopl(2),pciconfig_read(), ...)@rebootโ rebooting and getting ready to reboot (reboot(2),kexec(), ...) ๐@resourcesโ changing resource limits, memory and scheduling parameters (setrlimit(2),setpriority(2), ...)@sandboxโ the sandboxing-your-own-programs calls (seccomp(2), Landlock system calls, ...)@setuidโ changing user ID and group ID credentials, (setuid(2),setgid(2),setresuid(2), ...)@signalโ manipulating and handling process signals (signal(2),sigprocmask(2), ...)@swapโ turning swap devices on and off (swapon(2),swapoff(2))@syncโ syncing files and memory out to disk (fsync(2),msync(2), and related calls) ๐พ@system-serviceโ a reasonable set of system calls used by common system services, excluding- the group we were vibing with in the last part (her name is up there, go look ๐ซถ) โ this one is all about any special purpose calls. It's the recommended starting point for allow-listing system calls for system services, because it contains what is typically needed by system services, but it excludes overly specific interfaces. For example, these APIs are excluded:
@clock,@mount,@swap,@reboot. โจ @timerโ system calls for scheduling operations by time, likealarm(2),timer_create(2), โฆ โฐ@knownโ all system calls defined by the kernel. This list is defined statically in systemd based on a kernel version that was available when this systemd version was released. It will become progressively more out-of-date as the kernel is updated. ๐
okay so heads up bestie, there's some lore you need ๐
Note, that as new system calls are added to the kernel, additional system calls might be added to the groups above. Contents of the sets may also change between systemd versions. In addition, the list of system calls depends on the kernel version and architecture for which systemd was compiled. Use systemd-analyze syscall-filter to list the actual list of system calls in each filter. ๐ง
Generally, allow-listing system calls (rather than deny-listing) is the safer mode of operation. It is recommended to enforce system call allow lists for all long-running system services. Specifically, the following lines are a relatively safe basic choice for the majority of system services:
[Service]
SystemCallFilter=@system-service
SystemCallErrorNumber=EPERM
not me about to say the most important thing in this whole page: allow-lists are the safer mode of operation. deny-lists are giving "I'll fix it later" and later never comes ๐ญ
Note that various kernel system calls are defined redundantly: there are multiple system calls for executing the same operation. For example, the pidfd_send_signal() system call may be used to execute operations similar to what can be done with the older kill() system call, hence blocking the latter without the former only provides weak protection. Since new system calls are added regularly to the kernel as development progresses, keeping system call deny lists comprehensive requires constant work. It is thus recommended to use allow-listing instead, which offers the benefit that new system calls are by default implicitly blocked until the allow list is updated. ๐
Also note that a number of system calls are required to be accessible for the dynamic linker to work. The dynamic linker is required for running most regular programs (specifically: all dynamic ELF binaries, which is how most distributions build packaged programs). This means that blocking these system calls (which include open(), openat() or mmap()) will make most programs typically shipped with generic distributions unusable. ๐
It is recommended to combine the file system namespacing related options with SystemCallFilter=~@mount, in order to prohibit the unit's processes to undo the mappings. Specifically these are the options PrivateTmp=, PrivateDevices=, ProtectSystem=, ProtectHome=, ProtectKernelTunables=, ProtectControlGroups=, ProtectKernelLogs=, ProtectClock=, ReadOnlyPaths=, InaccessiblePaths= and ReadWritePaths=. ๐ธ
Added in version 187.
SystemCallErrorNumber= ๐
Takes an "errno" error number (between 1 and 4095) or errno name such as EPERM, EACCES or EUCLEAN, to return when the system call filter configured with SystemCallFilter= is triggered, instead of terminating the process immediately. See errno(3) for a full list of error codes. When this setting is not used, or when the empty string or the special setting "kill" is assigned, the process will be terminated immediately when the filter is triggered. ๐ฅ
Added in version 209.
SystemCallArchitectures= ๐
Takes a space-separated list of architecture identifiers to include in the system call filter. The known architecture identifiers are the same as for ConditionArchitecture= described in systemd.unit(5), as well as x32, mips64-n32, mips64-le-n32, and the special identifier native. The special identifier native implicitly maps to the native architecture of the system (or more precisely: to the architecture the system manager is compiled for). By default, this option is set to the empty list, i.e. no filtering is applied. โจ
If this setting is used, processes of this unit will only be permitted to call native system calls, and system calls of the specified architectures. For the purposes of this option, the x32 architecture is treated as including x86-64 system calls. However, this setting still fulfills its purpose, as explained below, on x32.
System call filtering is not equally effective on all architectures. For example, on x86 filtering of network socket-related calls is not possible, due to ABI limitations โ a limitation that x86-64 does not have, however. On systems supporting multiple ABIs at the same time โ such as x86/x86-64 โ it is hence recommended to limit the set of permitted system call architectures so that secondary ABIs may not be used to circumvent the restrictions applied to the native ABI of the system. In particular, setting SystemCallArchitectures=native is a good choice for disabling non-native ABIs. ๐
System call architectures may also be restricted system-wide via the SystemCallArchitectures= option in the global configuration. See systemd-system.conf(5) for details.
Added in version 209.
SystemCallLog= ๐
Takes a space-separated list of system call names. If this setting is used, all system calls executed by the unit processes for the listed ones will be logged. If the first character of the list is "~", the effect is inverted: all system calls except the listed system calls will be logged. This feature makes use of the Secure Computing Mode 2 interfaces of the kernel ('seccomp filtering') and is useful for auditing or setting up a minimal sandboxing environment. This option may be specified more than once, in which case the filter masks are merged. If the empty string is assigned, the filter is reset, all prior assignments will have no effect. This does not affect commands prefixed with "+". ๐
Added in version 247.
๐ซถ Environment=
Sets environment variables for executed processes, bestie. Each line gets unquoted using the rules in the "Quoting" section of systemd.syntax(7) and turns into a list of variable assignments. If you need a value that has spaces or an equals sign in it, wrap the whole assignment in quotes. Variable expansion does not happen inside these strings โ the $ character is just a normal little guy here, no special meaning. Specifier expansion does happen though, see the "Specifiers" section in systemd.unit(5).
You can say this option more than once and every listed variable gets set. Same variable twice? Later one wins, earlier one is deleted from the chat. And if you assign the empty string to this option, the whole environment variable list resets and all prior assignments stop existing. Gone. Poof.
Variable names can have ASCII letters, digits, and the underscore. Names can't be empty and can't start with a digit. Values allow most characters, but non-printable characters are currently rejected, sorry babe.
Example time:
Environment="VAR1=word1 word2" VAR2=word3 "VAR3=$word 5 6"
That gives you three variables โ VAR1, VAR2, VAR3 โ with the values word1 word2, word3, $word 5 6. Slay.
See environ(7) for the details about environment variables.
โ ๏ธ okay real talk, environment variables are not suitable for passing secrets (passwords, key material, whatever) to service processes. Environment variables set for a unit are exposed to unprivileged clients via D-Bus IPC, and they're generally not understood as being data that needs protection. On top of that, environment variables get propagated down the process tree, including across security boundaries (like setuid/setgid executables), so they might leak to processes that should not have access to the secret data. UseLoadCredential=,LoadCredentialEncrypted=orSetCredentialEncrypted=(see below) to pass data to unit processes securely. periodt.
๐ EnvironmentFile=
Basically Environment=, but it reads the environment variables out of a text file instead. The text file should have newline-separated variable assignments. Empty lines, lines without an = separator, or lines starting with ; or # get ignored โ so you can use those for comments. The file must be encoded with UTF-8. Valid characters are unicode scalar values[17] other than unicode noncharacters[18], U+0000 NUL, and U+FEFF unicode byte order mark[19]. Control codes other than NUL are allowed. generous of them honestly.
In the file, an unquoted value after the = is parsed with the same backslash-escape rules as POSIX shell unquoted text[20], but unlike in a shell, interior whitespace is preserved and quotes after the first non-whitespace character are preserved. Leading and trailing whitespace (space, tab, carriage return) is discarded, but interior whitespace within the line is preserved verbatim. A line ending with a backslash gets continued onto the following one, with the newline itself discarded. A backslash \ followed by any character other than newline preserves the following character, so \\ becomes the value \.
In the file, a '-quoted value after the = can span multiple lines and contain any character verbatim other than single quote, like POSIX shell single-quoted text[21]. No backslash-escape sequences are recognized. Leading and trailing whitespace outside of the single quotes is discarded.
In the file, a "-quoted value after the = can span multiple lines, and the same escape sequences are recognized as in POSIX shell double-quoted text[22]. Backslash (\) followed by any of ", the backtick, $, or \ will preserve that character. A backslash followed by newline is a line continuation, and the newline itself is discarded. A backslash followed by any other character is ignored โ both the backslash and the following character are preserved verbatim. Leading and trailing whitespace outside of the double quotes is discarded.
The argument you pass should be an absolute filename or wildcard expression. If the file does not exist, cannot be read, or contains invalid content, the service will fail to start. To make the file optional, prefix the path with -, which causes all errors related to the file to be silently ignored. This option may be specified more than once, in which case all specified files are read. If the empty string is assigned to this option, the list of files to read is reset and all prior assignments have no effect.
๐ก heads up: shell variables like$HOMEare not expanded in this path. Use%-specifiers instead; for example,%hexpands to the user's home directory. iykyk.
The files listed with this directive will be read shortly before the process is executed (more specifically, after all processes from a previous unit state terminated โ which means you can generate these files in one unit state, and read them with this option in the next. The files are read from the file system of the service manager, before any file system changes like bind mounts take place).
Settings from these files override settings made with Environment=. If the same variable is set twice from these files, the files get read in the order they are specified and the later setting overrides the earlier setting. periodt.
๐ฟ PassEnvironment=
Passes environment variables set for the system service manager to executed processes. Takes a space-separated list of variable names. This option may be specified more than once, in which case all listed variables will be passed. If the empty string is assigned to this option, the list of environment variables to pass is reset and all prior assignments have no effect. Variables specified that aren't set for the system manager will not be passed and will be silently ignored (they just don't show up, no drama). Note that this option is only relevant for the system service manager, since system services by default do not automatically inherit any environment variables set for the service manager itself. However, in the case of the user service manager, all environment variables are passed to the executed processes anyway, hence this option is without effect for the user service manager. it's giving redundant, but only there.
Variables set for invoked processes due to this setting are subject to being overridden by those configured with Environment= or EnvironmentFile=.
Example:
PassEnvironment=VAR1 VAR2 VAR3
That passes three variables โ VAR1, VAR2, VAR3 โ with the values set for those variables in PID1.
See environ(7) for details about environment variables.
Added in version 228.
๐งน UnsetEnvironment=
Explicitly unsets environment variable assignments that would normally be passed from the service manager to invoked processes of this unit. Takes a space-separated list of variable names or variable assignments. This option may be specified more than once, in which case all listed variables/assignments will be unset. If the empty string is assigned to this option, the list of environment variables/assignments to unset is reset.
If a variable assignment is specified (that is: a variable name, followed by =, followed by its value), then any environment variable matching this precise assignment is removed. If a variable name is specified (that is, a variable name without any following = or value), then any assignment matching the variable name, regardless of its value, is removed.
Note that the effect of UnsetEnvironment= is applied as the final step when the environment list passed to executed processes is compiled. That means it may undo assignments from any configuration source, including assignments made through Environment= or EnvironmentFile=, inherited from the system manager's global set of environment variables, inherited via PassEnvironment=, set by the service manager itself (such as $NOTIFY_SOCKET and such), or set by a PAM module (in case PAMName= is used). Note that variable expansion in the command lines of ExecStart= and friends is not affected by this option, as it happens before the unsetting is applied.
See "Environment Variables in Spawned Processes" below for a description of how those settings combine to form the inherited environment. See environ(7) for general information about environment variables.
Added in version 235.
๐ LOGGING AND STANDARD INPUT/OUTPUT ๐
โจ๏ธ StandardInput=
Controls where file descriptor 0 (STDIN) of the executed processes is connected to. Takes one of null, tty, tty-force, tty-fail, data, file:path, socket or fd:name.
- If
nullis selected, standard input is connected to/dev/null, i.e. all read attempts by the process will result in immediate EOF. bye bestie, nothing to read here ๐ - If
ttyis selected, standard input is connected to a TTY (as configured byTTYPath=, see below) and the executed process becomes the controlling process of the terminal. If the terminal is already being controlled by another process, the executed process waits until the current controlling process releases the terminal. polite of it honestly. tty-forceis similar totty, but the executed process is forcefully and immediately made the controlling process of the terminal, potentially removing previous controlling processes from the terminal. it does NOT ask first. main character energy ๐ฅtty-failis similar totty, but if the terminal already has a controlling process, start-up of the executed process fails. no waiting, just vibes and rejection.- The
dataoption may be used to configure arbitrary textual or binary data to pass via standard input to the executed process. The data to pass is configured viaStandardInputText=/StandardInputData=(see below). Note that the actual file descriptor type passed (memory file, regular file, UNIX pipe, ...) might depend on the kernel and available privileges. In any case, the file descriptor is read-only, and when read returns the specified data followed by EOF. - The
file:pathoption connects a specific filesystem object to standard input. You give it an absolute path after the":", and that path can point at a regular file, a FIFO, or a special file. If you point it at an AF_UNIX socket sitting in the filesystem, then a stream socket gets connected to it. That last one is so handy for wiring the stdin of your processes straight into arbitrary system services. Heads up though: the filesystem object gets opened by the service manager before the executable is even invoked โ peep the note about the security context that happens in, down atStandardOutput=below. ๐ - The
socketoption only works in socket-activated services, and it needs the relevant socket unit file (go readsystemd.socket(5)for the deets) to haveAccept=yesset, or to specify just a single socket. Set this and standard input gets connected to the socket your service was activated from โ which is mostly there for compatibility with daemons built for the traditionalinetd(8)socket activation daemon. Note that the$LISTEN_FDS(and related) environment variables do not get passed whensocketis configured. ๐ - The
fd:nameoption connects standard input to a specific, named file descriptor handed over by a socket unit. You can put the name right in the option after a":"character (like"fd:foobar"). No name given? Then the name"stdin"is implied โ meaning"fd"is equivalent to"fd:stdin". You need at least one socket unit defining that name, provided via theSockets=option, and the file descriptor name can totally differ from the name of the socket unit that contains it. If multiple matches turn up, the first one wins. Want more on named file descriptors and how they're ordered?FileDescriptorName=insystemd.socket(5), bestie. โจ - This setting defaults to
null, unlessStandardInputText=/StandardInputData=are set โ in which case it defaults todata. ๐
โจ StandardOutput= โจ
Controls where file descriptor 1 (stdout) of the executed processes is connected to. Takes one of inherit, null, tty, journal, kmsg, journal+console, kmsg+console, file:path, append:path, truncate:path, socket or fd:name.
inheritโ duplicates the file descriptor of standard input for standard output. Copy-paste but for fds. ๐ซถnullโ connects standard output to/dev/null, aka everything written to it is justโฆ gone. Lost to the void. ๐ttyโ connects standard output to a tty (as configured viaTTYPath=, see below). If the TTY is used for output only, the executed process will not become the controlling process of the terminal, and won't fail or wait around for other processes to release the terminal. Note: if a unit tries to print multiple lines to a TTY during bootup or shutdown, there's a chance those lines get broken up by status messages.SetShowStatus()can be used to prevent this problem. Seeorg.freedesktop.systemd1(5)for details.journalโ connects standard output with the journal, which you reach viajournalctl(1). Note that everything written tokmsg(see below) is implicitly stored in the journal as well, so the specific option listed below is hence a superset of this one. (Also note that any external, additional syslog daemons receive their log data from the journal too โ hence this is the option to use when logging shall be processed with such a daemon.)kmsgโ connects standard output with the kernel log buffer, which you reach viadmesg(1), in addition to the journal. The journal daemon might be configured to send all logs tokmsganyway, in which case this option is no different fromjournal.journal+consoleandkmsg+consoleโ work in a similar way as the two options above, but copy the output to the system console as well. Two places at once, main character energy. ๐- The
file:pathoption connects a specific filesystem object to standard output. The semantics are similar to the same option ofStandardInput=, see above. Ifpathrefers to a regular file on the filesystem, it's opened (created if it doesn't exist yet, using privileges of the user executing the systemd process) for writing at the beginning of the file, but without truncating it. If standard input and output are directed to the same file path, it's opened only once โ for reading as well as writing โ and duplicated. This is particularly useful when the specified path refers to an AF_UNIX socket in the filesystem, since in that case only a single stream connection is created for both input and output. ๐ append:pathโ similar tofile:pathabove, but it opens the file in append mode.truncate:pathโ similar tofile:pathabove, but it truncates the file when opening it. For units with multiple command lines โ e.g.Type=oneshotservices with multipleExecStart=, or services withExecCondition=,ExecStartPre=orExecStartPost=โ the output file is reopened and therefore re-truncated for each command line. If the output file gets truncated while another process still has the file open, e.g. by anExecReload=running concurrently with anExecStart=, and the other process keeps writing to the file without adjusting its offset, then the space between the file pointers of the two processes may be filled with NUL bytes, producing a sparse file. Sotruncate:pathis typically only useful for units where only one process runs at a time โ like services with a singleExecStart=and noExecStartPost=,ExecReload=,ExecStop=or similar.
Fr fr, watch out on that truncate thing. Concurrent writers + truncation = sparse file chaos, and nobody wants that. ๐ญ
- Note that for
file:path,append:pathandtruncate:pathofStandardOutput=,StandardError=orStandardInput=, the target path is opened by the service manager before the executable is invoked โ and hence beforeSELinuxContext=or any policy-defined transition takes effect. The resulting descriptor is then inherited across that transition into the unit's own domain. socketโ connects standard output to a socket acquired via socket activation. The semantics are similar to the same option ofStandardInput=, see above.- The
fd:nameoption connects standard output to a specific, named file descriptor provided by a socket unit. A name may be specified as part of this option, following a":"character (e.g."fd:foobar"). If no name is specified, the name"stdout"is implied (i.e."fd"is equivalent to"fd:stdout"). At least one socket unit defining the specified name must be provided via theSockets=option, and the file descriptor name may differ from the name of its containing socket unit. If multiple matches are found, the first one will be used. SeeFileDescriptorName=insystemd.socket(5)for more details about named descriptors and their ordering. ๐
If the standard output (or error output, see below) of a unit is connected to the journal or the kernel log buffer, the unit will implicitly gain a dependency of type After= on systemd-journald.socket (also see the "Implicit Dependencies" section above). Also note that, in this case, stdout (or stderr, see below) will be an AF_UNIX stream socket, and not a pipe or FIFO that can be reopened. This means when executing shell scripts the construct echo "hello" > /dev/stderr for writing text to stderr will not work. To mitigate this, use the construct echo "hello" >&2 instead, which is mostly equivalent and avoids this pitfall. ๐
If StandardInput= is set to one of tty, tty-force, tty-fail, socket, or fd:name, this setting defaults to inherit.
In other cases, this setting defaults to the value set with DefaultStandardOutput= in systemd-system.conf(5), which defaults to journal. Note that setting this parameter might result in additional dependencies being added to the unit (see above).
โจ StandardError= โจ
Controls where file descriptor 2 (stderr) of the executed processes is connected to. The available options are identical to those of StandardOutput=, with some exceptions: if set to inherit, the file descriptor used for standard output is duplicated for standard error, while fd:name will use a default file descriptor name of "stderr". ๐ฆ
This setting defaults to the value set with DefaultStandardError= in systemd-system.conf(5), which defaults to inherit. Note that setting this parameter might result in additional dependencies being added to the unit (see above).
โจ StandardInputText=, StandardInputData= โจ
Configures arbitrary textual or binary data to pass via file descriptor 0 (STDIN) to the executed processes. These settings have no effect unless StandardInput= is set to data (which is the default if StandardInput= is not set otherwise, but StandardInputText=/StandardInputData= is). Use this option to embed process input data directly in the unit file. It's giving "data, but make it a unit file". ๐
StandardInputText= accepts arbitrary textual data. C-style escapes for special characters as well as the usual "%"-specifiers are resolved. Each time this setting is used, the specified text is appended to the per-unit data buffer, followed by a newline character (so every use appends a new line to the end of the buffer). Note that leading and trailing whitespace of lines configured with this option is removed. If an empty line is specified, the buffer is cleared โ hence, in order to insert an empty line, add an additional "\n" to the end or beginning of a line.
StandardInputData= accepts arbitrary binary data, encoded in Base64[23]. No escape sequences or specifiers are resolved. Any whitespace in the encoded version is ignored during decoding.
Note that StandardInputText= and StandardInputData= operate on the same data buffer, and may be mixed in order to configure both binary and textual data for the same input stream. The textual or binary data is joined strictly in the order the settings appear in the unit file. Assigning an empty string to either will reset the data buffer. ๐ฅ
Please keep in mind that in order to maintain readability, long unit file settings may be split into multiple lines, by suffixing each line (except for the last) with a "\" character (see systemd.unit(5) for details). This is particularly useful for large data configured with these two options. Example:
StandardInput=data
StandardInputData=V2XigLJyZSBubyBzdHJhbmdlcnMgdG8gbG92ZQpZb3Uga25vdyB0aGUgcnVsZXMgYW5kIHNvIGRv \
IEkKQSBmdWxsIGNvbW1pdG1lbnQncyB3aGF0IEnigLJtIHRoaW5raW5nIG9mCllvdSB3b3VsZG4n \
dCBnZXQgdGhpcyBmcm9tIGFueSBvdGhlciBndXkKSSBqdXN0IHdhbm5hIHRlbGwgeW91IGhvdyBJ \
J20gZmVlbGluZwpHb3R0YSBtYWtlIHlvdSB1bmRlcnN0YW5kCgpOZXZlciBnb25uYSBnaXZlIHlv \
dSB1cApOZXZlciBnb25uYSBsZXQgeW91IGRvd24KTmV2ZXIgZ29ubmEgcnVuIGFyb3VuZCBhbmQg \
ZGVzZXJ0IHlvdQpOZXZlciBnb25uYSBtYWtlIHlvdSBjcnkKTmV2ZXIgZ29ubmEgc2F5IGdvb2Ri \
eWUKTmV2ZXIgZ29ubmEgdGVsbCBhIGxpZSBhbmQgaHVydCB5b3UK
...
okay so that's a whole base64 blob and I am not decoding it for you, that's between you and your journal bestie ๐
the takeaway is that StandardInputData= eats raw base64 and you keep it exactly as written โ \ line continuations, indentation, all of it, no cap.
Added in version 236.
LogLevelMax=โ sets the maximum log level for log messages generated by this unit. Takes a syslog log level, one ofemerg(lowest log level, only highest priority messages),alert,crit,err,warning,notice,info,debug(highest log level, also lowest priority messages). Seesyslog(3)for the deets. By default the maximum log level is not overridden.
So what's the vibe here? You can use this to tell the logging system "bestie, drop anything from this service above this level". Like, set LogLevelMax=info to turn off debug logging of a unit that will literally not stop yapping. Or flip it the other way: use it to turn on extra logging about one specific unit from the system or user manager processes โ LogLevelMax=debug โ without changing the global log level for the whole system or user manager. Obsessed honestly.
Note that the configured level hits any log message written by any of the processes belonging to this unit, plus any log messages the system or user manager processes write in reference to this unit, sent via any supported logging protocol. The override gets applied early in the logging pipeline, before any further processing happens. And plot twist: messages that make it through this filter might still get dropped by filters applied later in the logging subsystem. For example MaxLevelStore= configured in journald.conf(5) might stop messages of higher log levels from being stored on disk, even though the per-unit LogLevelMax= said it was fine to process them. ๐
Added in version 236.
LogExtraFields=โ configures extra log metadata fields to include in all log records generated by processes associated with this unit, includingsystemd. This setting takes one or more journal field assignments in the format"FIELD=VALUE"separated by whitespace. Seesystemd.journal-fields(7)for what a journal field even is. Even though the underlying journal implementation allows binary field values, this setting only accepts valid UTF-8 values. Want a space character inside a journal field value? Enclose the whole assignment in double quotes ("). The usual specifiers get expanded in all assignments (see below). And ngl this setting isn't just for sticking extra metadata onto a unit's log records โ since all fields and values are indexed, you can also use it to do cross-unit log record matching. Assign an empty string to reset the list.
Note that this functionality is currently only available in system services, not in per-user services. ๐
Added in version 236.
LogRateLimitIntervalSec=,LogRateLimitBurst=โ configures the rate limiting applied to log messages generated by this unit. If, in the time interval defined byLogRateLimitIntervalSec=, more messages than specified inLogRateLimitBurst=are logged by a service, all further messages within the interval are dropped until the interval is over. A message about the number of dropped messages is generated. The time specification forLogRateLimitIntervalSec=may be given in these units:"s","min","h","ms","us". Seesystemd.time(7)for details. The default settings are set byRateLimitIntervalSec=andRateLimitBurst=configured injournald.conf(5). Note that this only applies to log messages that are processed by the logging subsystem, i.e. bysystemd-journald.service(8). Which means: if you connect a service's stderr directly to a file viaStandardOutput=file:...or a similar setting, the rate limiting will not be applied to messages written that way โ but it will still be enforced for messages generated viasyslog(3)and similar functions. Sneaky.
Added in version 240.
LogFilterPatterns=โ define an extended regular expression to filter log messages based on theMESSAGE=field of the structured message. If the first character of the pattern is"~", log entries matching the pattern should be discarded. This option takes a single pattern as an argument, but you can use it multiple times to build a list of allowed and denied patterns. If the empty string is assigned, the filter is reset, and all prior assignments will have no effect.
Now, because the "~" character is what defines denied patterns, you have to replace it with "\x7e" if you want to allow a message that literally starts with "~". For example, "~foobar" would add a pattern matching "foobar" to the deny list, while "\x7efoobar" would add a pattern matching "~foobar" to the allow list. iykyk ๐ซถ
Log messages get tested against denied patterns (if any), then against allowed patterns (if any). If a log message matches any of the denied patterns, it's discarded immediately, no considering the allowed patterns, no second chances. Remaining log messages are tested against the allowed patterns. Messages matching none of the allowed patterns are discarded. If no allowed patterns are defined at all, then all messages are processed directly after going through the denied filters.
Filtering is based on the unit for which LogFilterPatterns= is defined, meaning log messages coming from systemd(1) about the unit are not taken into account. Filtered log messages will not be forwarded to traditional syslog daemons, the kernel log buffer (kmsg), the systemd console, or sent as wall messages to all logged-in users.
Note that this functionality is currently only available in system services, not in per-user services.
Added in version 253.
LogNamespace=โ run the unit's processes in the specified journal namespace. Expects a short user-defined string identifying the namespace. If you don't use it, the processes of the service run in the default journal namespace, i.e. their log stream is collected and processed bysystemd-journald.service. If you do use it, any log data generated by processes of this unit (regardless of whether it comes viasyslog(), journal native logging, or stdout/stderr logging) is collected and processed by an instance of thesystemd-journald@.servicetemplate unit, which manages the specified namespace. The log data is stored in a data store independent from the default log namespace's data store. Seesystemd-journald.service(8)for the full lore on journal namespaces.
Internally, journal namespaces are implemented through Linux mount namespacing and over-mounting the directory that contains the relevant AF_UNIX sockets used for logging in the unit's mount namespace. And since mount namespaces are involved, this setting disconnects propagation of mounts from the unit's processes to the host โ same energy as how ReadOnlyPaths= and the similar settings described above work. So journal namespaces may hence not be used for services that need to establish mount points on the host. Know that going in.
When this option is used, the unit automatically gains ordering and requirement dependencies on the two socket units associated with the systemd-journald@.service instance, so they get established automatically before the unit starts up. Also note: when this option is used, log output of this service does not show up in the regular journalctl(1) output, unless you pass the --namespace= option. ๐
This option is only available for system services and is not supported for services running in per-user instances of the service manager.
Added in version 245.
SyslogIdentifier=โ sets the process name ("syslog tag") to prefix log lines sent to the logging system or the kernel log buffer with. If not set, it defaults to the process name of the executed process. This option is only useful whenStandardOutput=orStandardError=are set tojournalorkmsg(or to the same settings in combination with+console), and it only applies to log messages written to stdout or stderr.SyslogFacility=โ sets the syslog facility identifier to use when logging. One ofkern,user,mail,daemon,auth,syslog,lpr,news,uucp,cron,authpriv,ftp,local0,local1,local2,local3,local4,local5,local6orlocal7. Seesyslog(3)for details. This option is only useful whenStandardOutput=orStandardError=are set tojournalorkmsg(or to the same settings in combination with+console), and only applies to log messages written to stdout or stderr. Defaults todaemon.SyslogLevel=โ the default syslog log level to use when logging to the logging system or the kernel log buffer. One ofemerg,alert,crit,err,warning,notice,info,debug. Seesyslog(3)for details. This option is only useful whenStandardOutput=orStandardError=are set tojournalorkmsg(or to the same settings in combination with+console), and only applies to log messages written to stdout or stderr. Note that individual lines output by executed processes may be prefixed with a different log level, which can be used to override the default log level specified here. The interpretation of these prefixes may be disabled withSyslogLevelPrefix=, see below. For details, seesd-daemon(3). Defaults toinfo.SyslogLevelPrefix=โ takes a boolean argument. If true, andStandardOutput=orStandardError=are set tojournalorkmsg(or to the same settings in combination with+console), log lines written by the executed process that are prefixed with a log level will be processed with this log level set, but the prefix removed. If set to false, the interpretation of these prefixes is disabled and the logged lines are passed on as-is. This only applies to log messages written to stdout or stderr. For details about this prefixing, seesd-daemon(3). Defaults totrue.TTYPath=โ picks the terminal device node to hook up when stdin, stdout or stderr is connected to a TTY (see above, bestie). Defaults to/dev/console.TTYReset=โ resets the terminal device fromTTYPath=before and after the thing runs. Heads up tho: this does not wipe the screen (scroll down toTTYVTDisallocate=if that's the vibe you want). Defaults to"no".TTYVHangup=โ kicks off every client that has the terminal device fromTTYPath=open, before and after execution. Defaults to"no".TTYColumns=,TTYRows=โ set the size of the TTY fromTTYPath=. If you leave these unset or set them to the empty string, systemd tries to sniff the terminal screen dimensions via ANSI sequences, and if that flops it just uses the kernel defaults (which is typically80x24). โจ
Added in version 250.
TTYVTDisallocate=โ if the terminal device fromTTYPath=is a virtual console terminal, it tries to deallocate that TTY before and after execution. That's the one that actually guarantees the screen and the scrollback buffer get cleared. If the terminal device is literally any other kind of TTY, it instead attempts to clear the screen via ANSI sequences. Defaults to"no".
๐ CREDENTIALS ๐
LoadCredential=ID[:PATH],LoadCredentialEncrypted=ID[:PATH]โ hands a credential to the unit. So what even is a credential? It's a limited-size binary or textual object that you can pass to unit processes. The main character use case is passing cryptographic keys (public and private) or certificates, user account info, or identity info from the host into services โ but fr, you can use them to pass any kind of limited-size info to a service, no gatekeeping. The data shows up to the unit's processes through the file system, at a read-only location that (if it's possible and permitted) is backed by non-swappable memory. Only the user associated with the unit gets at it, via theUser=/DynamicUser=settings (plus the superuser, obviously). When it's available, the location of credentials gets exported as the$CREDENTIALS_DIRECTORYenvironment variable to the unit's processes. ๐ซถ
LoadCredential= takes a textual ID to use as the name for a credential, plus a file system path, separated by a colon. The ID has to be a short ASCII string that works as a filename in the filesystem, and you can pick it freely yourself. If the path you give is absolute, it's opened as a regular file and the credential data is read out of it. If that absolute path points at an AF_UNIX stream socket in the file system, a connection is made to it (once, at process invocation) and the credential data is read off the connection โ which is a super easy IPC integration point for dynamically transferring credentials from other services. ๐ฆ
If the path you give is not absolute and it itself qualifies as a valid credential identifier, systemd tries to find a credential that the service manager itself received under that name โ which you can use to propagate credentials from an invoking environment (like a container manager that invoked the service manager) into a service. If no matching passed credential is found, the system service manager goes hunting through the directories /etc/credstore/, /run/credstore/ and /usr/lib/credstore/ for files under the credential's name โ which is exactly why those are the recommended locations for credential data on disk. If LoadCredentialEncrypted= is in play, /run/credstore.encrypted/, /etc/credstore.encrypted/, and /usr/lib/credstore.encrypted/ get searched too. The per-user service manager searches $XDG_CONFIG_HOME/credstore/, $XDG_RUNTIME_DIR/credstore/, $HOME/.local/lib/credstore/ instead (plus the counterparts ending with .../credstore.encrypted/). You can use the systemd-path(1) tool to query the precise credential store search path. ๐
If the file system path is omitted entirely, it's chosen identical to the credential name โ i.e. it's the terse way to declare credentials to inherit from the service manager or the credstore directories into a service. This option can be used multiple times, and each time it defines an additional credential to pass to the unit.
Ngl, important: if the path isn't specified, or a valid credential identifier is given (those two cases right above), a missing credential is not considered fatal.
If an absolute path referring to a directory is specified, every file in that directory (recursively) gets loaded as a separate credential. The ID for each credential is the provided ID suffixed with _$FILENAME (e.g., "Key_file1"). When loading from a directory, symlinks are ignored, no cap. ๐ซ๐
The contents of the file/socket can be arbitrary binary or textual data โ newline characters and NUL bytes fully welcome.
LoadCredentialEncrypted= is identical to LoadCredential=, except the credential data gets decrypted and authenticated before being passed on to the executed processes. Specifically, the referenced path should point at a file or socket with an encrypted credential, as implemented by systemd-creds(1). This credential is loaded, decrypted, authenticated, and then passed to the application in plaintext form โ the exact same way a regular credential specified via LoadCredential= would be. A credential configured this way may be symmetrically encrypted/authenticated with a secret key derived from the system's TPM2 security chip, or with a secret key stored in /var/lib/systemd/credential.secret, or with both. Using encrypted and authenticated credentials is a whole security glow-up: credentials aren't stored in plaintext and only get authenticated and decrypted into plaintext the moment a service that needs them is started. Plus, credentials can be bound to the local hardware and installations, so they can't easily be analyzed offline, or generated externally. See systemd.resource-control(5) for the details about DevicePolicy= or DeviceAllow=. ๐ฅ
Note that encrypted credentials targeted for services of the per-user service manager must be encrypted with systemd-creds encrypt --user, and those for the system service manager without the --user switch. Encrypted credentials are always targeted to a specific user or to the system as a whole, and it's ensured that per-user service managers cannot decrypt secrets intended for the system or for other users. ๐
The credential files/IPC sockets have to be accessible to the service manager, but they do not have to be directly accessible to the unit's processes: the credential data is read and copied into separate, read-only copies for the unit that are accessible to appropriately privileged processes. This is especially iconic in combination with DynamicUser=, because that way privileged data can be made available to processes running under a dynamic UID (i.e. not a previously known one) without having to open up access to all users. ๐
In order to reference the path a credential may be read from within an ExecStart= command line, use "${CREDENTIALS_DIRECTORY}/mycred", e.g. "ExecStart=cat ${CREDENTIALS_DIRECTORY}/mycred". In order to reference the path a credential may be read from within an Environment= line, use %d/mycred, e.g. "Environment=MYCREDPATH=%d/mycred". For system services the path may also be referenced as "/run/credentials/UNITNAME" in cases where no interpolation is possible, e.g. configuration files of software that doesn't yet support credentials natively. That said, $CREDENTIALS_DIRECTORY is considered the primary interface to look for credentials, since it also works for user services. ๐
Currently, an accumulated credential size limit of 1 MB per unit is enforced.
The service manager itself may receive system credentials that can be propagated to services from a hosting container manager or VM hypervisor. See the Container Interface[24] documentation for details about the former. For the latter, pass DMI/SMBIOS[25] OEM string table entries (field type 11) with a prefix of "io.systemd.credential:" or "io.systemd.credential.binary:". In both cases a key/value pair separated by "=" is expected. In the latter case, the right-hand side is Base64 decoded when parsed (thus permitting binary data to be passed in). Example qemu[26] switch: "-smbios type=11,value=io.systemd.credential:xx=yy", or "-smbios type=11,value=io.systemd.credential.binary:rick=TmV2ZXIgR29ubmEgR2l2ZSBZb3UgVXA=". Alternatively, use the qemu "fw_cfg" node "opt/io.systemd.credentials/". Example qemu switch: "-fw_cfg name=opt/io.systemd.credentials/mycred,string=supersecret". They may also be passed from the UEFI firmware environment via systemd-stub(7), from the initrd (see systemd(1)), or be specified on the kernel command line using the "systemd.set_credential=" and "systemd.set_credential_binary=" switches (see systemd(1) โ this is not recommended, since unprivileged userspace can read the kernel command line ๐).
If referencing an AF_UNIX stream socket to connect to, the connection will originate from an abstract namespace socket that includes information about the unit and the credential ID in its socket name. Use getpeername(2) to query this information. The returned socket name is formatted as NUL RANDOM "/unit/" UNIT "/" ID โ i.e. a NUL byte (as required for abstract namespace socket names), followed by a random string (consisting of alphadecimal characters), followed by the literal string "/unit/", followed by the requesting unit name, followed by the literal character "/", followed by the textual credential ID requested. Example: "\0adf9d86b6eda275e/unit/foobar.service/credx" in case the credential "credx" is requested for a unit "foobar.service". This functionality is useful for using a single listening socket to serve credentials to multiple consumers. ๐
For further information see System and Service Credentials[27] documentation.
Added in version 247.
ImportCredential=GLOBโ bestie, this one imports credentials from the service manager itself ๐ You give it a credential name, and systemd goes hunting for a credential that the manager itself received under that name โ which is how you propagate credentials from an invoking environment (like, say, a container manager that invoked the service manager) into a service. If the credential name is a glob, all credentials matching the glob get passed to the unit. Matching credentials are searched for in the system credentials, the encrypted system credentials, and under/etc/credstore/,/run/credstore/,/usr/lib/credstore/,/run/credstore.encrypted/,/etc/credstore.encrypted/, and/usr/lib/credstore.encrypted/โ in that order. When multiple credentials of the same name are found, the first one found is used. Periodt. โจ
The globbing expression implements a restrictive subset of glob(7): only a single trailing "*" wildcard may be specified. Both "?" and "[]" wildcards are not permitted, nor are "*" wildcards anywhere except at the end of the glob expression. So no wilding out in the middle, babe ๐
Optionally, the credential name or glob may be followed by a colon followed by a rename pattern. If specified, all credentials matching the credential name or glob are renamed according to the given pattern. For example, if "ImportCredential=my.original.cred:my.renamed.cred" is specified, the service manager will read the "my.original.cred" credential and make it available as the "my.renamed.cred" credential to the service. Similarly, if "ImportCredential=my.original.*:my.renamed." is specified, the service manager will read all credentials starting with "my.original." and make them available as "my.renamed.xxx" to the service. Glow-up complete ๐
If ImportCredential= is specified multiple times and multiple credentials end up with the same name after renaming, the first one is kept and later ones are dropped.
When multiple credentials of the same name are found, credentials found by LoadCredential= and LoadCredentialEncrypted= take priority over credentials found by ImportCredential=.
Note that if decryption or authentication of a credential picked up as a result ofImportCredential=fails it will be skipped gracefully (a warning is generated, but the credential will not be made available to the invoked service). This is different for those configured viaSetCredentialEncrypted=/LoadCredentialEncrypted=, where failed decryption/authentication will result in service failure. ๐ญ
Added in version 254.
SetCredential=ID:VALUE,SetCredentialEncrypted=ID:VALUEโ okay soSetCredential=is basicallyLoadCredential='s cousin who shows up with the data already in hand ๐ซถ It accepts a literal value to use as data for the credential, instead of a file system path to read the data from.
Do not use this option for data that is supposed to be secret, as it is accessible to unprivileged processes via IPC. It's only safe to use this for user IDs, public key material and similar non-sensitive data. For everything else use LoadCredential=. ๐ฅ
In order to embed binary data into the credential data use C-style escaping (i.e. "\n" to embed a newline, or "\x00" to embed a NUL byte).
The SetCredentialEncrypted= setting is identical to SetCredential= but expects an encrypted credential in literal form as value. This allows embedding confidential credentials securely directly in unit files ๐ Use systemd-creds(1)' -p switch to generate suitable SetCredentialEncrypted= lines directly from plaintext credentials. For further details see LoadCredentialEncrypted= above.
When multiple credentials of the same name are found, credentials found by LoadCredential=, LoadCredentialEncrypted= and ImportCredential= take priority over credentials found by SetCredential=. As such, SetCredential= will act as default if no credentials are found by any of the former. In this case, not being able to retrieve the credential from the path specified in LoadCredential= or LoadCredentialEncrypted= is not considered fatal. Very forgiving of it, we stan ๐
Added in version 247.
๐ SYSTEM V COMPATIBILITY ๐
Old-school but iconic, no cap. These settings exist so your unit can cosplay as a classic SysV-era service ๐
UtmpIdentifier=โ takes a four character identifier string for anutmp(5)andwtmpentry for this service. This should only be set for services such asgettyimplementations (such asagetty(8)) whereutmp/wtmpentries must be created and cleared before and after execution, or for services that shall be executed as if they were run by agettyprocess (see below). If the configured string is longer than four characters, it is truncated and the terminal four characters are used. This setting interprets%Istyle string replacements. This setting is unset by default, i.e. noutmp/wtmpentries are created or cleaned up for this service. โจUtmpMode=โ takes one of"init","login"or"user". IfUtmpIdentifier=is set, controls which type ofutmp(5)/wtmpentries for this service are generated. This setting has no effect unlessUtmpIdentifier=is set too. If"init"is set, only anINIT_PROCESSentry is generated and the invoked process must implement agetty-compatibleutmp/wtmplogic. If"login"is set, first anINIT_PROCESSentry, followed by aLOGIN_PROCESSentry is generated. In this case, the invoked process must implement alogin(1)-compatibleutmp/wtmplogic. If"user"is set, first anINIT_PROCESSentry, then aLOGIN_PROCESSentry and finally aUSER_PROCESSentry is generated. In this case, the invoked process may be any process that is suitable to be run as session leader. Defaults to"init". ๐ฆ
Added in version 225.
๐ธ ENVIRONMENT VARIABLES IN SPAWNED PROCESSES ๐ธ
Processes started by the service manager are executed with an environment variable block assembled from multiple sources. Processes started by the system service manager generally do not inherit environment variables set for the service manager itself (but this may be altered via PassEnvironment=), but processes started by the user service manager instances generally do inherit all environment variables set for the service manager itself. The duality of man, honestly ๐ญ
For each invoked process the list of environment variables set is compiled from the following sources:
- Variables globally configured for the service manager, using the
DefaultEnvironment=setting insystemd-system.conf(5), the kernel command line optionsystemd.setenv=understood bysystemd(1), or viasystemctl(1)set-environmentverb. - Variables defined by the service manager itself (see the list below).
- Variables set in the service manager's own environment variable block (subject to
PassEnvironment=for the system service manager). - Variables set via
Environment=in the unit file. - Variables read from files specified via
EnvironmentFile=in the unit file. - Variables set by any PAM modules in case
PAMName=is in effect, cf.pam_env(8).
If the same environment variable is set by multiple of these sources, the later source โ according to the order of the list above โ wins. Note that as the final step all variables listed in UnsetEnvironment= are removed from the compiled environment variable list, immediately before it is passed to the executed process. This unsetting happens after variable expansion in the command lines of ExecStart= and friends is performed, hence command line expansion is not affected by UnsetEnvironment=. Sneaky little ordering detail, iykyk ๐
The general philosophy is to expose a small curated list of environment variables to processes. Services started by the system manager (PID 1) will be started, without additional service-specific configuration, with just a few environment variables. The user manager inherits environment variables as any other system service, but in addition may receive additional environment variables from PAM, and, typically, additional imported variables when the user starts a graphical session. It is recommended to keep the environment blocks in both the system and user managers lean. Importing all variables inherited by the graphical session or by one of the user shells is strongly discouraged. ๐ซ
Hint:systemd-run -P envandsystemd-run --user -P envprint the effective system and user service environment blocks. Go peek bestie, it's free real estate ๐
๐ Environment Variables Set or Propagated by the Service Manager ๐
The following environment variables are propagated by the service manager or generated internally for each invoked process:
$PATHโ colon-separated list of directories to use when launching executables.systemduses a fixed value of"/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin"in the system manager. In case of the user manager, a different path may be configured by the distribution. It is recommended to not rely on the order of entries, and have only one program with a given name in$PATH.
Added in version 208.
$LANGโ locale. Can be set inlocale.conf(5)or on the kernel command line (seesystemd(1)andkernel-command-line(7)).
Added in version 208.
$USER,$LOGNAME,$HOME,$SHELLโ user name (twice), home directory, and the login shell.$USERis set unconditionally, while$HOME,$LOGNAME, and$SHELLare only set for the units that haveUser=set andSetLoginEnvironment=unset or set totrue. For user services, these variables are typically inherited from the user manager itself. Seepasswd(5).
Added in version 208.
$INVOCATION_IDโ contains a randomized, unique 128-bit ID identifying each runtime cycle of the unit, formatted as 32 character hexadecimal string. A new ID is assigned each time the unit changes from an inactive state into an activating or active state, and may be used to identify this specific runtime cycle, in particular in data stored offline, such as the journal. The same ID is passed to all processes run as part of the unit. It's giving โจtraceabilityโจ
Added in version 232.
$XDG_RUNTIME_DIRโ the directory to use for runtime objects (such as IPC objects) and volatile state. Set for all services run by the usersystemdinstance, as well as any system services that usePAMName=with a PAM stack that includespam_systemd. See below andpam_systemd(8)for more information.
Added in version 208.
$RUNTIME_DIRECTORY,$STATE_DIRECTORY,$CACHE_DIRECTORY,$LOGS_DIRECTORY,$CONFIGURATION_DIRECTORYโ absolute paths to the directories defined withRuntimeDirectory=,StateDirectory=,CacheDirectory=,LogsDirectory=, andConfigurationDirectory=when those settings are used. Your unit's little directory entourage ๐
Added in version 244.
$CREDENTIALS_DIRECTORY
An absolute path to the per-unit directory that holds all the credentials you set up with ImportCredential= / LoadCredential= / SetCredential=. Bestie, this directory is read-only, it lives in unswappable memory (if the system supports it and actually lets us do it), and only the UID tied to the unit via User= or DynamicUser= can touch it โ plus the superuser, obviously. ๐
Added in version 247.
$TMPDIR
Set to /tmp when PrivateTmp=disconnected is specified together with DefaultDependencies=no, and without RootDirectory=/RootImage= and without RequiresMountsFor=/WantsMountsFor= for /var/. Go peek at the explanation for PrivateTmp= above for the whole tea. ๐
Added in version 258.
$MAINPID
The UNIX process ID (PID) of the unit's main process, if it's known. Heads up: this one is only set for control processes, like the ones kicked off by ExecReload= and friends. ๐
Added in version 209.
$MAINPIDFDID
The 64bit inode ID of the file descriptor that pidfd_open(3) hands back for the main process (if that's supported). Same deal as above โ only set for control processes invoked by ExecReload= and similar.
Added in version 258.
$MANAGERPID
The PID of the per-user systemd service manager instance, set for processes it spawns. ๐ง
Added in version 208.
$MANAGERPIDFDID
The pidfd_open() inode ID (see right above) of the per-user systemd service manager instance, set for processes it spawns.
Added in version 258.
$LISTEN_FDS, $LISTEN_PID, $LISTEN_PIDFDID, $LISTEN_FDNAMES
All the info about file descriptors passed to a service for socket activation. Go read sd_listen_fds(3). โจ
Added in version 208.
$NOTIFY_SOCKET
The socket that sd_notify() talks to. See sd_notify(3). ๐ซถ
Added in version 229.
$WATCHDOG_PID, $WATCHDOG_USEC
Info about watchdog keep-alive notifications. See sd_watchdog_enabled(3).
Added in version 229.
$SYSTEMD_EXEC_PID
The PID of the unit process (like, the process invoked by ExecStart=). The child process can use this to figure out whether it was invoked directly by the service manager or indirectly as a child of another process โ just compare this value against the current PID. It's the same kind of scheme sd_listen_fds(3) uses with $LISTEN_PID and $LISTEN_FDS. ๐
Added in version 248.
$TERM
Terminal type. Only set for units that are actually connected to a terminal (StandardInput=tty, StandardOutput=tty, or StandardError=tty). See termcap(5). ๐ฅ๏ธ
Added in version 209.
$LOG_NAMESPACE
Holds the name of the selected logging namespace when the LogNamespace= service setting is in play.
Added in version 246.
$JOURNAL_STREAM
If the standard output or standard error output of the executed processes are connected to the journal (say, by setting StandardError=journal), $JOURNAL_STREAM contains the device and inode numbers of the connection file descriptor โ formatted in decimal, separated by a colon (:). This is the glow-up that lets invoked processes safely detect whether their stdout or stderr is hooked up to the journal. ๐
So like โ compare the device and inode numbers of the file descriptors against the values in the environment variable to figure out whether the process output is still connected to the journal.
โ ๏ธ Real talk: it's generally not enough to just check whether $JOURNAL_STREAM is set at all, because services might invoke external processes that replace their stdout or stderr without unsetting the environment variable. ๐
If both the standard output and standard error of the executed processes are connected to the journal via a stream socket, this environment variable will carry information about the standard error stream, since that's usually the preferred destination for log data. (And note โ typically the same stream is used for both stdout and stderr, so very likely the environment variable's device and inode info matches both stream file descriptors anyway.) ๐
This variable is mainly useful so services can optionally upgrade their log protocol to the native journal protocol (using sd_journal_print(3) and other functions) when their stdout or stderr is connected to the journal regardless โ which means structured metadata can ride along with the logged messages. Iconic behaviour, honestly.
Added in version 231.
$SERVICE_RESULT
Only used for the service unit type. This environment variable gets passed to all ExecStop= and ExecStopPost= processes, and it encodes the service "result". Currently, these are the values that are defined:
"success"โ The service ran successfully and exited cleanly. ๐"protocol"โ A protocol violation occurred: the service did not take the steps required by its unit configuration (specifically, what's configured in itsType=setting)."timeout"โ One of the steps timed out. โฐ"exit-code"โ Service process exited with a non-zero exit code; see$EXIT_CODEbelow for the actual exit code returned."signal"โ A service process was terminated abnormally by a signal, without dumping core. See$EXIT_CODEbelow for the actual signal causing the termination."core-dump"โ A service process terminated abnormally with a signal and dumped core. See$EXIT_CODEbelow for the signal causing the termination."watchdog"โ Watchdog keep-alive ping was enabled for the service, but the deadline was missed."exec-condition"โ Service did not run becauseExecCondition=failed (that is, its command exited with an exit status of 1 through 254, inclusive)."oom-kill"โ A service process was terminated by the Out-Of-Memory (OOM) killer."start-limit-hit"โ A start limit was defined for the unit and it was hit, causing the unit to fail to start. Seesystemd.unit(5)'sStartLimitIntervalSec=andStartLimitBurst=for the details."resources"โ A catch-all condition, for when a system operation failed.
This environment variable is genuinely useful for monitoring whether a service failed or terminated successfully. And even though it's available in both ExecStop= and ExecStopPost=, it's usually a better call to put monitoring tools in the latter โ because the former is only invoked for services that managed to start up correctly, while the latter covers both the services that failed during start-up and the ones that failed during runtime. ๐
Added in version 232.
$EXIT_CODE, $EXIT_STATUS ๐
okay so these two? service units only, bestie, don't come for me. They get passed to every single ExecStop= and ExecStopPost= process, and they carry the exit status/code tea from the main process of the service. For the precise definition of exit code and status, go read wait(2) โ that's the receipts.
$EXIT_CODEis literally one of"exited","killed","dumped". That's the whole list, no notes.$EXIT_STATUSholds the numeric exit code, formatted as a string, if$EXIT_CODEis"exited". In all other cases it's the signal name instead.
ngl there's a catch ๐ซถ these environment variables are only set if the service manager actually succeeded in starting and identifying the main process of the service. No main process identified, no variables. Periodt.
๐ Table 7. Summary of possible service result variable values
k so this is the whole vibe check, row by row, $SERVICE_RESULT โ $EXIT_CODE โ $EXIT_STATUS:
"success"โ"killed"โ"HUP","INT","TERM","PIPE""success"โ"exited"โ"0""protocol"โ not set โ not set"protocol"โ"exited"โ"0""timeout"โ"killed"โ"TERM","KILL""timeout"โ"exited"โ"0","1","2","3", ...,"255""exit-code"โ"exited"โ"1","2","3", ...,"255""signal"โ"killed"โ"HUP","INT","KILL", ..."core-dump"โ"dumped"โ"ABRT","SEGV","QUIT", ..."watchdog"โ"dumped"โ"ABRT""watchdog"โ"killed"โ"TERM","KILL""watchdog"โ"exited"โ"0","1","2","3", ...,"255""exec-condition"โ"exited"โ"1","2","3","4", ...,"254""oom-kill"โ"killed"โ"TERM","KILL""start-limit-hit"โ not set โ not set"resources"โ any of the above โ any of the above
๐จ okay so the fine print, and this one's important: the process may also be terminated by a signal that systemd did not send. Like, in particular, the process may send an arbitrary signal to itself inside a handler for any of the non-maskable signals. Nevertheless โ in the"timeout"and"watchdog"rows above, only the signals that systemd sends have been included. And on top of that? UsingSuccessExitStatus=you can declare additional exit statuses to indicate clean termination, and that is not reflected by this table. So don't @ me when the table doesn't match your reality, it was never gonna. ๐
Added in version 232.
$MONITOR_SERVICE_RESULT, $MONITOR_EXIT_CODE, $MONITOR_EXIT_STATUS, $MONITOR_INVOCATION_ID, $MONITOR_UNIT ๐
Again: service units only. These get passed to all ExecStart= and ExecStartPre= processes that run in services triggered by OnFailure= or OnSuccess= dependencies. Yes, the whole crew.
$MONITOR_SERVICE_RESULT,$MONITOR_EXIT_CODEand$MONITOR_EXIT_STATUStake the same values as they do forExecStop=andExecStopPost=processes (so like, the table up there โฌ๏ธ applies).$MONITOR_INVOCATION_IDand$MONITOR_UNITare set to the invocation id and unit name of the service that triggered the dependency. Cute, right?
๐ plot twist: when multiple services specify the same unit as theirOnFailure=orOnSuccess=handler, those variables will not be passed. Like, at all. So instead, consider using a template handler unit for that case: -"OnFailure=handler@%n.service"for non-templated units -"OnFailure=handler@%p-%i.service"for templated units
Added in version 251.
$PIDFILE ๐
The path to the configured PID file โ this is for when the process is forked off on behalf of a service that uses the PIDFile= setting. (See systemd.service(5) for the details, bestie, I'm not repeating the whole thing here.)
Service code may use this environment variable to automatically generate a PID file at the location configured in the unit file. And yes, this field is set to an absolute path in the file system, no relative nonsense. โจ
Added in version 242.
$REMOTE_ADDR, $REMOTE_PORT ๐
If this is a unit started via per-connection socket activation (i.e. via a socket unit with Accept=yes), these environment variables contain the info about the remote peer of the socket connection. Peek-a-boo, I see your peer. ๐
- For IPv4 and IPv6 connections:
$REMOTE_ADDRcontains the IP address, and$REMOTE_PORTcontains the port number of the remote peer. - For
AF_UNIXsocket connections:$REMOTE_ADDRcontains either the remote socket's file system path starting with a slash ("/"), or its address in the abstract namespace starting with an at symbol ("@"), or is unset in case of an unnamed socket. And$REMOTE_PORT? Not set forAF_UNIXsockets. Not even a little bit.
Added in version 220.
$SO_COOKIE ๐ช
Same setup โ if this is a unit started via per-connection socket activation (i.e. via a socket unit with Accept=yes), this environment variable contains the Linux socket cookie, formatted as a decimal integer. The socket cookie can otherwise be acquired via getsockopt(7), if that's more your vibe.
Added in version 258.
$TRIGGER_UNIT, $TRIGGER_PATH, $TRIGGER_TIMER_REALTIME_USEC, $TRIGGER_TIMER_MONOTONIC_USEC โฐ
If the unit was activated dynamically (e.g.: a corresponding path unit or timer unit), the unit that triggered it โ plus other type-dependent information โ will be passed via these variables.
๐ญ but here's the thing, and I need you to hear me: this information is provided in a best-effort way. For example, multiple triggers happening one after another will be coalesced and only one will be reported, with no guarantee as to which one it will be. Because of this, in most cases this variable will be primarily informational (i.e. useful for debugging purposes), is lossy, and should not be relied upon to propagate a comprehensive reason for activation. It's giving vibes, not receipts. ๐
Added in version 252.
$MEMORY_PRESSURE_WATCH, $MEMORY_PRESSURE_WRITE ๐ง
If memory pressure monitoring is enabled for this service unit, these hold the path to watch and the data to write into it. See Resource Pressure Handling[28] for the details about these variables and the service protocol data they convey.
Added in version 254.
$CPU_PRESSURE_WATCH, $CPU_PRESSURE_WRITE ๐ฅ
If CPU pressure monitoring is enabled for this service unit, these hold the path to watch and the data to write into it. See Resource Pressure Handling[28] for details about these variables and the service protocol data they convey. Same energy, different resource. ๐
Added in version 261.
$IO_PRESSURE_WATCH, $IO_PRESSURE_WRITE ๐พ
If IO pressure monitoring is enabled for this service unit, these hold the path to watch and the data to write into it. See Resource Pressure Handling[28] for details about these variables and the service protocol data they convey. That's the trio, bestie, memory + CPU + IO, all watched, all slaying. โจ
Added in version 261.
$FDSTOREโ the max number of file descriptors that can be chilling in the manager for the service. This one only shows up when the file descriptor store is enabled for the service, aka whenFileDescriptorStoreMax=is set to something non-zero (go readsystemd.service(5)for the deets). Your apps can peek at this env var before yeeting file descriptors over to the service manager viasd_pid_notify_with_fds(3). ๐
Added in version 254.
$DEBUG_INVOCATIONโ ifRestartMode=debugis set and a previous attempt at starting the unit flopped, this gets handed to the service to basically say "bestie, turn on the extra logging at startup". Seesystemd.service(5)for more. ๐
Added in version 257.
For system services, when PAMName= is enabled and pam_systemd is part of the chosen PAM stack, systemd may set extra environment variables for services. Specifically those are $XDG_SEAT and $XDG_VTNR โ check pam_systemd(8) for the details. โจ
๐ PROCESS EXIT CODES ๐
Okay so here's the tea: when the service manager invokes a unit process, it might fail to apply the execution parameters you configured with all those settings above. When that happens, the already-created service process will exit with a non-zero exit code before the configured command line even gets executed. (In other words: the child process can exit with these error codes after being created by the fork(2) system call, but before the matching execve(2) system call is called.) The exit codes in play here are the ones defined by the C library, by the LSB specification, and by the systemd service manager itself. ๐ซถ
Basic C library exit codes
The following basic service exit codes are defined by the C library:
0โEXIT_SUCCESSโ generic success code. we love to see it ๐1โEXIT_FAILUREโ generic failure or unspecified error. ๐ญ
LSB service exit codes
The following service exit codes are defined by the LSB specification[29]:
2โEXIT_INVALIDARGUMENTโ invalid or excess arguments.3โEXIT_NOTIMPLEMENTEDโ unimplemented feature.4โEXIT_NOPERMISSIONโ the user has insufficient privileges.5โEXIT_NOTINSTALLEDโ the program is not installed.6โEXIT_NOTCONFIGUREDโ the program is not configured.7โEXIT_NOTRUNNINGโ the program is not running.
The LSB specification suggests that error codes 200 and above are reserved for implementations. Some of those are used by the service manager to flag problems during process invocation:
okay bestie, table 10 time ๐ these are the systemd-specific exit codes, and they're literally the receipts for when systemd itself is like โnah, couldn't do that one thingโ before your program even gets to run. no cap.
Table 10. systemd-specific exit codes
200โEXIT_CHDIR: changing to the requested working directory failed. peepWorkingDirectory=above. ๐ญ201โEXIT_NICE: failed to set up process scheduling priority (nice level). peepNice=above. ๐202โEXIT_FDS: failed to close unwanted file descriptors, or to adjust passed file descriptors. ๐203โEXIT_EXEC: the actual process execution failed โ specifically, theexecve(2)system call. most likely this is caused by a missing or non-accessible executable file. ๐204โEXIT_MEMORY: failed to perform an action due to memory shortage. ๐ตโ๐ซ205โEXIT_LIMITS: failed to adjust resource limits. peepLimitCPU=and related settings above. ๐ง206โEXIT_OOM_ADJUST: failed to adjust the OOM setting. peepOOMScoreAdjust=above. ๐207โEXIT_SIGNAL_MASK: failed to set process signal mask. ๐ซถ208โEXIT_STDIN: failed to set up standard input. peepStandardInput=above. ๐209โEXIT_STDOUT: failed to set up standard output. peepStandardOutput=above. โจ210โEXIT_CHROOT: failed to change root directory (chroot(2)). peepRootDirectory=/RootImage=above. ๐ธ211โEXIT_IOPRIO: failed to set up IO scheduling priority. peepIOSchedulingClass=/IOSchedulingPriority=above. ๐ฅ212โEXIT_TIMERSLACK: failed to set up timer slack. peepTimerSlackNSec=above. โญ213โEXIT_SECUREBITS: failed to set process secure bits. peepSecureBits=above. ๐214โEXIT_SETSCHEDULER: failed to set up CPU scheduling. peepCPUSchedulingPolicy=/CPUSchedulingPriority=above. ๐215โEXIT_CPUAFFINITY: failed to set up CPU affinity. peepCPUAffinity=above. ๐ฆ216โEXIT_GROUPโ couldn't figure out or switch the group credentials, bestie ๐ญ seeGroup=andSupplementaryGroups=up above.217โEXIT_USERโ the user credentials were a whole mess to determine or change, OR setting up user namespacing flopped. checkUser=/PrivateUsers=above. ๐218โEXIT_CAPABILITIESโ dropping capabilities? failed. applying ambient capabilities? also failed. look atCapabilityBoundingSet=/AmbientCapabilities=above ๐219โEXIT_CGROUPโ setting up the service control group did not happen. that's it, that's the code.220โEXIT_SETSIDโ couldn't create a new process session. rip to the session that never was ๐ซถ221โEXIT_CONFIRMโ the execution got cancelled by the user themselves ๐ณ see thesystemd.confirm_spawn=kernel command line setting onkernel-command-line(7)for the deets.222โEXIT_STDERRโ standard error output setup said no โ seeStandardError=above.224โEXIT_PAMโ PAM session setup failed. peek atPAMName=above ๐ธ225โEXIT_NETWORKโ network namespacing didn't come together. seePrivateNetwork=above.226โEXIT_NAMESPACEโ mount, UTS, or IPC namespacing setup failed, ugh ๐ฉ checkReadOnlyPaths=,ProtectHostname=,PrivateIPC=, and the related settings above.227โEXIT_NO_NEW_PRIVILEGESโ couldn't disable new privileges. seeNoNewPrivileges=yesabove.228โEXIT_SECCOMPโ applying system call filters failed ๐ seeSystemCallFilter=and related settings above.229โEXIT_SELINUX_CONTEXTโ determining or changing the SELinux context did not go well. seeSELinuxContext=above.230โEXIT_PERSONALITYโ failed to set up an execution domain (aka personality, it's giving main character syndrome fr). seePersonality=above.231โEXIT_APPARMOR_PROFILEโ failed to prepare changing the AppArmor profile. seeAppArmorProfile=above.232โEXIT_ADDRESS_FAMILIESโ restricting address families failed. seeRestrictAddressFamilies=above.233โEXIT_RUNTIME_DIRECTORYโ runtime directory setup flopped. seeRuntimeDirectory=and related settings above.235โEXIT_CHOWNโ failed to adjust socket ownership. used for socket units only iykyk โจ236โEXIT_SMACK_PROCESS_LABELโ setting the SMACK label failed. seeSmackProcessLabel=above.237โEXIT_KEYRINGโ kernel keyring setup failed ๐ no keyring for you.238โEXIT_STATE_DIRECTORYโ couldn't set up the unit's state directory. seeStateDirectory=above.239โEXIT_CACHE_DIRECTORYโ couldn't set up the unit's cache directory. seeCacheDirectory=above ๐
and bestie we are so not done yet โ the exit code saga continues ๐
240โEXIT_LOGS_DIRECTORYโ bestie the unit's logging directory did NOT get set up ๐ peepLogsDirectory=up above for the tea.241โEXIT_CONFIGURATION_DIRECTORYโ couldn't set up the unit's configuration directory, tragic. seeConfigurationDirectory=above ๐ซถ242โEXIT_NUMA_POLICYโ the unit's NUMA memory policy would not set up, no cap. seeNUMAPolicy=andNUMAMask=above โจ243โEXIT_CREDENTIALSโ setting up the unit's credentials flopped. seeImportCredential=,LoadCredential=andSetCredential=above ๐245โEXIT_BPFโ applying BPF restrictions said nope. seeRestrictFileSystems=above ๐ง
Okay and finally, the BSD girlies define a whole set of exit codes too, and those are typically defined on Linux systems as well ๐ like, they're just there:
๐ Table 11. BSD exit codes ๐
64โEX_USAGEโ command line usage error. you typed it wrong bestie ๐ญ65โEX_DATAERRโ data format error.66โEX_NOINPUTโ cannot open input.67โEX_NOUSERโ addressee unknown.68โEX_NOHOSTโ host name unknown.69โEX_UNAVAILABLEโ service unavailable.70โEX_SOFTWAREโ internal software error.71โEX_OSERRโ system error (e.g., cannot fork).72โEX_OSFILEโ critical OS file missing.73โEX_CANTCREATโ cannot create (user) output file.74โEX_IOERRโ input/output error.75โEX_TEMPFAILโ temporary failure; user is invited to retry. it's giving "try again later" energy โจ76โEX_PROTOCOLโ remote error in protocol.77โEX_NOPERMโ permission denied.78โEX_CONFIGโ configuration error.
๐ EXAMPLES ๐
โจ Example 9. $MONITOR_* usage โจ
So picture this: a service called myfailer.service that can trigger an OnFailure= dependency ๐
[Unit]
Description=Service which can trigger an OnFailure= dependency
OnFailure=myhandler.service
[Service]
ExecStart=/bin/myprogram
Then a service called mysuccess.service that can trigger an OnSuccess= dependency ๐ธ
[Unit]
Description=Service which can trigger an OnSuccess= dependency
OnSuccess=myhandler.service
[Service]
ExecStart=/bin/mysecondprogram
And then a service called myhandler.service that can get triggered by either of the two above, iconic behavior fr fr ๐
[Unit]
Description=Acts on service failing or succeeding
[Service]
ExecStart=/bin/bash -c "echo $MONITOR_SERVICE_RESULT $MONITOR_EXIT_CODE $MONITOR_EXIT_STATUS $MONITOR_INVOCATION_ID $MONITOR_UNIT"
So if myfailer.service were to run and exit in failure, then myhandler.service would be triggered and the monitor variables would be set as follows, periodt:
MONITOR_SERVICE_RESULT=exit-code
MONITOR_EXIT_CODE=exited
MONITOR_EXIT_STATUS=1
MONITOR_INVOCATION_ID=cc8fdc149b2b4ca698d4f259f4054236
MONITOR_UNIT=myfailer.service
And if mysuccess.service were to run and exit in success, then myhandler.service would be triggered and the monitor variables would be set as follows, main character energy only:
MONITOR_SERVICE_RESULT=success
MONITOR_EXIT_CODE=exited
MONITOR_EXIT_STATUS=0
MONITOR_INVOCATION_ID=6ab9af147b8c4a3ebe36e7a5f8611697
MONITOR_UNIT=mysuccess.service
๐ SEE ALSO ๐
systemd(1), systemctl(1), systemd-analyze(1), journalctl(1), systemd-system.conf(5), systemd.unit(5), systemd.service(5), systemd.socket(5), systemd.swap(5), systemd.mount(5), systemd.kill(5), systemd.resource-control(5), systemd.time(7), systemd.directives(7), tmpfiles.d(5), exec(3), fork(2)
like, go read those too, iykyk ๐ฆ
๐ NOTES
okay bestie this is the receipts section โ every single footnote from the whole page, all 29 of them, in order, links and all. go touch grass after this one fr fr ๐ฑ
- UAPI.2 Discoverable Partitions Specification โ
https://uapi-group.org/specifications/specs/discoverable_partitions_specification - polkit โ
https://www.freedesktop.org/software/polkit/docs/latest/ - The /proc Filesystem โ
https://docs.kernel.org/filesystems/proc.html#mount-options - User/Group Name Syntax โ
https://systemd.io/USER_NAMES - Password Agent โ
https://systemd.io/PASSWORD_AGENTS - No New Privileges Flag โ
https://docs.kernel.org/userspace-api/no_new_privs.html - Executability check โ
https://docs.kernel.org/userspace-api/check_exec.html - JSON User Record โ
https://systemd.io/USER_RECORD - The /proc Filesystem โ
https://docs.kernel.org/filesystems/proc.html - id-mapped mounts โ
https://lwn.net/Articles/896255/ - quotactl โ
https://man7.org/linux/man-pages/man2/quotactl.2.html - quotaon. โ
https://linux.die.net/man/8/quotaon - repquota โ
https://man7.org/linux/man-pages/man8/repquota.8.html - Kernel Samepage Merging โ
https://docs.kernel.org/admin-guide/mm/ksm.html - Transparent Hugepage Support โ
https://docs.kernel.org/admin-guide/mm/transhuge.html - LWN post โ
https://lwn.net/Articles/947173/ - unicode scalar values โ
https://www.unicode.org/glossary/#unicode_scalar_value - unicode noncharacters โ
https://www.unicode.org/glossary/#noncharacter - unicode byte order mark โ
https://www.unicode.org/glossary/#byte_order_mark - POSIX shell unquoted text โ
https://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_02_01 - POSIX shell single-quoted text โ
https://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_02_02 - POSIX shell double-quoted text โ
https://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_02_03 - Base64 โ
https://tools.ietf.org/html/rfc2045#section-6.8 - Container Interface โ
https://systemd.io/CONTAINER_INTERFACE - DMI/SMBIOS โ
https://www.dmtf.org/standards/smbios - qemu โ
https://www.qemu.org/docs/master/system/index.html - System and Service Credentials โ
https://systemd.io/CREDENTIALS - Resource Pressure Handling โ
https://systemd.io/PRESSURE - LSB specification โ
https://refspecs.linuxbase.org/LSB_5.0.0/LSB-Core-generic/LSB-Core-generic/iniscrptact.html
and that's a wrap on the whole page, bestie ๐ you read all of it, every option, every flag, every warning, every single footnote. genuinely iconic behavior, no notes. โจ
this was systemd 262.1, page SYSTEMD.EXEC(5) โ go forth and exec responsibly, mwah ๐๐ฆ
๐ฆ where this page comes from
- arch rolling: systemd 262.1-1 ยท LGPL-2.1-or-later AND CC0-1.0 AND GPL-2.0-or-later AND MIT-0
- ๐ girlified by deepseek-ai/DeepSeek-V4.1-Flash on : a language model's rewrite of the original, under its terms