Config...ervice(3pm)

๐Ÿ’… Config::Model::models::Systemd::Section::Service(3pm) ๐Ÿ’…

okay bestie, this is User Contributed Perl Documentation, and it's giving extremely niche but also extremely powerful. Come sit with me. ๐Ÿซถ


๐ŸŒˆ NAME ๐ŸŒˆ

Config::Model::models::Systemd::Section::Service โ€” a Configuration class, and the class in question is literally Systemd::Section::Service. That's it, that's the name, periodt. โœจ


๐Ÿ’– DESCRIPTION ๐Ÿ’–

So these are Configuration classes used by Config::Model. Slay.

A unit configuration file whose name ends in .service encodes information about a process controlled and supervised by systemd. Main character behavior, honestly. ๐Ÿ‘‘

This man page lists the configuration options specific to this unit type. See systemd.unit(5) for the common options of all unit configuration files. The common configuration items are configured in the generic [Unit] and [Install] sections. The service specific configuration options are configured in the [Service] section. Not me memorizing which section goes where โ€” iykyk. ๐Ÿ’€

Additional options are listed in systemd.exec(5), which define the execution environment the commands are executed in, and in systemd.kill(5), which define the way the processes of the service are terminated, and in systemd.resource-control(5), which configure resource control settings for the processes of the service. Three whole other man pages. She's connected. ๐ŸŒโœจ

The systemd-run(1) command allows creating .service and .scope units dynamically and transiently from the command line. This configuration class was generated from systemd documentation. by parse-man.pl <https://github.com/dod38fr/config-model-systemd/contrib/parse-man.pl> โ€” so yeah, it was auto-generated, that's the lore, we respect the pipeline. ๐Ÿงš


โœจ ELEMENTS โœจ


๐Ÿ’– CPUWeight

These settings control the "cpu" controller in the unified hierarchy. ๐Ÿง 

These options accept an integer value or the special string "idle":

While "StartupCPUWeight" applies to the startup and shutdown phases of the system, "CPUWeight" applies to normal runtime of the system, and if the former is not set also to the startup and shutdown phases. Using "StartupCPUWeight" allows prioritizing specific services at boot-up and shutdown differently than during normal runtime. boot-up gets its own vibe check, fr fr. ๐ŸŒธ

In addition to the resource allocation performed by the "cpu" controller, the kernel may automatically divide resources based on session-id grouping, see "The autogroup feature" in sched(7). The effect of this feature is similar to the "cpu" controller with no explicit configuration, so users should be careful to not mistake one for the other. โš ๏ธ genuine caution here, bestie, don't confuse the two, they are NOT the same girl.

Optional. Type uniline.


๐Ÿ’– StartupCPUWeight

These settings control the "cpu" controller in the unified hierarchy. Same controller, different phase, we're in the startup era now. ๐Ÿš€

These options accept an integer value or the special string "idle":

While "StartupCPUWeight" applies to the startup and shutdown phases of the system, "CPUWeight" applies to normal runtime of the system, and if the former is not set also to the startup and shutdown phases. Using "StartupCPUWeight" allows prioritizing specific services at boot-up and shutdown differently than during normal runtime. Different priorities for different moments, that's range. ๐Ÿ’…

In addition to the resource allocation performed by the "cpu" controller, the kernel may automatically divide resources based on session-id grouping, see "The autogroup feature" in sched(7). The effect of this feature is similar to the "cpu" controller with no explicit configuration, so users should be careful to not mistake one for the other. So please, do not mistake them. ๐Ÿ™ˆ

Optional. Type uniline.


๐Ÿ’– CPUQuota

This setting controls the "cpu" controller in the unified hierarchy. โฑ๏ธ

Assign the specified CPU time quota to the processes executed. Takes a percentage value, suffixed with "%". The percentage specifies how much CPU time the unit shall get at maximum, relative to the total CPU time available on one CPU. Use values > 100% for allotting CPU time on more than one CPU. This controls the "cpu.max" attribute on the unified control group hierarchy and "cpu.cfs_quota_us" on legacy. For details about these control group attributes, see Control Groups v2 <https://docs.kernel.org/admin-guide/cgroup-v2.html> and CFS Bandwidth Control <https://docs.kernel.org/scheduler/sched-bwc.html>. Setting "CPUQuota" to an empty value unsets the quota. empty = gone, no cap. ๐Ÿซฅ

Example: "CPUQuota=20%" ensures that the executed processes will never get more than 20% CPU time on one CPU.

Optional. Type uniline.


๐Ÿ’– CPUQuotaPeriodSec

This setting controls the "cpu" controller in the unified hierarchy. ๐ŸŽš๏ธ

Assign the duration over which the CPU time quota specified by "CPUQuota" is measured. Takes a time duration value in seconds, with an optional suffix such as "ms" for milliseconds (or "s" for seconds.) The default setting is 100ms. The period is clamped to the range supported by the kernel, which is [1ms, 1000ms]. Additionally, the period is adjusted up so that the quota interval is also at least 1ms. Setting "CPUQuotaPeriodSec" to an empty value resets it to the default. she snaps right back. ๐ŸŒŸ

This controls the second field of "cpu.max" attribute on the unified control group hierarchy and "cpu.cfs_period_us" on legacy. For details about these control group attributes, see Control Groups v2 <https://docs.kernel.org/admin-guide/cgroup-v2.html> and CFS Scheduler <https://docs.kernel.org/scheduler/sched-design-CFS.html>.

Example: "CPUQuotaPeriodSec=10ms" to request that the CPU quota is measured in periods of 10ms.

Optional. Type uniline.


๐Ÿ’– AllowedCPUs

This setting controls the "cpuset" controller in the unified hierarchy. ๐Ÿ–ฅ๏ธโœจ

Restrict processes to be executed on specific CPUs. Takes a list of CPU indices or ranges separated by either whitespace or commas. CPU ranges are specified by the lower and upper CPU indices separated by a dash.

Setting "AllowedCPUs" or "StartupAllowedCPUs" does not guarantee that all of the CPUs will be used by the processes as it may be limited by parent units. The effective configuration is reported as "EffectiveCPUs". so like. you asked. but the parents might say no. ๐Ÿ˜ญ very real.

While "StartupAllowedCPUs" applies to the startup and shutdown phases of the system, "AllowedCPUs" applies to normal runtime of the system, and if the former is not set also to the startup and shutdown phases. Using "StartupAllowedCPUs" allows prioritizing specific services at boot-up and shutdown differently than during normal runtime.

This setting is supported only with the unified control group hierarchy.

Optional. Type uniline.


๐Ÿ’– StartupAllowedCPUs

This setting controls the "cpuset" controller in the unified hierarchy. ๐ŸŒธ

Restrict processes to be executed on specific CPUs. Takes a list of CPU indices or ranges separated by either whitespace or commas. CPU ranges are specified by the lower and upper CPU indices separated by a dash. (dash between the low one and the high one, noted. ๐Ÿ“)

Setting "AllowedCPUs" or "StartupAllowedCPUs" does not guarantee that all of the CPUs will be used by the processes as it may be limited by parent units. The effective configuration is reported as "EffectiveCPUs". the truth will be revealed as "EffectiveCPUs", no cap. ๐Ÿ‘‘

While "StartupAllowedCPUs" applies to the startup and shutdown phases of the system, "AllowedCPUs" applies to normal runtime of the system, and if the former is not set also to the startup and shutdown phases. Using "StartupAllowedCPUs" allows prioritizing specific services at boot-up and shutdown differently than during normal runtime.

This setting is supported only with the unified control group hierarchy. โ— unified hierarchy only, don't try it anywhere else, she will not show up.

Optional. Type uniline.


๐Ÿ’– MemoryAccounting

This setting controls the "memory" controller in the unified hierarchy. ๐Ÿง ๐Ÿ’พ

Turn on process and kernel memory accounting for this unit. Takes a boolean argument. Note that turning on memory accounting for one unit will also implicitly turn it on for all units contained in the same slice and for all its parent slices and the units contained therein. it spreads. it's infectious. in a cute way. ๐Ÿฆ‹ The system default for this setting may be controlled with "DefaultMemoryAccounting" in systemd-system.conf(5).

Optional. Type boolean.


๐Ÿ’– MemoryMin

These settings control the "memory" controller in the unified hierarchy. ๐ŸŽ€

Specify the memory usage protection of the executed processes in this unit. When reclaiming memory, the unit is treated as if it was using less memory resulting in memory to be preferentially reclaimed from unprotected units. Using "MemoryLow" results in a weaker protection where memory may still be reclaimed to avoid invoking the OOM killer in case there is no other reclaimable memory. so "MemoryLow" is the softer version โ€” still protected, just with less conviction. ๐Ÿ’…

For a protection to be effective, it is generally required to set a corresponding allocation on all ancestors, which is then distributed between children (with the exception of the root slice). Any "MemoryMin" or "MemoryLow" allocation that is not explicitly distributed to specific children is used to create a shared protection for all children. As this is a shared protection, the children will freely compete for the memory. sibling rivalry but make it cgroups. ๐Ÿ˜ญ

Takes a memory size in bytes. If the value is suffixed with K, M, G or T, the specified memory size is parsed as Kilobytes, Megabytes, Gigabytes, or Terabytes (with the base 1024), respectively. Alternatively, a percentage value may be specified, which is taken relative to the installed physical memory on the system. If assigned the special value "infinity", all available memory is protected, which may be useful in order to always inherit all of the protection afforded by ancestors. This controls the "memory.min" or "memory.low" control group attribute. For details about this control group attribute, see Memory Interface Files <https://docs.kernel.org/admin-guide/cgroup-v2.html#memory-interface-files>. infinity is the mood. ๐ŸŒŒ

While "StartupMemoryLow" applies to the startup and shutdown phases of the system, "MemoryMin" applies to normal runtime of the system, and if the former is not set also to the startup and shutdown phases. Using "StartupMemoryLow" allows prioritizing specific services at boot-up and shutdown differently than during normal runtime.

Optional. Type uniline.


๐Ÿ’– StartupMemoryLow

These settings control the "memory" controller in the unified hierarchy. ๐Ÿงšโœจ

Specify the memory usage protection of the executed processes in this unit. When reclaiming memory, the unit is treated as if it was using less memory resulting in memory to be preferentially reclaimed from unprotected units. Using "MemoryLow" results in a weaker protection where memory may still be reclaimed to avoid invoking the OOM killer in case there is no other reclaimable memory. protecting you from the OOM killer is genuinely the most romantic thing a kernel does ngl. ๐Ÿฅบ

For a protection to be effective, it is generally required to set a corresponding allocation on all ancestors, which is then distributed between children (with the exception of the root slice). Any "MemoryMin" or "MemoryLow" allocation that is not explicitly distributed to specific children is used to create a shared protection for all children. As this is a shared protection, the children will freely compete for the memory.

Okay so continuing right where we left off, no notes ๐Ÿ’…

Takes a memory size in bytes, bestie. If the value is suffixed with K, M, G or T, then the specified memory size is parsed as Kilobytes, Megabytes, Gigabytes, or Terabytes respectively โ€” and yes, at base 1024, we're being civilized about it ๐ŸŽ€. Alternatively you can throw in a percentage value, which is taken relative to the installed physical memory on the system. And if you assign it the special value "infinity", all available memory is protected โ€” which is lowkey iconic when you want to always inherit all of the protection afforded by ancestors ๐Ÿ’–. This controls the "memory.min" or "memory.low" control group attribute. For details about this control group attribute, see Memory Interface Files over at https://docs.kernel.org/admin-guide/cgroup-v2.html#memory-interface-files (iykyk).

And the way that it splits: "StartupMemoryLow" applies to the startup and shutdown phases of the system, while "MemoryMin" applies to normal runtime of the system โ€” and if the former is not set, it also covers the startup and shutdown phases. Using "StartupMemoryLow" is how you prioritize specific services at boot-up and shutdown differently than during normal runtime. Optional. Type uniline.


๐ŸŒธ MemoryHigh

These settings control the "memory" controller in the unified hierarchy.

So here's the tea โ˜• โ€” you specify the throttling limit on memory usage of the executed processes in this unit. Can memory usage go above the limit if it's unavoidable? Unfortunately yes ๐Ÿ˜ญ, but in those cases the processes are heavily slowed down and memory is taken away aggressively. This is the main mechanism to control memory usage of a unit. No cap.

Takes a memory size in bytes. Suffix it with K, M, G or T and the specified memory size is parsed as Kilobytes, Megabytes, Gigabytes, or Terabytes (with the base 1024), respectively. Alternatively, a percentage value may be specified, which is taken relative to the installed physical memory on the system. If you assign the special value "infinity", no memory throttling is applied at all โ€” free spirit energy ๐Ÿฆ‹. This controls the "memory.high" control group attribute. For details about this control group attribute, see Memory Interface Files: https://docs.kernel.org/admin-guide/cgroup-v2.html#memory-interface-files. The effective configuration is reported as "EffectiveMemoryHigh" (see also "EffectiveMemoryMax").

And because the vibes differ between boot and runtime: while "StartupMemoryHigh" applies to the startup and shutdown phases of the system, "MemoryHigh" applies to normal runtime of the system, and if the former is not set, also to the startup and shutdown phases. Using "StartupMemoryHigh" allows prioritizing specific services at boot-up and shutdown differently than during normal runtime. Optional. Type uniline.

โœจ StartupMemoryHigh

These settings control the "memory" controller in the unified hierarchy.

Yeah it's the same energy as MemoryHigh, just for a different moment in the system's life ๐ŸŒˆ. Specify the throttling limit on memory usage of the executed processes in this unit. Memory usage may go above the limit if unavoidable, but the processes are heavily slowed down and memory is taken away aggressively in such cases. This is the main mechanism to control memory usage of a unit, periodt.

Takes a memory size in bytes. Values suffixed with K, M, G or T get parsed as Kilobytes, Megabytes, Gigabytes, or Terabytes (with the base 1024), respectively. Alternatively, a percentage value may be specified, which is taken relative to the installed physical memory on the system. If assigned the special value "infinity", no memory throttling is applied. This controls the "memory.high" control group attribute. For details about this control group attribute, see Memory Interface Files: https://docs.kernel.org/admin-guide/cgroup-v2.html#memory-interface-files. The effective configuration is reported as "EffectiveMemoryHigh" (see also "EffectiveMemoryMax").

While "StartupMemoryHigh" applies to the startup and shutdown phases of the system, "MemoryHigh" applies to normal runtime of the system, and if the former is not set also to the startup and shutdown phases. Using "StartupMemoryHigh" allows prioritizing specific services at boot-up and shutdown differently than during normal runtime. Optional. Type uniline.

๐Ÿ’€ MemoryMax

These settings control the "memory" controller in the unified hierarchy.

Now this one is the โœจseriousโœจ one. Specify the absolute limit on memory usage of the executed processes in this unit. If memory usage cannot be contained under the limit, out-of-memory killer is invoked inside the unit ๐Ÿ’€. Listen โ€” it is recommended to use "MemoryHigh" as the main control mechanism and use "MemoryMax" as the last line of defense. It's giving "designated survivor," not "first responder," bestie.

Takes a memory size in bytes. Suffix K, M, G or T and the specified memory size is parsed as Kilobytes, Megabytes, Gigabytes, or Terabytes (with the base 1024), respectively. Alternatively, a percentage value may be specified, which is taken relative to the installed physical memory on the system. If assigned the special value "infinity", no memory limit is applied. This controls the "memory.max" control group attribute. For details about this control group attribute, see Memory Interface Files: https://docs.kernel.org/admin-guide/cgroup-v2.html#memory-interface-files. The effective configuration is reported as "EffectiveMemoryMax" โ€” and heads up, that value is the most stringent limit of the unit and parent slices, and it is capped by physical memory ๐Ÿ‘‘

While "StartupMemoryMax" applies to the startup and shutdown phases of the system, "MemoryMax" applies to normal runtime of the system, and if the former is not set also to the startup and shutdown phases. Using "StartupMemoryMax" allows prioritizing specific services at boot-up and shutdown differently than during normal runtime. Optional. Type uniline.

๐Ÿซถ StartupMemoryMax

These settings control the "memory" controller in the unified hierarchy.

Specify the absolute limit on memory usage of the executed processes in this unit. If memory usage cannot be contained under the limit, out-of-memory killer is invoked inside the unit ๐Ÿ˜ต. And once more for the people in the back: it is recommended to use "MemoryHigh" as the main control mechanism and use "MemoryMax" as the last line of defense.

Takes a memory size in bytes. If the value is suffixed with K, M, G or T, the specified memory size is parsed as Kilobytes, Megabytes, Gigabytes, or Terabytes (with the base 1024), respectively. Alternatively, a percentage value may be specified, which is taken relative to the installed physical memory on the system. If assigned the special value "infinity", no memory limit is applied. This controls the "memory.max" control group attribute. For details about this control group attribute, see Memory Interface Files: https://docs.kernel.org/admin-guide/cgroup-v2.html#memory-interface-files. The effective configuration is reported as "EffectiveMemoryMax" (the value is the most stringent limit of the unit and parent slices and it is capped by physical memory).

While "StartupMemoryMax" applies to the startup and shutdown phases of the system, "MemoryMax" applies to normal runtime of the system, and if the former is not set also to the startup and shutdown phases. Using "StartupMemoryMax" allows prioritizing specific services at boot-up and shutdown differently than during normal runtime. Optional. Type uniline.

๐Ÿงš MemorySwapMax

These settings control the "memory" controller in the unified hierarchy.

Specify the absolute limit on swap usage of the executed processes in this unit. Yes, swap, the thing your laptop does when you open forty tabs ๐Ÿ“.

Takes a swap size in bytes. Suffixed with K, M, G or T, the specified swap size is parsed as Kilobytes, Megabytes, Gigabytes, or Terabytes (with the base 1024), respectively. Alternatively, a percentage value may be specified, which is taken relative to the specified swap size on the system. If assigned the special value "infinity", no swap limit is applied. These settings control the "memory.swap.max" control group attribute. For details about this control group attribute, see Memory Interface Files: https://docs.kernel.org/admin-guide/cgroup-v2.html#memory-interface-files.

While "StartupMemorySwapMax" applies to the startup and shutdown phases of the system, "MemorySwapMax" applies to normal runtime of the system, and if the former is not set also to the startup and shutdown phases. Using "StartupMemorySwapMax" allows prioritizing specific services at boot-up and shutdown differently than during normal runtime. Optional. Type uniline.

๐Ÿ’œ StartupMemorySwapMax

These settings control the "memory" controller in the unified hierarchy.

Specify the absolute limit on swap usage of the executed processes in this unit โ€” swap again, but make it boot-time ๐ŸŽ€.

Takes a swap size in bytes. If the value is suffixed with K, M, G or T, the specified swap size is parsed as Kilobytes, Megabytes, Gigabytes, or Terabytes (with the base 1024), respectively. Alternatively, a percentage value may be specified, which is taken relative to the specified swap size on the system. If assigned the special value "infinity", no swap limit is applied. These settings control the "memory.swap.max" control group attribute. For details about this control group attribute, see Memory Interface Files: https://docs.kernel.org/admin-guide/cgroup-v2.html#memory-interface-files.

While "StartupMemorySwapMax" applies to the startup and shutdown phases of the system, "MemorySwapMax" applies to normal runtime of the system, and if the former is not set also to the startup and shutdown phases. Using "StartupMemorySwapMax" allows prioritizing specific services at boot-up and shutdown differently than during normal runtime. Optional. Type uniline.

๐Ÿ’… MemoryZSwapMax

These settings control the "memory" controller in the unified hierarchy.

Specify the absolute limit on zswap usage of the processes in this unit. Now what is zswap, you ask? Babygirl, it's a lightweight compressed cache for swap pages ๐Ÿ’–. It takes pages that are in the process of being swapped out and attempts to compress them into a dynamically allocated RAM-based memory pool. If the limit you specified is hit, no entries from this unit will be stored in the pool until existing entries are faulted back or written out to disk. See the kernel's Zswap documentation at https://docs.kernel.org/admin-guide/mm/zswap.html for more details, no gatekeeping here.

Takes a size in bytes. Suffixed with K, M, G or T, the specified size is parsed as Kilobytes, Megabytes, Gigabytes, or Terabytes (with the base 1024), respectively. If assigned the special value "infinity", no limit is applied. These settings control the "memory.zswap.max" control group attribute. For details about this control group attribute, see Memory Interface Files: https://docs.kernel.org/admin-guide/cgroup-v2.html#memory-interface-files.

While "StartupMemoryZSwapMax" applies to the startup and shutdown phases of the system, "MemoryZSwapMax" applies to normal runtime of the system, and if the former is not set also to the startup and shutdown phases. Using "StartupMemoryZSwapMax" allows prioritizing specific services at boot-up and shutdown differently than during normal runtime. Optional. Type uniline.

StartupMemoryZSwapMax

These settings control the "memory" controller in the unified hierarchy. โœจ

Bestie, meet zswap: it's a lightweight compressed cache for swap pages. What it does is take pages that are in the process of being swapped out and try to squish them into a dynamically allocated RAM-based memory pool. Cute, right? ๐Ÿ’– This setting specifies the absolute limit on zswap usage of the processes in this unit. And if that limit gets hit? Then no entries from this unit will be stored in the pool until existing entries are faulted back or written out to disk. See the kernel's Zswap docs at https://docs.kernel.org/admin-guide/mm/zswap.html for the full lore.

Takes a size 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. And if you assign it the special value "infinity", no limit is applied โ€” living free ๐ŸŒˆ. These settings control the memory.zswap.max control group attribute. For deets about that attribute, see Memory Interface Files at https://docs.kernel.org/admin-guide/cgroup-v2.html#memory-interface-files.

So here's the vibe: while StartupMemoryZSwapMax applies to the startup and shutdown phases of the system, MemoryZSwapMax applies to normal runtime of the system, and if the former is not set also to the startup and shutdown phases. Using StartupMemoryZSwapMax lets you prioritize specific services at boot-up and shutdown differently than during normal runtime. Optional. Type uniline.

MemoryZSwapWriteback

This setting controls the "memory" controller in the unified hierarchy.

Takes a boolean argument. When true, pages stored in the Zswap cache are permitted to be written to the backing storage โ€” false otherwise. Defaults to true. This lets you disable writeback of swap pages for IO-intensive applications, while still retaining the ability to store compressed pages in Zswap (iconic behavior, honestly ๐Ÿ‘‘). See the kernel's Zswap docs at https://docs.kernel.org/admin-guide/mm/zswap.html for more. Optional. Type boolean.

  • upstream_default value : yes

AllowedMemoryNodes

These settings control the "cpuset" controller in the unified hierarchy.

Restrict processes to be executed on specific memory NUMA nodes. Takes a list of memory NUMA nodes indices or ranges separated by either whitespace or commas. Memory NUMA nodes ranges are specified by the lower and upper NUMA nodes indices separated by a dash. ๐Ÿ“

Ok important caveat, no cap: setting AllowedMemoryNodes or StartupAllowedMemoryNodes does not guarantee that all of the memory NUMA nodes will actually be used by the processes, because it may be limited by parent units. The effective configuration is reported as EffectiveMemoryNodes.

While StartupAllowedMemoryNodes applies to the startup and shutdown phases of the system, AllowedMemoryNodes applies to normal runtime of the system, and if the former is not set also to the startup and shutdown phases. Using StartupAllowedMemoryNodes allows prioritizing specific services at boot-up and shutdown differently than during normal runtime.

Heads up bestie: this setting is supported only with the unified control group hierarchy. Optional. Type uniline.

StartupAllowedMemoryNodes

These settings control the "cpuset" controller in the unified hierarchy.

Restrict processes to be executed on specific memory NUMA nodes. Takes a list of memory NUMA nodes indices or ranges separated by either whitespace or commas. Memory NUMA nodes ranges are specified by the lower and upper NUMA nodes indices separated by a dash. ๐Ÿ“

Setting AllowedMemoryNodes or StartupAllowedMemoryNodes does not guarantee that all of the memory NUMA nodes will be used by the processes as it may be limited by parent units. The effective configuration is reported as EffectiveMemoryNodes.

While StartupAllowedMemoryNodes applies to the startup and shutdown phases of the system, AllowedMemoryNodes applies to normal runtime of the system, and if the former is not set also to the startup and shutdown phases. Using StartupAllowedMemoryNodes allows prioritizing specific services at boot-up and shutdown differently than during normal runtime.

Same warning as above, so listen up: this setting is supported only with the unified control group hierarchy. Optional. Type uniline.

TasksAccounting

This setting controls the "pids" controller in the unified hierarchy.

Turn on task accounting for this unit. Takes a boolean argument. If enabled, the kernel will keep track of the total number of tasks in the unit and its children โ€” and yes, that number includes both kernel threads and userspace processes, with each thread counted individually. ๐Ÿงฎ

Little rippling effect to know about: turning on tasks accounting for one unit will also implicitly turn it on for all units contained in the same slice and for all its parent slices and the units contained therein. The system default for this setting may be controlled with DefaultTasksAccounting in systemd-system.conf(5). Optional. Type boolean.

TasksMax

This setting controls the "pids" controller in the unified hierarchy.

Specify the maximum number of tasks that may be created in the unit. This ensures that the number of tasks accounted for the unit (see above โฌ†๏ธ) stays below a specific limit. This either takes an absolute number of tasks or a percentage value that is taken relative to the configured maximum number of tasks on the system. If assigned the special value "infinity", no tasks limit is applied โ€” boundless ๐Ÿ’ซ. This controls the pids.max control group attribute. For details about this control group attribute, see the pids controller at https://docs.kernel.org/admin-guide/cgroup-v2.html#pid. The effective configuration is reported as EffectiveTasksMax.

The system default for this setting may be controlled with DefaultTasksMax in systemd-system.conf(5). Optional. Type uniline.

IOAccounting

This setting controls the "io" controller in the unified hierarchy.

Turn on Block I/O accounting for this unit, if the unified control group hierarchy is used on the system. Takes a boolean argument. Note that turning on block I/O accounting for one unit will also implicitly turn it on for all units contained in the same slice and all for its parent slices and the units contained therein. The system default for this setting may be controlled with DefaultIOAccounting in systemd-system.conf(5). Optional. Type boolean.

IOWeight

These settings control the "io" controller in the unified hierarchy.

Set the default overall block I/O weight for the executed processes, if the unified control group hierarchy is used on the system. Takes a single weight value (between 1 and 10000) to set the default block I/O weight. This controls the io.weight control group attribute, which defaults to 100. For details about this control group attribute, see IO Interface Files at https://docs.kernel.org/admin-guide/cgroup-v2.html#io-interface-files. The available I/O bandwidth is split up among all units within one slice relative to their block I/O weight โ€” a higher weight means more I/O bandwidth, a lower weight means less. Pretty intuitive honestly, slay. โš–๏ธ

While StartupIOWeight applies to the startup and shutdown phases of the system, IOWeight applies to the later runtime of the system, and if the former is not set also to the startup and shutdown phases. This allows prioritizing specific services at boot-up and shutdown differently than during runtime. Optional. Type uniline.

StartupIOWeight

These settings control the "io" controller in the unified hierarchy.

Set the default overall block I/O weight for the executed processes, if the unified control group hierarchy is used on the system. Takes a single weight value (between 1 and 10000) to set the default block I/O weight. This controls the io.weight control group attribute, which defaults to 100. For details about this control group attribute, see IO Interface Files at https://docs.kernel.org/admin-guide/cgroup-v2.html#io-interface-files. The available I/O bandwidth is split up among all units within one slice relative to their block I/O weight. A higher weight means more I/O bandwidth, a lower weight means less. โš–๏ธ

While StartupIOWeight applies to the startup and shutdown phases of the system, IOWeight applies to the later runtime of the system, and if the former is not set also to the startup and shutdown phases. This allows prioritizing specific services at boot-up and shutdown differently than during runtime. Optional. Type uniline.

IODeviceWeight

This setting controls the "io" controller in the unified hierarchy.

Set the per-device overall block I/O weight for the executed processes, if the unified control group hierarchy is used on the system. Takes a space-separated pair of a file path and a weight value to specify the device specific weight value, between 1 and 10000. (Example: "/dev/sda 1000"). The file path may be specified as path to a block device node or as any other file, in which case the backing block device of the file system of the file is determined. This controls the io.weight control group attribute, which defaults to 100. Use this option multiple times to set weights for multiple devices โ€” go off, set all the weights ๐Ÿ’…. For details about this control group attribute, see IO Interface Files at https://docs.kernel.org/admin-guide/cgroup-v2.html#io-interface-files.

And here's the tea you actually need ๐Ÿซ–: the specified device node should reference a block device that has an I/O scheduler associated, i.e. it should not refer to partition or loopback block devices, but to the originating, physical device. When a path to a regular file or directory is specified, it is attempted to discover the correct originating device backing the file system of the specified path. This works correctly only for simpler cases, where the file system is directly placed on a partition or physical block device, or where simple 1:1 encryption using dm-crypt/LUKS is used. This discovery does not cover complex storage, and in particular not RAID and volume management storage devices. Optional. Type uniline.

IOReadBandwidthMax

These settings control the "io" controller in the unified hierarchy.

Set the per-device overall block I/O bandwidth maximum limit for the processes that get executed, if the unified control group hierarchy is being used on your system. Heads up bestie, this limit is not work-conserving โ€” the executed processes are not allowed to use more bandwidth even if the device has idle capacity just sitting there. ๐Ÿ™ˆ

It takes a space-separated pair of a file path and a bandwidth value (in bytes per second) to specify the device specific bandwidth. The file path may be a path to a block device node, or as any other file โ€” in which case the backing block device of the file system of the file is used. โœจ

If the bandwidth is suffixed with K, M, G, or T, the specified bandwidth is parsed as Kilobytes, Megabytes, Gigabytes, or Terabytes, respectively, to the base of 1000. (Example: "/dev/disk/by-path/pci-0000:00:1f.2-scsi-0:0:0:0 5M").

This controls the "io.max" control group attributes. Use this option multiple times to set bandwidth limits for multiple devices. ๐Ÿ” For details about this control group attribute, see IO Interface Files https://docs.kernel.org/admin-guide/cgroup-v2.html#io-interface-files.

Similar restrictions on block device discovery as for "IODeviceWeight" apply, see above. Optional. Type uniline.


๐Ÿฆ‹ IOWriteBandwidthMax

These settings control the "io" controller in the unified hierarchy. ๐Ÿซถ

Set the per-device overall block I/O bandwidth maximum limit for the processes that get executed, if the unified control group hierarchy is used on the system. And ngl, this limit is not work-conserving โ€” the executed processes are not allowed to use more even if the device has idle capacity. ๐Ÿ’€

It takes a space-separated pair of a file path and a bandwidth value (in bytes per second) to specify the device specific bandwidth. The file path may be a path to a block device node, or as any other file in which case the backing block device of the file system of the file is used. If the bandwidth is suffixed with K, M, G, or T, the specified bandwidth is parsed as Kilobytes, Megabytes, Gigabytes, or Terabytes, respectively, to the base of 1000. (Example: "/dev/disk/by-path/pci-0000:00:1f.2-scsi-0:0:0:0 5M").

This controls the "io.max" control group attributes. Use this option multiple times to set bandwidth limits for multiple devices. For details about this control group attribute, see IO Interface Files https://docs.kernel.org/admin-guide/cgroup-v2.html#io-interface-files.

Similar restrictions on block device discovery as for "IODeviceWeight" apply, see above. Optional. Type uniline.


๐ŸŒธ IOReadIOPSMax

These settings control the "io" controller in the unified hierarchy.

Set the per-device overall block I/O IOs-Per-Second maximum limit for the executed processes, if the unified control group hierarchy is used on the system. Again: not work-conserving, so the executed processes are not allowed to use more even if the device has idle capacity. ๐Ÿ˜ญ

Takes a space-separated pair of a file path and an IOPS value to specify the device specific IOPS. The file path may be a path to a block device node, or as any other file โ€” in that case the backing block device of the file system of the file is used. If the IOPS is suffixed with K, M, G, or T, the specified IOPS is parsed as KiloIOPS, MegaIOPS, GigaIOPS, or TeraIOPS, respectively, to the base of 1000. (Example: "/dev/disk/by-path/pci-0000:00:1f.2-scsi-0:0:0:0 1K").

This controls the "io.max" control group attributes. Use this option multiple times to set IOPS limits for multiple devices. For details about this control group attribute, see IO Interface Files https://docs.kernel.org/admin-guide/cgroup-v2.html#io-interface-files.

Similar restrictions on block device discovery as for "IODeviceWeight" apply, see above. Optional. Type uniline.


๐Ÿ’œ IOWriteIOPSMax

These settings control the "io" controller in the unified hierarchy.

Set the per-device overall block I/O IOs-Per-Second maximum limit for the executed processes, if the unified control group hierarchy is used on the system. This limit is not work-conserving and the executed processes are not allowed to use more even if the device has idle capacity. โœจ

Takes a space-separated pair of a file path and an IOPS value to specify the device specific IOPS. The file path may be a path to a block device node, or as any other file in which case the backing block device of the file system of the file is used. If the IOPS is suffixed with K, M, G, or T, the specified IOPS is parsed as KiloIOPS, MegaIOPS, GigaIOPS, or TeraIOPS, respectively, to the base of 1000. (Example: "/dev/disk/by-path/pci-0000:00:1f.2-scsi-0:0:0:0 1K").

This controls the "io.max" control group attributes. Use this option multiple times to set IOPS limits for multiple devices. For details about this control group attribute, see IO Interface Files https://docs.kernel.org/admin-guide/cgroup-v2.html#io-interface-files.

Similar restrictions on block device discovery as for "IODeviceWeight" apply, see above. Optional. Type uniline.


๐ŸŽ€ IODeviceLatencyTargetSec

This setting controls the "io" controller in the unified hierarchy.

Set the per-device average target I/O latency for the executed processes, if the unified control group hierarchy is used on the system.

Takes a file path and a timespan separated by a space to specify the device specific latency target. (Example: "/dev/sda 25ms"). The file path may be specified as path to a block device node or as any other file, in which case the backing block device of the file system of the file is determined.

This controls the "io.latency" control group attribute. Use this option multiple times to set latency target for multiple devices. For details about this control group attribute, see IO Interface Files https://docs.kernel.org/admin-guide/cgroup-v2.html#io-interface-files.

๐Ÿ’… Implies "IOAccounting=yes".

These settings are supported only if the unified control group hierarchy is used.

Similar restrictions on block device discovery as for "IODeviceWeight" apply, see above. Optional. Type uniline.


๐Ÿฆ‹ IPAccounting

Takes a boolean argument. If true, turns on IPv4 and IPv6 network traffic accounting for packets sent or received by the unit. ๐Ÿ‘‘ When this option is turned on, all IPv4 and IPv6 sockets created by any process of the unit are accounted for.

When this option is used in socket units, it applies to all IPv4 and IPv6 sockets associated with it (including both listening and connection sockets where this applies). Note that for socket-activated services, this configuration setting and the accounting data of the service unit and the socket unit are kept separate, and displayed separately. No propagation of the setting and the collected statistics is done, in either direction. ๐Ÿšซ Moreover, any traffic sent or received on any of the socket unit's sockets is accounted to the socket unit โ€” and never to the service unit it might have activated, even if the socket is used by it.

The system default for this setting may be controlled with "DefaultIPAccounting" in systemd-system.conf(5).

โš ๏ธ Note that this functionality is currently only available for system services, not for per-user services. Optional. Type boolean.

๐ŸŒˆ IPAddressAllow

Turn on network traffic filtering for IP packets sent and received over "AF_INET" and "AF_INET6" sockets. ๐Ÿ”ฅ Both directives take a space separated list of IPv4 or IPv6 addresses, each optionally suffixed with an address prefix length in bits after a / character. If the suffix is omitted, the address is considered a host address, i.e. the filter covers the whole address (32 bits for IPv4, 128 bits for IPv6).

The access lists configured with this option are applied to all sockets created by processes of this unit (or in the case of socket units, associated with it). The lists are implicitly combined with any lists configured for any of the parent slice units this unit might be a member of. By default, both access lists are empty. Both ingress and egress traffic is filtered by these settings. In case of ingress traffic the source IP address is checked against these access lists, in case of egress traffic the destination IP address is checked. ๐ŸŒธ The following rules are applied in turn:

In order to implement an allow-listing IP firewall, it is recommended to use a "IPAddressDeny"="any" setting on an upper-level slice unit (such as the root slice "-.slice" or the slice containing all system services "system.slice" โ€“ see systemd.special(7) for details on these slice units), plus individual per-service "IPAddressAllow" lines permitting network access to relevant services, and only them. ๐Ÿ‘‘

Note that for socket-activated services, the IP access list configured on the socket unit applies to all sockets associated with it directly, but not to any sockets created by the ultimately activated services for it. Conversely, the IP access list configured for the service is not applied to any sockets passed into the service via socket activation. ๐Ÿซ  Thus, it is usually a good idea to replicate the IP access lists on both the socket and the service unit. Nevertheless, it may make sense to maintain one list more open and the other one more restricted, depending on the use case.

If these settings are used multiple times in the same unit the specified lists are combined. If an empty string is assigned to these settings the specific access list is reset and all previous settings undone. ๐Ÿงš

In place of explicit IPv4 or IPv6 address and prefix length specifications a small set of symbolic names may be used. The following names are defined:

๐Ÿ’€ Note that these settings might not be supported on some systems (for example if eBPF control group support is not enabled in the underlying kernel or container manager). These settings will have no effect in that case. If compatibility with such systems is desired it is hence recommended to not exclusively rely on them for IP security. Optional. Type uniline.

๐ŸŒŸ IPAddressDeny

Turn on network traffic filtering for IP packets sent and received over "AF_INET" and "AF_INET6" sockets. ๐Ÿซถ Both directives take a space separated list of IPv4 or IPv6 addresses, each optionally suffixed with an address prefix length in bits after a / character. If the suffix is omitted, the address is considered a host address, i.e. the filter covers the whole address (32 bits for IPv4, 128 bits for IPv6).

The access lists configured with this option are applied to all sockets created by processes of this unit (or in the case of socket units, associated with it). The lists are implicitly combined with any lists configured for any of the parent slice units this unit might be a member of. By default, both access lists are empty. Both ingress and egress traffic is filtered by these settings. In case of ingress traffic the source IP address is checked against these access lists, in case of egress traffic the destination IP address is checked. โœจ The following rules are applied in turn:

so if you wanna build an allow-listing IP firewall, here's the move that's actually recommended: slap an IPAddressDeny=any on some upper-level slice unit โ€” like the root slice -.slice, or the slice that holds all your system services, system.slice (peep systemd.special(7) for the deets on those slice units) โ€” and then add individual per-service IPAddressAllow lines that let the relevant services touch the network. And only them. Periodt. ๐Ÿ’…

Real talk though, for socket-activated services: the IP access list you set on the socket unit applies to all the sockets tied to it directly, but not to any sockets that the ultimately-activated services create for it. And flip side โ€” the IP access list on the service is not applied to any sockets handed into the service via socket activation. ๐Ÿ’€ So yeah, it's usually a good idea to just replicate the IP access lists on both the socket and the service unit. That said, depending on your use case it can totally make sense to keep one list more open and the other one more locked down.

If you use these settings multiple times in the same unit, the specified lists get combined. And if you assign an empty string to them, that specific access list gets reset and all the previous settings are undone. ๐Ÿซถ

Instead of spelling out explicit IPv4 or IPv6 addresses plus prefix lengths, you can use a small set of symbolic names. The following names are defined:

Heads up bestie: these settings might not be supported on some systems โ€” like if eBPF control group support isn't enabled in the underlying kernel or the container manager. In that case these settings do nothing. So if you need compatibility with systems like that, pls don't rely on them exclusively for IP security. ๐Ÿ™ˆ

Optional. Type uniline.


๐ŸŒธ SocketBindAllow ๐ŸŒธ

This one configures restrictions on whether your unit processes can invoke bind(2) on a socket. You can define both allow and deny rules that restrict which addresses a socket is allowed to bind to.

A bind-rule describes socket properties like address-family, transport-protocol and ip-ports:

bind-rule := { [address-family":"][transport-protocol":"][ip-ports] | "any" }

address-family := { "ipv4" | "ipv6" }

transport-protocol := { "tcp" | "udp" }

ip-ports := { ip-port | ip-port-range }
  • The optional address-family expects "ipv4" or "ipv6" values. If you don't specify it, the rule matches for both IPv4 and IPv6 addresses and gets applied depending on the other socket fields โ€” like transport-protocol, ip-port. โœจ
  • The optional transport-protocol expects the "tcp" or "udp" transport protocol names. If you don't specify it, the rule matches for any transport protocol.
  • The optional ip-port value has to sit within the 1โ€ฆ65535 interval, inclusively โ€” so dynamic port 0 is not allowed. A range of sequential ports is written as ip-port-range := ip-port-low"-"ip-port-high, where ip-port-low is smaller than or equal to ip-port-high and both live within 1โ€ฆ65535 inclusively.
  • The special value "any" can be used to apply a rule to any address family, any transport protocol, and any port with a positive value.

Wanna allow multiple rules? Just assign SocketBindAllow or SocketBindDeny multiple times. Wanna wipe the existing assignments? Pass an empty SocketBindAllow or SocketBindDeny assignment.

For each of SocketBindAllow and SocketBindDeny, the maximum allowed number of assignments is 128. ๐Ÿ”ข

This feature is implemented with the cgroup/bind4 and cgroup/bind6 cgroup-bpf hooks.

โš ๏ธ Big note: these settings apply to any bind(2) system call invocation by the unit processes, regardless of which network namespace they're placed in. Or, put another way: changing the network namespace is not a suitable mechanism for escaping these restrictions on bind(). Sorry bestie, no loophole. ๐Ÿ’€

Examples:

โ€ฆ
# Allow binding IPv6 socket addresses with a port greater than or equal to 10000.
[Service]
SocketBindAllow=ipv6:10000-65535
SocketBindDeny=any
โ€ฆ
# Allow binding IPv4 and IPv6 socket addresses with 1234 and 4321 ports.
[Service]
SocketBindAllow=1234
SocketBindAllow=4321
SocketBindDeny=any
โ€ฆ
# Deny binding IPv6 socket addresses.
[Service]
SocketBindDeny=ipv6
โ€ฆ
# Deny binding IPv4 and IPv6 socket addresses.
[Service]
SocketBindDeny=any
โ€ฆ
# Allow binding only over TCP
[Service]
SocketBindAllow=tcp
SocketBindDeny=any
โ€ฆ
# Allow binding only over IPv6/TCP
[Service]
SocketBindAllow=ipv6:tcp
SocketBindDeny=any
โ€ฆ
# Allow binding ports within 10000-65535 range over IPv4/UDP.
[Service]
SocketBindAllow=ipv4:udp:10000-65535
SocketBindDeny=any
โ€ฆ

Optional. Type uniline.


๐Ÿ’œ SocketBindDeny ๐Ÿ’œ

Okay so this is the twin of the one above โ€” literally the same energy, just the deny side of things. It configures restrictions on the ability of unit processes to invoke bind(2) on a socket. Both allow and deny rules can be defined, restricting which addresses a socket may be bound to.

bind-rule describes socket properties such as address-family, transport-protocol and ip-ports:

bind-rule := { [address-family":"][transport-protocol":"][ip-ports] | "any" }

address-family := { "ipv4" | "ipv6" }

transport-protocol := { "tcp" | "udp" }

ip-ports := { ip-port | ip-port-range }
  • The optional address-family expects "ipv4" or "ipv6" values. Leave it out and the rule matches both IPv4 and IPv6 addresses, applied depending on the other socket fields like transport-protocol, ip-port.
  • The optional transport-protocol expects the "tcp" or "udp" transport protocol names. Leave it out and the rule matches any transport protocol.
  • The optional ip-port value must lie within the 1โ€ฆ65535 interval, inclusively โ€” so a dynamic port of 0 is not allowed. A range of sequential ports is described by ip-port-range := ip-port-low"-"ip-port-high, where ip-port-low is smaller than or equal to ip-port-high and both fall within 1โ€ฆ65535 inclusively.
  • The special value "any" applies a rule to any address family, transport protocol, and any port with a positive value. ๐ŸŒˆ

To allow multiple rules, assign SocketBindAllow or SocketBindDeny multiple times. To clear the existing assignments, pass an empty SocketBindAllow or SocketBindDeny assignment.

For each of SocketBindAllow and SocketBindDeny, the maximum allowed number of assignments is 128.

The feature is implemented with the cgroup/bind4 and cgroup/bind6 cgroup-bpf hooks.

โš ๏ธ Same warning, same energy: these settings apply to any bind(2) system call invoked by the unit processes, no matter which network namespace they're in. Changing the network namespace is not a valid escape hatch for these bind() restrictions.

Examples:

โ€ฆ
# Allow binding IPv6 socket addresses with a port greater than or equal to 10000.
[Service]
SocketBindAllow=ipv6:10000-65535
SocketBindDeny=any
โ€ฆ
# Allow binding IPv4 and IPv6 socket addresses with 1234 and 4321 ports.
[Service]
SocketBindAllow=1234
SocketBindAllow=4321
SocketBindDeny=any
โ€ฆ
# Deny binding IPv6 socket addresses.
[Service]
SocketBindDeny=ipv6
โ€ฆ
# Deny binding IPv4 and IPv6 socket addresses.
[Service]
SocketBindDeny=any
โ€ฆ
# Allow binding only over TCP
[Service]
SocketBindAllow=tcp
SocketBindDeny=any
โ€ฆ
# Allow binding only over IPv6/TCP
[Service]
SocketBindAllow=ipv6:tcp
SocketBindDeny=any
โ€ฆ
# Allow binding ports within 10000-65535 range over IPv4/UDP.
[Service]
SocketBindAllow=ipv4:udp:10000-65535
SocketBindDeny=any
โ€ฆ

Optional. Type uniline.


๐Ÿฆ‹ RestrictNetworkInterfaces ๐Ÿฆ‹

Takes a list of space-separated network interface names, and this option restricts which network interfaces the processes of this unit can use. By default, processes can only use the network interfaces that are listed โ€” that's an allow-list. But if the very first character of the rule is ~, the effect flips: processes can only use network interfaces that are not listed โ€” that's a deny-list. ๐Ÿ˜Œ

This option can appear multiple times, and when it does, the network interface names get merged. If you assign the empty string, the set gets reset and all prior assignments have no effect.

Okay so if you specify both types of this option (allow-listing and deny-listing), the first one encountered takes precedence and dictates the default action (allow vs deny). Then the next occurrences of this option add or delete the listed network interface names from the set, depending on its type and the default action. Iykyk. ๐Ÿ’ซ

Bestie, the loopback interface (lo) is not treated in any special way โ€” you have to configure it explicitly in the unit file. Don't forget it!!

Example 1: allow-list

RestrictNetworkInterfaces=eth1
RestrictNetworkInterfaces=eth2

Programs in the unit will only be able to use the eth1 and eth2 network interfaces.

Example 2: deny-list

RestrictNetworkInterfaces=~eth1 eth2

Programs in the unit will be able to use any network interface but eth1 and eth2.

Example 3: mixed

RestrictNetworkInterfaces=eth1 eth2
RestrictNetworkInterfaces=~eth1

Programs in the unit will only be able to use the eth2 network interface.

Optional. Type uniline.


๐ŸŽ€ BindNetworkInterface ๐ŸŽ€

Takes the name of a network interface, bestie. This option makes every socket created by the processes of this unit get bound to that specified network interface. โœจ

It's especially handy for confining a process to a VRF when the program doesn't offer native support for it. It's equivalent to running the program using ip vrf exec.

On systems using nss-resolve, the interface used for DNS resolution can be chosen with the SYSTEMD_NSS_RESOLVE_IFINDEX environment variable. ๐Ÿงš

The feature is implemented with cgroup/sock_create cgroup-bpf hooks.

Example:

[Service]
BindNetworkInterface=vrf-mgmt

Optional. Type uniline.


๐Ÿ‘‘ NFTSet ๐Ÿ‘‘

This setting gives you a method for integrating dynamic cgroup, user and group IDs into firewall rules with NFT <https://netfilter.org/projects/nftables/index.html> sets. The whole point of using this setting is that you get to use those IDs as selectors in firewall rules super easily, and that in turn allows for more fine-grained filtering. ๐Ÿ’…

NFT rules for cgroup matching use numeric cgroup IDs โ€” which, fun fact, change every single time a service is restarted, making them lowkey impossible to use in a systemd environment otherwise. ๐Ÿ˜ญ

The dynamic and random IDs used by DynamicUser can also be integrated with this setting.

Okay so bestie, this is a whitespace separated list of NFT set definitions. Each definition is a colon-separated tuple of these four things, in this order:

  1. source type โ€” one of cgroup, user, or group
  2. NFT address family โ€” one of arp, bridge, inet, ip, ip6, or netdev
  3. table name
  4. set name

Table names and set names have to follow the lexicographic restrictions of NFT table names (yes, really, they're picky like that ๐Ÿ™ˆ). And the type of the element used in the NFT filter must match the type implied by the directive (cgroup, user or group) as shown in the table below.

When a control group or a unit gets realized, the corresponding ID gets appended to the NFT sets ๐Ÿ’ซ โ€” and it gets removed again when that control group or unit is removed. Important vibe: systemd only inserts elements to (or removes from) the sets. It does not build the whole thing for you. So the related NFT rules, tables and sets have to be prepared elsewhere, in advance. And if managing the sets fails? Those failures are ignored. ๐Ÿ’€

If the firewall rules get reinstalled and the contents of the NFT sets are destroyed, you can hit systemctl daemon-reload to refill the sets. Slay.

Example:

[Unit]
NFTSet=cgroup:inet:filter:my_service user:inet:filter:serviceuser

Corresponding NFT rules:

table inet filter {
set my_service {
type cgroupsv2
}
set serviceuser {
typeof meta skuid
}
chain x {
socket cgroupv2 level 2 @my_service accept
drop
}
chain y {
meta skuid @serviceuser accept
drop
}
}

I< Optional. Type uniline. >


๐Ÿ’– IPIngressFilterPath ๐Ÿ’–

This one lets you add custom network traffic filters implemented as BPF programs, applying to all IP packets sent and received over AF_INET and AF_INET6 sockets. It takes an absolute path to a pinned BPF program in the BPF virtual filesystem (/sys/fs/bpf/) โ€” that path bit is non-negotiable, bestie ๐Ÿซถ.

The filters configured with this option get applied to all sockets created by processes of this unit (or, in the case of socket units, associated with it). They're loaded in addition to the filters of any parent slice units this unit might be a member of, plus any IPAddressAllow and IPAddressDeny filters in any of those units. By default? No filters specified. Chill vibes โœจ.

If you use these settings multiple times in the same unit, all the specified programs get attached. But if you assign an empty string to these settings, the program list gets reset and all previously specified programs are ignored. Clean slate, periodt.

If the path BPF_FS_PROGRAM_PATH in an IPIngressFilterPath assignment is already being handled by a BPFProgram ingress hook, like "BPFProgram"="ingress":BPF_FS_PROGRAM_PATH, the assignment is still considered valid and the program gets attached to a cgroup anyway. Same deal for the IPEgressFilterPath path and the egress hook. Teamwork ๐Ÿ’….

Heads up for socket-activated services: the IP filter programs configured on the socket unit apply to all sockets associated with it directly, but not to any sockets created by the ultimately activated services for it. And the other way around โ€” the IP filter programs configured for the service are not applied to any sockets passed into the service via socket activation. So basically, it's usually a good idea to replicate the IP filter programs on both the socket and the service unit. That said, it often makes sense to keep one configuration more open and the other more restricted, depending on your use case. iykyk ๐ŸŒธ
Also note these settings might not be supported on some systems โ€” for example if eBPF control group support isn't enabled in the underlying kernel or container manager. In that case these settings will have no effect ๐Ÿ˜ญ. If you want compatibility with such systems, the recommended move is to attach your filter manually (which requires Delegate=yes) instead of using this setting.

Optional. Type uniline.


๐ŸŒ™ IPEgressFilterPath ๐ŸŒ™

Samesies energy: this adds custom network traffic filters implemented as BPF programs, applying to all IP packets sent and received over AF_INET and AF_INET6 sockets. Takes an absolute path to a pinned BPF program in the BPF virtual filesystem (/sys/fs/bpf/).

The filters configured with this option are applied to all sockets created by processes of this unit (or in the case of socket units, associated with it). The filters are loaded in addition to filters of any of the parent slice units this unit might be a member of, as well as any IPAddressAllow and IPAddressDeny filters in any of these units. By default, there are no filters specified.

If these settings are used multiple times in the same unit, all the specified programs are attached. If an empty string is assigned to these settings, the program list is reset and all previously specified programs ignored ๐Ÿ’ซ.

If the path BPF_FS_PROGRAM_PATH in IPIngressFilterPath assignment is already being handled by BPFProgram ingress hook, e.g. "BPFProgram"="ingress":BPF_FS_PROGRAM_PATH, the assignment will be still considered valid and the program will be attached to a cgroup. Same for IPEgressFilterPath path and egress hook.

Same warning as above, and it matters just as much: for socket-activated services, the IP filter programs configured on the socket unit apply to all sockets associated with it directly, but not to any sockets created by the ultimately activated services for it. Conversely, the IP filter programs configured for the service are not applied to any sockets passed into the service via socket activation. So it is usually a good idea to replicate the IP filter programs on both the socket and the service unit โ€” though it often makes sense to maintain one configuration more open and the other one more restricted, depending on the use case.
And again: these settings might not be supported on some systems (for example if eBPF control group support is not enabled in the underlying kernel or container manager). These settings will have no effect in that case. If compatibility with such systems is desired, it's hence recommended to attach your filter manually (requires Delegate=yes) instead of using this setting.

Optional. Type uniline.


๐Ÿ‘‘ BPFProgram ๐Ÿ‘‘

BPFProgram lets you attach custom BPF programs to the cgroup of a unit. (It's basically the glow-up generalization of what IPEgressFilterPath and IPIngressFilterPath expose, just for other hooks too ๐Ÿ’….) Cgroup-bpf hooks in the form of BPF programs loaded to the BPF filesystem are attached with cgroup-bpf attach flags determined by the unit. For all the deets about attachment types and flags, see bpf.h (https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/plain/include/uapi/linux/bpf.h). Also go peek at the general BPF documentation (https://docs.kernel.org/bpf/).

The specification of a BPF program is a pair of BPF program type and program path in the file system, with : as the separator:

type:program-path

The BPF program type is equivalent to the BPF attach type used in bpftool(8). It may be one of:

egress, ingress, sock_create, sock_ops, device, bind4, bind6, connect4, connect6, post_bind4, post_bind6, sendmsg4, sendmsg6, sysctl, recvmsg4, recvmsg6, getsockopt, or setsockopt.

That's the whole roster, no cap ๐ŸŒˆ.

The specified program path must be an absolute path referencing a BPF program inode in the bpffs file system (which generally means it must begin with /sys/fs/bpf/). If a specified program doesn't exist yet (i.e. it hasn't been uploaded to the BPF subsystem of the kernel yet), it will not be installed โ€” but unit activation will continue anyway, and a warning gets printed to the logs. Not a disaster, just a lil note in the group chat ๐Ÿ˜ญ.

Setting BPFProgram to an empty value makes previous assignments ineffective.

Multiple assignments of the same program type/path pair have the same effect as a single assignment: the program will be attached just once. No dupes, bestie.

If BPF egress pinned to program-path path is already being handled by IPEgressFilterPath, the BPFProgram assignment will be considered valid and BPFProgram will be attached to a cgroup. Similarly for ingress hook and IPIngressFilterPath assignment. ๐Ÿซถ

BPF programs passed with BPFProgram are attached to the cgroup of a unit with BPF attach flag multi, which allows further attachments of the same type within the cgroup hierarchy topped by the unit cgroup.

Examples:

BPFProgram=egress:/sys/fs/bpf/egress-hook
BPFProgram=bind6:/sys/fs/bpf/sock-addr-hook

Optional. Type uniline.


๐ŸŽ€ DeviceAllow ๐ŸŽ€

This one controls access to specific device nodes by the executed processes. It takes two space-separated strings: a device node specifier followed by a combination of r, w, m to control reading, writing, or creation of the specific device nodes by the unit (mknod), respectively. This functionality is implemented using eBPF filtering โœจ.

When access to all physical devices should be disallowed, PrivateDevices may be used instead. See systemd.exec(5).

The device node specifier is either:

  • a path to a device node in the file system, starting with /dev/, or
  • a string starting with either char- or block- followed by a device group name, as listed in /proc/devices

That second form is the useful one if you wanna allow-list every current and future device belonging to a specific device group all at once ๐Ÿ‘‘. The device group is matched according to filename globbing rules, so you may use the * and ? wildcards. (Note that such globbing wildcards are not available for device node path specifications!) If you want to match device nodes by numeric major/minor, use device node paths in the /dev/char/ and /dev/block/ directories. But honestly? Matching devices by major/minor is generally not recommended, because assignments are neither stable nor portable between systems or different kernel versions. It's giving fragile ๐Ÿ’€.

Examples: /dev/sda5 is a path to a device node, referring to an ATA or SCSI block device. char-pts and char-alsa are specifiers for all pseudo TTYs and all ALSA sound devices, respectively. char-cpu/* is a specifier matching all CPU related device groups. ๐Ÿฆ‹

Note that allow lists defined this way should only reference device groups which are resolvable at the time the unit is started. Any device groups not resolvable then are not added to the device allow list. To work around this limitation, consider extending service units with a pair of After=modprobe@xyz.service and Wants=modprobe@xyz.service lines that load the necessary kernel module implementing the device group if missing.

Example:

โ€ฆ
[Unit]
Wants=modprobe@loop.service
After=modprobe@loop.service
[Service]
DeviceAllow=block-loop
DeviceAllow=/dev/loop-control
โ€ฆ

Optional. Type list of uniline.


๐ŸŒธ DevicePolicy ๐ŸŒธ

Control the policy for allowing device access:

Optional. Type enum. choice: 'auto', 'closed', 'strict'.

๐ŸงŠ Slice

Where does this unit live in the slice tree, bestie? This is the name of the slice unit we're dropping the unit into. It defaults to system.slice for all non-instantiated units of all unit types โ€” except slice units themselves, see below babe ๐Ÿ’… โ€” and instance units default to being placed in a subslice of system.slice named after the template name.

You can use this to arrange systemd units in a hierarchy of slices, each of which might have resource settings applied. Iconic little nesting situation ๐Ÿช†โœจ

For units of type slice, the only accepted value here is the parent slice. And since the name of a slice unit already implies its parent slice, setting this parameter directly for slice units is straight up redundant, no cap.

โš ๏ธ Real talk: be super careful when you're relying on the default slice assignment in templated service units that have DefaultDependencies=no set. Peep systemd.service(5), section "Default Dependencies" for the deets ๐Ÿ™ˆ

Optional. Type uniline.


๐ŸŽ€ Delegate

This one turns on delegation of further resource control partitioning to the processes of the unit. Units where it's enabled can create and manage their own private subhierarchy of control groups below the control group of the unit itself. And for unprivileged services (like, the ones using the User setting) the unit's control group gets made accessible to the relevant user. Giving main character energy to your cgroups fr fr ๐Ÿ‘‘

When it's enabled, the service manager will refrain from manipulating control groups or moving processes below the unit's control group, so that a clear concept of ownership is established: the control group tree at the level of the unit's control group and above (i.e. towards the root control group) is owned and managed by the service manager of the host ๐ŸŒˆ, while the control group tree below the unit's control group is owned and managed by the unit itself. Clean boundaries, we love to see it.

It takes either a boolean argument or a (possibly empty) list of control group controller names:

  • If true, delegation is turned on and all supported controllers are enabled for the unit, making them available to the unit's processes for management โœจ
  • If false, delegation is turned off entirely (and no additional controllers are enabled) ๐Ÿ’€
  • If set to a list of controllers, delegation is turned on and the specified controllers are enabled for the unit.

Assigning the empty string will enable delegation, but reset the list of controllers, and all assignments prior to this will have no effect. Also note that additional controllers other than the ones you specified might be made available as well, depending on the configuration of the containing slice unit or other units contained in it. Defaults to false.

๐Ÿšจ Note that controller delegation to less privileged code is only safe on the unified control group hierarchy. Accordingly, access to the specified controllers will not be granted to unprivileged services on the legacy hierarchy, even when requested. Not the vibe you want, so heads up.

Not all of these controllers are available on all kernels though, and some are specific to the unified hierarchy while others are specific to the legacy hierarchy. Also note the kernel might support further controllers that aren't covered here yet, because delegation is either not supported at all for them or not defined cleanly ๐Ÿ˜ญ

And because of the hierarchical nature of cgroup hierarchy, any controllers that are delegated will be enabled for the parent and sibling units of the unit with delegation. It's giving family group chat, everyone's involved ๐Ÿซถ

For further details on the delegation model consult Control Group APIs and Delegation https://systemd.io/CGROUP_DELEGATION. Optional. Type uniline.


๐Ÿฆ‹ DelegateSubgroup

Puts the unit processes in the specified subgroup of the unit's control group. Takes a valid control group name (not a path!) as its parameter, or an empty string to turn this feature off. Defaults to off.

The control group name must be usable as a filename and must avoid conflicts with the kernel's control group attribute files โ€” i.e. cgroup.procs is not an acceptable name, since the kernel exposes a native control group attribute file by that name. Don't fight the kernel on this one babe ๐Ÿ˜ค

This option has no effect unless control group delegation is turned on via Delegate (see above). And note this setting only applies to the "main" processes of a unit, i.e. for services to ExecStart, but not for ExecReload and similar. If delegation is enabled, the latter are always placed inside a subgroup named .control. The specified subgroup is automatically created (and potentially ownership is passed to the unit's configured user/group) when a process is started in it โœจ

This option is useful to avoid manually moving the invoked process into a subgroup after it's been started. Since no processes should live in inner nodes of the control group tree, it's almost always necessary to run the main ("supervising") process of a unit that has delegation turned on in a subgroup. Like, it's just the rules ๐Ÿ’…

Optional. Type uniline.


๐ŸŒธ DisableControllers

Disables controllers from being enabled for a unit's children. If a controller you listed is already in use in its subtree, the controller will be removed from the subtree. This can be used to avoid configuration in child units from being able to implicitly or explicitly enable a controller. Defaults to empty.

Multiple controllers may be specified, separated by spaces. You may also pass DisableControllers multiple times, in which case each new instance adds another controller to disable. Passing DisableControllers by itself with no controller name present resets the disabled controller list. Not me resetting the whole list with zero warning ๐Ÿ™ˆ

It may not be possible to disable a controller after units have been started, if the unit or any child of the unit in question delegates controllers to its children, as any delegated subtree of the cgroup hierarchy is unmanaged by systemd.

Optional. Type uniline.


๐Ÿ’€ ManagedOOMSwap

Specifies how systemd-oomd.service(8) will act on this unit's cgroups. Defaults to auto.

When set to kill, the unit becomes a candidate for monitoring by systemd-oomd. If the cgroup passes the limits set by oomd.conf(5) or the unit configuration, systemd-oomd will select a descendant cgroup and send SIGKILL to all of the processes under it. You can find more details on candidates and kill behavior at systemd-oomd.service(8) and oomd.conf(5).

Setting either of these properties to kill will also result in After and Wants dependencies on systemd-oomd.service unless DefaultDependencies=no.

When set to auto, systemd-oomd will not actively use this cgroup's data for monitoring and detection. However, if an ancestor cgroup has one of these properties set to kill, a unit with auto can still be a candidate for systemd-oomd to terminate. Sis is on the list whether she knows it or not ๐Ÿ˜ญ

Optional. Type enum. choice: 'auto', 'kill'.


๐Ÿซถ ManagedOOMMemoryPressure

Specifies how systemd-oomd.service(8) will act on this unit's cgroups. Defaults to auto.

When set to kill, the unit becomes a candidate for monitoring by systemd-oomd. If the cgroup passes the limits set by oomd.conf(5) or the unit configuration, systemd-oomd will select a descendant cgroup and send SIGKILL to all of the processes under it. You can find more details on candidates and kill behavior at systemd-oomd.service(8) and oomd.conf(5).

Setting either of these properties to kill will also result in After and Wants dependencies on systemd-oomd.service unless DefaultDependencies=no.

When set to auto, systemd-oomd will not actively use this cgroup's data for monitoring and detection. However, if an ancestor cgroup has one of these properties set to kill, a unit with auto can still be a candidate for systemd-oomd to terminate. Bestie is catching strays from the ancestor units, and honestly? Same.

Optional. Type enum. choice: 'auto', 'kill'.


๐Ÿ“ ManagedOOMMemoryPressureLimit

Overrides the default memory pressure limit set by oomd.conf(5) for the cgroup of this unit. Takes a percentage value between 0% and 100%, inclusive. Defaults to 0%, which means to use the default set by oomd.conf(5).

โš ๏ธ This property is ignored unless ManagedOOMMemoryPressure=kill.

Optional. Type uniline.


โญ ManagedOOMMemoryPressureDurationSec

Overrides the default memory pressure duration set by oomd.conf(5) for the cgroup of this unit. The specified value supports a time unit such as ms or ฮผs, see systemd.time(7) for details on the permitted syntax. Must be set to either empty or a value of at least 1s. Defaults to empty, which means to use the default set by oomd.conf(5).

โš ๏ธ This property is ignored unless ManagedOOMMemoryPressure=kill.

Optional. Type uniline.


๐Ÿ‘‘ ManagedOOMPreference

Allows deprioritizing or omitting this unit's cgroup as a candidate when systemd-oomd needs to act. Requires support for extended attributes (see xattr(7)) in order to use avoid or omit.

When calculating candidates to relieve swap usage, systemd-oomd will only respect these extended attributes if the unit's cgroup is owned by root.

When calculating candidates to relieve memory pressure, systemd-oomd will only respect these extended attributes if the unit's cgroup is owned by root, or if the unit's cgroup owner and the owner of the monitored ancestor cgroup are the same. For example, if systemd-oomd is calculating candidates for -.slice, then extended attributes set on descendants of /user.slice/user-1000.slice/user@1000.service/ will be ignored, because the descendants are owned by UID 1000, and -.slice is owned by UID 0. But, if calculating candidates for /user.slice/user-1000.slice/user@1000.service/, then extended attributes set on the descendants would be respected. Ownership matching is the whole plot twist ๐Ÿ”

If this property is set to avoid, the service manager will convey this to systemd-oomd, which will only select this cgroup if there are no other viable candidates.

If this property is set to omit, the service manager will convey this to systemd-oomd, which will ignore this cgroup as a candidate and will not perform any actions on it.

๐Ÿ’… It is recommended to use avoid and omit sparingly, as it can adversely affect systemd-oomd's kill behavior. Also note that these extended attributes are not applied recursively to cgroups under this unit's cgroup.

Defaults to none, which means systemd-oomd will rank this unit's cgroup as defined in systemd-oomd.service(8) and oomd.conf(5).

Optional. Type enum. choice: 'avoid', 'none', 'omit'.

MemoryPressureWatch ๐ŸŒธ

Okay so this one controls memory pressure monitoring for the processes you invoke, bestie. It takes a boolean, or one of auto and skip. Here's the tea:

  • If you say no, you're telling the service not to watch for memory pressure events โ€” it does this by setting the $MEMORY_PRESSURE_WATCH environment variable to the literal string "/dev/null".
  • If you say yes, you're telling the service to watch for memory pressure events. This enables memory accounting for the service, and makes sure the "memory.pressure" cgroup attribute file is accessible for reading and writing by the service's user. It then sets the $MEMORY_PRESSURE_WATCH environment variable (for processes invoked by the unit) to the file system path to this file. The threshold info you configured with "MemoryPressureThresholdSec" gets encoded in the $MEMORY_PRESSURE_WRITE environment variable.
  • If you set auto, the protocol is enabled if memory accounting is anyway enabled for the unit, and disabled otherwise.
  • If you set skip, the logic is neither enabled nor disabled, and those two environment variables are just... not set. ๐Ÿ™ˆ
Ngl, services are totally free to use those two environment variables, but it's unproblematic if they ignore them. Memory pressure handling has to be implemented individually in each service, and it usually means different things for different software.

For further details on memory pressure handling, see Memory Pressure Handling in systemd <https://systemd.io/MEMORY_PRESSURE>.

Services implemented using sd-event(3) may use sd_event_add_memory_pressure(3) to watch for and handle memory pressure events.

If not explicitly set, this defaults to the "DefaultMemoryPressureWatch" setting in systemd-system.conf(5). Optional. Type enum. choice: 'auto', 'no', 'skip', 'yes'. โœจ


MemoryPressureThresholdSec โฑ๏ธ

Sets the memory pressure threshold time for memory pressure monitor, as configured via "MemoryPressureWatch". It specifies the max allocation latency before a memory pressure event gets signalled to the service, per 2s window. If you don't specify it, it defaults to the "DefaultMemoryPressureThresholdSec" setting in systemd-system.conf(5) (which in turn defaults to 200ms). The value you give it expects a time unit like "ms" or "ฮผs" โ€” see systemd.time(7) for the details on the permitted syntax. Optional. Type uniline.


CoredumpReceive ๐Ÿ’€

Takes a boolean argument. This setting enables coredump forwarding for containers that belong to this unit's cgroup. Units with "CoredumpReceive=yes" must also be configured with "Delegate=yes". Defaults to false.

So when systemd-coredump is handling a coredump for a process from a container, if the container's leader process is a descendant of a cgroup with "CoredumpReceive=yes" and "Delegate=yes", then systemd-coredump will attempt to forward the coredump to systemd-coredump within the container. See also systemd-coredump(8). Optional. Type boolean.

upstream_default value:

no

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. Optional. Type list of uniline.


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, it 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. Heads up: setting this parameter might result in additional dependencies being added to the unit (see above ๐Ÿ‘€). Optional. Type uniline.


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, using the pivot_root(2) or chroot(2) system call. If you use this, you gotta make sure the process binary and all its auxiliary files are available in the new root. Also note: setting this parameter might result in additional dependencies being 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.

Lowkey important: 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. Optional. Type uniline.


RootImage ๐Ÿ“€

Takes a path to a block device node or regular file as argument. This call is similar to "RootDirectory", however it 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:

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. Optional. Type uniline.


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 <https://uapi-group.org/specifications/specs/discoverable_partitions_specification>: "root", "usr", "home", "srv", "esp", "xbootldr", "tmp", "var". Optional. Type uniline.


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. Optional. Type boolean.


RootHash ๐Ÿ”

Takes a data integrity (dm-verity) root hash specified in hexadecimal, or the path to a file containing a root hash in ASCII hexadecimal format. This option enables data integrity checks using dm-verity, if the used image contains the appropriate integrity data (see above) or if "RootVerity" is used. The specified hash must match the root hash of integrity data, and is usually at least 256 bits (and hence 64 formatted hexadecimal characters) long (in case of SHA256 for example). If this option is not specified, but the image file carries the "user.verity.roothash" extended file attribute (see xattr(7)), then the root hash is read from it, also as formatted hexadecimal characters. If the extended file attribute is not found (or is not supported by the underlying file system), but a file with the ".roothash" suffix is found next to the image file, bearing otherwise the same name (except if the image has the ".raw" suffix, in which case the root hash file must not have it in its name), the root hash is read from it and automatically used, also as formatted hexadecimal characters.

If the disk image contains a separate "/usr/" partition it may also be Verity protected, in which case the root hash may be configured via an extended attribute "user.verity.usrhash" or a ".usrhash" file adjacent to the disk image. There's currently no option to configure the root hash for the "/usr/" file system via the unit file directly. Optional. Type uniline.


RootHashSignature โœ๏ธ

Takes a PKCS7 signature of the "RootHash" option as a path to a DER-encoded signature file, or as an ASCII base64 string encoding of a DER-encoded signature prefixed by "base64:". The dm-verity volume will only be opened if the signature of the root hash is valid and signed by a public key present in the kernel keyring. If this option is not specified, but a file with the ".roothash.p7s" suffix is found next to the image file, bearing otherwise the same name (except if the image has the ".raw" suffix, in which case the signature file must not have it in its name), the signature is read from it and automatically used.

If the disk image contains a separate /usr/ partition it may also be Verity-protected, and in that case the signature for the root hash can be configured via a .usrhash.p7s file sitting right next to the disk image. Bestie, hear me: there is currently no option to configure the root hash signature for the /usr/ via the unit file directly. That's just how it is. Optional. Type uniline.


๐Ÿฆ‹ RootVerity

Takes the path to a data integrity (dm-verity) file. This option switches on data integrity checks using dm-verity โ€” but only if RootImage is used, and a root-hash is passed, and the image itself doesn't already contain the integrity data. And the integrity data has to be matched by the root hash, no exceptions. โœจ

If you don't specify this option, but a file with the .verity suffix is found next to the image file, bearing otherwise the same name (except if the image has the .raw suffix, in which case the verity data file must not have it in its name), then the verity data gets read from it and used automatically. Freebie behaviour, we love it.

This option is supported only for disk images that contain a single file system, with no enveloping partition table. Images that contain a GPT partition table should instead include both the root file system and the matching Verity data in the same image, implementing the Discoverable Partitions Specification <https://uapi-group.org/specifications/specs/discoverable_partitions_specification>. Optional. Type uniline.


๐ŸŒธ RootImagePolicy

Takes an image policy string as per systemd.image-policy(7) to use when mounting the disk images (DDI) specified in RootImage, MountImage, ExtensionImage, respectively. If you don't specify it, the following policy string is the default for RootImagePolicy and MountImagePolicy:

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

The default policy for ExtensionImagePolicy is:

root=verity+signed+encrypted+unprotected+absent: \
usr=verity+signed+encrypted+unprotected+absent

Optional. Type uniline.


๐ŸŽ€ MountImagePolicy

Takes an image policy string as per systemd.image-policy(7) to use when mounting the disk images (DDI) specified in RootImage, MountImage, ExtensionImage, respectively. If you don't specify it, the following policy string is the default for RootImagePolicy and MountImagePolicy:

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

The default policy for ExtensionImagePolicy is:

root=verity+signed+encrypted+unprotected+absent: \
usr=verity+signed+encrypted+unprotected+absent

Optional. Type uniline.


๐Ÿ‘‘ ExtensionImagePolicy

Takes an image policy string as per systemd.image-policy(7) to use when mounting the disk images (DDI) specified in RootImage, MountImage, ExtensionImage, respectively. If you don't specify it, the following policy string is the default for RootImagePolicy and MountImagePolicy:

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

The default policy for ExtensionImagePolicy is:

root=verity+signed+encrypted+unprotected+absent: \
usr=verity+signed+encrypted+unprotected+absent

Optional. Type uniline.

okay bestie I know those three paragraphs are word-for-word the same ๐Ÿ˜ญ that's literally what the original does, I'm not gonna gaslight you and pretend they're different. Three options, one shared blurb, that's the vibe. ๐Ÿ’€

๐Ÿงš RootMStack

Takes a path to a systemd.mstack(7) directory encapsulating a mount stack made out of layers and bind mounts. Similar to RootDirectory and RootImage, this runs the service off a distinct root file system โ€” except in this case it's set up via overlayfs. Iconic layered-oufit energy. ๐Ÿ’…

Since .mstack/ directories may reference disk images (DDIs), similar device policy extensions and dependencies are in effect when RootMStack is used as are if RootImage is used. Optional. Type uniline.


โญ MountAPIVFS

Takes a boolean argument. If on, a private mount namespace for the unit's processes is created, and the API file systems /proc/, /sys/, /dev/ and /run/ (as an empty tmpfs) are mounted inside of it โ€” unless they're already mounted. Note that this option has no effect unless it's used together with RootDirectory/RootImage, because those four mounts are generally mounted on the host anyway, and unless the root directory is changed, the private mount namespace will just be a 1:1 copy of the host's and will include these four mounts. Also note: the /dev/ file system of the host is bind mounted if this option is used without PrivateDevices. So if you want the service running with a private, minimal version of /dev/, combine this option with PrivateDevices. ๐Ÿซถ

In order to allow propagating mounts 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. Optional. Type boolean.


๐Ÿ“ BindLogSockets

Takes a boolean argument. If true, sockets from systemd-journald.socket(8) will be bind mounted into the mount namespace. This is particularly useful when a different instance of /run/ is employed, so that processes running in the namespace can still make use of sd-journal(3). Very thoughtful of them ngl.

This option is implied when LogNamespace is used, when MountAPIVFS=yes, or when PrivateDevices=yes is used in conjunction with either RootDirectory or RootImage. Optional. Type boolean.


๐Ÿ’€ ProtectProc

Takes one of noaccess, invisible, ptraceable or default (which it defaults to, obviously, it's the default ๐Ÿ’…). When set, this controls the hidepid= mount option of the procfs instance for the unit, which 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 be ptrace()'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 <https://docs.kernel.org/filesystems/proc.html#mount-options>. It's generally recommended to run most system services with this option set to invisible. โœจ

This option is implemented via file system namespacing, and thus cannot be used with services that shall be able to install mount points in the host file system hierarchy. Heads up: the root user is unaffected by this option, so to actually make it effective it has to be used together with User or DynamicUser=yes, and also without the CAP_SYS_PTRACE capability โ€” because that capability also lets a process bypass this whole feature. ๐Ÿ’€ It cannot be used for services that need to access metainformation about other users' processes. This option implies MountAPIVFS.

If the kernel does not support per-mount point hidepid= mount options, this setting justโ€ฆ remains without effect, and the unit's processes will be able to access and see other processes as if the option was never used. Optional. Type enum. choice: 'default', 'invisible', 'noaccess', 'ptraceable'.


๐Ÿ”ฅ ProcSubset

Takes one of all (the default) and pid. If 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 /proc Filesystem <https://docs.kernel.org/filesystems/proc.html#mount-options>.

Note that Linux exposes various kernel APIs via /proc/, which get made unavailable with this setting. And since those APIs are used frequently, this option is genuinely useful only in a few specific cases โ€” it is not suitable for most non-trivial programs. Like, don't do it bestie. ๐Ÿ˜ญ

Much like ProtectProc above, this is implemented via file system mount namespacing, and hence the exact same restrictions apply: it's only available to system services, it disables mount propagation to the host mount table, and it implies MountAPIVFS. Also, just like ProtectProc, this setting is gracefully disabled if the kernel in use doesn't support the subset= mount option of procfs. Optional. Type enum. choice: 'all', 'pid'.


๐ŸŒˆ BindPaths

Configures unit-specific bind mounts. A bind mount makes a particular file or directory available at an additional place in the unit's view of the file system. Any bind mounts created with this option are specific to the unit and are not visible in the host's mount table. Sneaky, but make it legal. ๐Ÿ’œ

This option expects a whitespace separated list of bind mount definitions. Each definition consists of a colon-separated triple of source path, destination path and option string, where the latter two are optional. If only a source path is specified, the source and destination are taken to be the same. The option string may be either rbind or norbind, for configuring a recursive or non-recursive bind mount. If the destination path is omitted, the option string must be omitted too. Each bind mount definition may be prefixed with -, in which case it will be ignored when its source path does not exist or is not accessible. ๐ŸŽ€

BindPaths creates regular writable bind mounts (unless the source file system mount is already marked read-only), while BindReadOnlyPaths creates read-only bind mounts. These settings may be used more than once, and each usage appends to the unit's list of bind mounts.

โš ๏ธ If the empty string is assigned to either of these two options, the entire list of bind mounts defined prior to this is reset. And note that in this case both read-only and regular bind mounts are reset, regardless of which of the two settings is used. So like, don't do that by accident, fr fr.

Using this option implies that a mount namespace is allocated for the unit, i.e. it implies the effect of PrivateMounts (see below).

This option is particularly useful when RootDirectory/RootImage is used. In this case, the source path refers to a path on the host file system, while the destination path refers to a path below the root directory of the unit.

Note that the destination directory must exist, or systemd must be able to create it. So it's not possible to use these options for mount points nested underneath paths specified in InaccessiblePaths, or under /home/ and other protected directories if ProtectHome=yes is specified. TemporaryFileSystem with :ro or ProtectHome=tmpfs should be used instead. Optional. Type list of uniline.

BindReadOnlyPaths ๐ŸŽ€

okay so this one configures unit-specific bind mounts, bestie. A bind mount is literally just making a particular file or directory show up at an additional spot in the unit's view of the file system. The way that any bind mounts made with this option are specific to the unit and not visible in the host's mount table? iconic, no notes. ๐Ÿ’…

This option expects a whitespace separated list of bind mount definitions. Each definition is a colon-separated triple of source path, destination path, and option string โ€” where those last two are optional. If you only give a source path, the source and destination are taken to be the same. The option string can be either "rbind" or "norbind" for configuring a recursive or non-recursive bind mount. And if the destination path is omitted, the option string has to be omitted too. ๐Ÿ‘‘

Each bind mount definition may be prefixed with -, in which case it will be ignored when its source path does not exist or is not accessible.

So "BindPaths" creates regular writable bind mounts (unless the source file system mount is already marked read-only), while "BindReadOnlyPaths" creates read-only bind mounts. These settings may be used more than once, and each usage appends to the unit's list of bind mounts. If the empty string is assigned to either of these two options, the entire list of bind mounts defined prior to this is reset. ๐ŸŒธ

and like, note that in this case, both read-only and regular bind mounts are reset, regardless of which of the two settings you used. so be careful, fr fr ๐Ÿ˜ญ

Using this option implies that a mount namespace is allocated for the unit, i.e. it implies the effect of "PrivateMounts" (see below).

This option is particularly useful when "RootDirectory"/"RootImage" is used. In this case, the source path refers to a path on the host file system, while the destination path refers to a path below the root directory of the unit.

Note that the destination directory must exist or systemd must be able to create it. Thus, it is not possible to use those options for mount points nested underneath paths specified in "InaccessiblePaths", or under "/home/" and other protected directories if "ProtectHome=yes" is specified. "TemporaryFileSystem" with ":ro" or "ProtectHome=tmpfs" should be used instead. Optional. Type list of uniline.


MountImages โœจ

This setting is similar to "RootImage" in that it mounts a file system hierarchy from a block device node or loopback file, but the destination directory can be specified as well as mount options. This option expects a whitespace separated list of mount definitions. Each definition consists of a colon-separated tuple of source path and destination definitions, optionally followed by another colon and a list of mount options. ๐Ÿฆ‹

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 "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 regular file. If source or destination contain 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 mount paths. If the empty string is assigned, the entire list of mount paths defined prior to this is reset.

Note that the destination directory must exist or systemd must be able to create it. Thus, it is not possible to use those options for mount points nested underneath paths specified in "InaccessiblePaths", or under "/home/" and other protected directories if "ProtectHome=yes" is 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, "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". Optional. Type list of uniline.


ExtensionImages ๐Ÿงš

This setting is similar to "MountImages" in that it mounts a file system hierarchy from a block device node or loopback file, but instead of providing a destination path, an overlay will be set up. This option expects a whitespace separated list of mount definitions. Each definition consists of 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 "/usr/" and "/opt/" hierarchies for sysext images and "/etc/" hierarchy for confext images. The order in which the images are listed will determine the order in which the overlay is laid down: images specified first to last will result in overlayfs layers bottom to top. ๐Ÿฅž

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 "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 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.

Each sysext image must carry a "/usr/lib/extension-release.d/extension-release.IMAGE" file while each confext image must carry a "/etc/extension-release.d/extension-release.IMAGE" file, with the appropriate metadata which matches "RootImage"/"RootDirectory" or the host. See: os-release(5). To disable the safety check that the extension-release file name matches the image file name, the "x-systemd.relax-extension-release-check" mount option may be appended. ๐Ÿ‘‘

If a service employs this option with systemd.v(7), and has "RefreshOnReload=extensions" enabled (the default), the confexts will be refreshed to pick up any changes on service reload. This only applies to confext extensions. Note that in case 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, "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". Optional. Type list of uniline.


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. โœจ

A read-only OverlayFS will be set up on top of "/usr/" and "/opt/" hierarchies for sysext images and "/etc/" hierarchy for confext images. The order in which the directories are listed will determine the order in which the overlay is laid down: directories specified first to last will result in overlayfs layers bottom to top. ๐ŸŒˆ

Each directory listed in "ExtensionDirectories" may be prefixed with -, in which case it will be ignored when its source path does not exist. 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 directories paths. If the empty string is assigned, the entire list of mount paths defined prior to this is reset.

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 which matches "RootImage"/"RootDirectory" or the host. See: os-release(5). ๐Ÿ“–

If a service employs this option with systemd.v(7), and has "RefreshOnReload=extensions" enabled (the default), the confexts will be refreshed to pick up any changes on service reload. This only applies to confext extensions. Note that in case 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.

Note that usage from user units requires overlayfs support in unprivileged user namespaces, which was first introduced in kernel v5.11. Optional. Type list of uniline. ๐Ÿง


User ๐Ÿ‘‘

Set the UNIX user or group that the processes are executed as, respectively. Takes a single user or group name, or a numeric ID as argument. For system services (services run by the system service manager, i.e. 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 "User" may be used to specify a different user. For user services of any other user, switching user identity is not permitted, hence 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. This setting does not affect commands whose command line is prefixed with "+". ๐Ÿ’–

okay so real talk about the name police ๐Ÿšจ this thing only enforces weak restrictions on the user/group name syntax, but it will generate warnings in lots of cases where user/group names don't follow these rules: the name you specify should consist only of the characters a-z, A-Z, 0-9, "_" and "-" โ€” with one exception, the first character, which has to be one of a-z, A-Z and "_" (so digits and "-" are not permitted as the first character, bestie ๐Ÿ’€). The user/group name must have at least one character, and at most 31. Why are we like this? To avoid ambiguities and to make sure user/group names and unit files stay portable among Linux systems. Want the full tea on which names get accepted and which names get warned about? See User/Group Name Syntax at https://systemd.io/USER_NAMES. ๐Ÿ‘‘

When it's used together with DynamicUser, the user/group name you specified gets dynamically allocated at the time the service is started, and released at the time the service is stopped โ€” unless it's already allocated statically (more on that 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 starts, for example using the sysusers.d(5) facility, which is applied at boot or package install time. If the user doesn't 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. You can add extra groups through the SupplementaryGroups setting (see below ๐Ÿ‘‡).

Optional. Type uniline.


๐ŸŒธ 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 argument. For system services (services run by the system service manager, i.e. 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 User may be used to specify a different user. For user services of any other user, switching user identity is 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. This setting does not affect commands whose command line is prefixed with +. ๐Ÿซถ

and yes bestie, the name rules from up there apply here too, it's the same energy:

Note that this enforces only weak restrictions on the user/group name syntax, but will generate warnings in many cases where user/group names do not 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 first character). The user/group name must have at least one character, and at most 31. These restrictions are made in order 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 at https://systemd.io/USER_NAMES. ๐Ÿฆ‹

When used in conjunction with DynamicUser the user/group name specified 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). โœจ

Optional. Type uniline.

๐ŸŽ€ 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 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. 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, as a different unit might get the same UID/GID assigned later on, and thus gain access to these files or directories. ๐Ÿ˜ญ (no cap, this is the part you actually have to read)

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. ๐Ÿ˜”

Optional. Type boolean.

upstream_default value : no

๐Ÿ’œ 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 +. ๐ŸŒŸ

Optional. Type list of uniline.

๐Ÿฆ‹ SetLoginEnvironment ๐Ÿฆ‹

Takes a boolean parameter that controls whether to set the $HOME, $LOGNAME, and $SHELL environment variables. If not set, this defaults to true if User, DynamicUser or PAMName 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 whether User, DynamicUser, or PAMName 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 user manager's own environment anyway. โœจ

Optional. Type boolean.

๐ŸŒธ 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 the User setting, and is otherwise ignored. If not set, no PAM session will be opened for the executed processes. See pam(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. ๐Ÿ‘‘

okay so continuing the PAMName tea โ˜• because it's messier than it looks:

big heads up bestie: when you use this option for a unit, it's mad likely (depends on your PAM config ngl) that the main unit process gets migrated to its own session scope unit the moment it's activated. so that process is now associated with two units: the unit it was originally started from (the one where PAMName was configured), and the session scope unit. but here's the plot twist โ€” any child processes of that process? they're associated with the session scope unit only. ๐Ÿ˜ญ

this matters big time if you're also using NotifyAccess= all, because those child processes won't be able to affect changes in the original unit through notification messages. those messages get considered as belonging to the session scope unit, not the original unit. so yeah โ€” PAMName + NotifyAccess= all is not recommended. periodt. don't do it.

and one more thing ๐Ÿซถ if a PAM module interactively asks for input (a password or suchlike), systemd will try to read it 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. (and yes, the credential stays accessible for the runtime of the service โ€” clock that! โฐ) if there's no matching credential set, the user just gets prompted for it interactively via the Password Agent https://systemd.io/PASSWORD_AGENTS logic.

Optional ๐Ÿ’… Type: uniline


๐Ÿ›ก๏ธ CapabilityBoundingSet

okay so this one controls which capabilities get included in the capability bounding set for the executed process. see capabilities(7) for the full breakdown, iykyk.

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 list with ~, it flips โ€” all but the listed capabilities get included, the effect of the assignment inverted, slay.

lowkey important: this option also affects the respective capabilities in the effective, permitted and inheritable capability sets. if you don't use this option at all, the capability bounding set isn't modified on process execution, so no limits on the process's capabilities are enforced.

you can use this option more than once โ€” in that case, the bounding sets get merged by OR, or by AND if the lines are prefixed with ~.

  • if you assign the empty string, the bounding set resets to the empty capability set, and all prior settings have no effect.
  • if you set it to ~ (with no further argument), the bounding set resets to the full set of available capabilities, also undoing any previous settings.

this does not affect commands prefixed with +. ๐Ÿ™…

oh and use systemd-analyze(1)'s capability command to retrieve a list of capabilities defined on the local system. very handy, very iconic.

Example: if a unit has the following,

CapabilityBoundingSet=CAP_A CAP_B
CapabilityBoundingSet=CAP_B CAP_C

then CAP_A, CAP_B, and CAP_C are set. and if the second line is prefixed with ~, e.g.

CapabilityBoundingSet=CAP_A CAP_B
CapabilityBoundingSet=~CAP_B CAP_C

then, only CAP_A is set. cute.

Optional ๐Ÿ’… Type: uniline


โœจ AmbientCapabilities

this one controls which capabilities go into 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.

it may appear more than once, and then the ambient capability sets are merged (see the above examples in CapabilityBoundingSet ๐Ÿ˜Œ). if the list is prefixed with ~, all but the listed capabilities get included, effect inverted, you know the drill.

  • assigning the empty string resets the ambient capability set to the empty capability set, and all prior settings have no effect.
  • setting it to ~ (no further argument) resets the ambient capability set to the full set of available capabilities, also undoing any previous settings.

note that adding capabilities to the ambient capability set also adds them to the process's inherited capability set. ๐Ÿ”ฅ

ambient capability sets are genuinely so useful if you wanna execute a process as a non-privileged user but still give it some capabilities. bestie the way that works: option keep-caps is automatically added to SecureBits to retain the capabilities over the user change. ๐Ÿค

AmbientCapabilities does not affect commands prefixed with +.

Optional ๐Ÿ’… Type: uniline


๐Ÿ”’ NoNewPrivileges

takes a boolean argument. if true, it ensures the service process and all its children can never gain new privileges through execve() (e.g. via setuid or setgid bits, or filesystem capabilities โ€” sneaky sneaky). this is literally 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 at https://docs.kernel.org/userspace-api/no_new_privs.html.

ok real talk, don't get it twisted: 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 as at(1), crontab(1), systemd-run(1), or arbitrary IPC services. ๐Ÿ˜ญ

Optional ๐Ÿ’… Type: boolean

upstream_default value :
    no

๐Ÿ” SecureBits

controls the secure bits set for the executed process. takes a space-separated combination of options from this list: keep-caps, keep-caps-locked, no-setuid-fixup, no-setuid-fixup-locked, noroot, and noroot-locked.

this option may appear more than once, and then the secure bits are ORed. if the empty string is assigned, the bits are reset to 0. this does not affect commands prefixed with +. see capabilities(7) for details.

Optional ๐Ÿ’… Type: uniline


๐Ÿงก SELinuxContext

sets the SELinux security context of the executed process. if you set it, it overrides the automated domain transition โ€” however, the policy still needs to authorize the transition, so no sneaking around SELinux, bestie.

this directive is ignored if SELinux is disabled. if prefixed by -, failing to set the SELinux security context will be ignored, but it's still possible that the subsequent execve() may fail if the policy doesn't allow the transition for the non-overridden context. ๐Ÿ’€

this does not affect commands prefixed with +. see setexeccon(3) for details.

Optional ๐Ÿ’… Type: uniline


๐Ÿฆ‹ AppArmorProfile

takes a profile name as argument. the process executed by the unit will switch to this profile when it starts. profiles must already be loaded in the kernel, or the unit will fail. if prefixed by -, all errors will be ignored.

this setting has no effect if AppArmor is not enabled. and it does not affect commands prefixed with +.

Optional ๐Ÿ’… Type: uniline


๐ŸŒธ 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 keep running under the label you specified here โ€” unless the executable has its own SMACK64EXEC label, in which case the process transitions to run under that label instead. if you don't specify anything, the label that systemd is running under gets 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 +.

Optional ๐Ÿ’… Type: uniline


๐Ÿ“ LimitCPU

okay this is the big family of limits, so buckle up ๐ŸŽ€ โ€” this sets soft and hard limits on various resources for executed processes. see setrlimit(2) for details on the whole process resource limit concept.

process resource limits may be specified in two formats:

  1. a single value, to set a specific soft and hard limit to the same value.
  2. 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 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).

ngl these next bits matter: if no time unit is specified for LimitCPU, the default unit of seconds is implied, while for LimitRTTIME the default unit of microseconds is implied. also, 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).
โš ๏ธ real talk, this is the important caveat section: 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. literally nothing. ๐Ÿ™ˆ

often it's advisable to prefer the resource controls listed in systemd.resource-control(5) over these per-process limits, because 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. ๐Ÿ’…

also, LimitNPROC will limit the number of processes from one (real) UID โ€” not the number of processes started (forked) by the service. so the limit is cumulative for all processes running under the same UID. and heads up: LimitNPROC will not be enforced if the service is running as root (and not dropping privileges). because of these limitations, TasksMax (see systemd.resource-control(5)) is typically a better choice than LimitNPROC. ๐Ÿ‘‘

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). โœจ

For system units these resource limits may be chosen freely ๐Ÿ’…. but the second you configure these settings in a user service (i.e. a service run by the per-user instance of the service manager), you cannot use them to raise the limits above the ones that were set for the user manager itself back when it was first invoked โ€” because the user's service manager generally doesn't have the privileges to do that, and it's giving "tried my best, still locked out" ๐Ÿ˜ญ. so in user context these configuration options are honestly only useful for lowering the limits that got passed in, or for raising the soft limit up to the max of the hard limit as configured for the user. to raise the user's limits any further than that, the available configuration mechanisms differ between operating systems, but they typically require privileges. in most cases it's possible to configure higher per-user resource limits via PAM, or by setting limits on the system service that encapsulates the user's service manager โ€” i.e. the user's instance of "user@.service". after you make changes like that, make sure you restart the user's service manager, no cap ๐Ÿซถ.

Optional. Type uniline.


๐ŸŒธ LimitFSIZE

okay so this is where you set soft and hard limits on all the resources for executed processes. for the whole lore on what a process resource limit even is, go read setrlimit(2) โœจ. you can write process resource limits in two formats:

  • one single value, which sets a specific soft and hard limit to the same value ๐Ÿ”ฅ
  • a colon-separated pair "soft:hard" to set both limits individually (e.g. "LimitAS=4G:16G") ๐ŸŽ€

want no limit at all on a specific resource? use the string "infinity". for resource limits measured in bytes, the multiplicative suffixes K, M, G, T, P and E (to the base 1024) are yours to use (e.g. "LimitAS=16G"). for the limits that refer to time values, the usual time units ms, s, min, h and so on may be used (see systemd.time(7) for details). heads up though: if you don't specify a time unit for "LimitCPU", the default unit of seconds is implied, while for "LimitRTTIME" the default unit of microseconds is implied. 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. and for "LimitNICE" the value may be specified in two syntaxes: if it's prefixed with "+" or "-", the value is understood as a regular Linux nice value in the range -20โ€ฆ19; if it's not prefixed like that, the value is understood as a raw resource limit parameter in the range 0โ€ฆ40 (with 0 being equivalent to 1).

now the messy part, bestie ๐Ÿ™ˆ. 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 the limits you set ๐Ÿ’€. also note that "LimitRSS" is not implemented on Linux, and setting it has no effect. 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 "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".

resource limits you don't configure 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).

for system units these resource limits may be chosen freely. when these settings are configured in a user service (i.e. a service run by the per-user instance of the service manager) they cannot be used to raise the limits above those set for the user manager itself when it was first invoked, as the user's service manager generally lacks the privileges to do so. in user context these configuration options are hence only useful to lower the limits passed in or to raise the soft limit to the maximum of the hard limit as configured for the user. to raise the user's limits further, the available configuration mechanisms differ between operating systems, but typically require privileges. in most cases it is possible to configure higher per-user resource limits via PAM or by setting limits on the system service encapsulating the user's service manager, i.e. the user's instance of "user@.service". after making such changes, make sure to restart the user's service manager ๐Ÿซก.

Optional. Type uniline.


๐Ÿฆ‹ LimitDATA

set soft and hard limits on various resources for executed processes, bestie โ€” that's the whole gig. see setrlimit(2) for details on the process resource limit concept if you want the deep lore. 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 for "LimitCPU" the default unit of seconds is implied, while for "LimitRTTIME" the default unit of microseconds is implied. 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 regular Linux nice value in the range -20โ€ฆ19. if not prefixed like this the value is understood as 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. 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 "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".

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).

for system units these resource limits may be chosen freely. when these settings are configured in a user service (i.e. a service run by the per-user instance of the service manager) they cannot be used to raise the limits above those set for the user manager itself when it was first invoked, as the user's service manager generally lacks the privileges to do so. in user context these configuration options are hence only useful to lower the limits passed in or to raise the soft limit to the maximum of the hard limit as configured for the user. to raise the user's limits further, the available configuration mechanisms differ between operating systems, but typically require privileges. in most cases it is possible to configure higher per-user resource limits via PAM or by setting limits on the system service encapsulating the user's service manager, i.e. the user's instance of "user@.service". after making such changes, make sure to restart the user's service manager ๐ŸŒˆ.

Optional. Type uniline.


๐ŸŽ€ LimitSTACK

set soft and hard limits on various resources for executed processes โ€” yes, same concept, and setrlimit(2) is still the place for details on the process resource limit concept ๐Ÿ“. 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 for "LimitCPU" the default unit of seconds is implied, while for "LimitRTTIME" the default unit of microseconds is implied. 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 regular Linux nice value in the range -20โ€ฆ19. if not prefixed like this the value is understood as 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. 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" ๐Ÿ”ฅ.

Also please clock this one: LimitNPROC limits the number of processes from one (real) UID, not the number of processes started (forked) by the service ๐Ÿ˜ญ. So the limit is cumulative for all processes running under the same UID. And plot twist โ€” LimitNPROC will not be enforced if the service is running as root (and not dropping privileges). Because of all those little limitations, TasksMax (see systemd.resource-control(5)) is typically a better choice than LimitNPROC. iykyk ๐Ÿ’…

Resource limits you didn't configure explicitly for a unit just default to the value configured in the various DefaultLimitCPU, DefaultLimitFSIZE, โ€ฆ options available in systemd-system.conf(5), and โ€” if it's not configured there either โ€” the kernel or per-user defaults, as defined by the OS (that last one only for user services, see below) ๐ŸŒธ

For system units these resource limits may be chosen freely โœจ. But when these settings are configured in a user service (i.e. a service run by the per-user instance of the service manager) they cannot be used to raise the limits above those set for the user manager itself when it was first invoked โ€” because the user's service manager generally lacks the privileges to do so ๐Ÿ™ˆ. So in user context these configuration options are only useful to lower the limits passed in, or to raise the soft limit to the maximum of the hard limit as configured for the user. Wanna raise the user's limits further? The available configuration mechanisms differ between operating systems, but they typically require privileges. In most cases it's possible to configure higher per-user resource limits via PAM, or by setting limits on the system service encapsulating the user's service manager, i.e. the user's instance of user@.service ๐ŸŒˆ. After making such changes, make sure to restart the user's service manager ๐Ÿ’– Optional. Type uniline.


LimitCORE ๐Ÿซถ

Set soft and hard limits on various resources for executed processes.</purl> Yeah that's the whole vibe of this one. Peep setrlimit(2) for the details on the process resource limit concept, it's lowkey required reading ๐Ÿ“š

Process resource limits may be specified in two formats:

  • a single value, to set a specific soft and hard limit to the same value, or
  • 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 for LimitCPU the default unit of seconds is implied, while for LimitRTTIME the default unit of microseconds is implied ๐ŸŒ™ 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 it's 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).

Heads up bestie: 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. Often it's 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 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 ๐Ÿ˜Œ

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) ๐Ÿ“

For system units these resource limits may be chosen freely. When these settings are configured in a user service (i.e. a service run by the per-user instance of the service manager) they cannot be used to raise the limits above those set for the user manager itself when it was first invoked, as the user's service manager generally lacks the privileges to do so ๐ŸŒง๏ธ In user context these configuration options are hence only useful to lower the limits passed in or to raise the soft limit to the maximum of the hard limit as configured for the user. To raise the user's limits further, the available configuration mechanisms differ between operating systems, but typically require privileges. In most cases it is possible to configure higher per-user resource limits via PAM or by setting limits on the system service encapsulating the user's service manager, i.e. the user's instance of user@.service. After making such changes, make sure to restart the user's service manager ๐Ÿ’ซ Optional. Type uniline.


LimitRSS ๐ŸŽ€

Set soft and hard limits on various resources for executed processes. See setrlimit(2) for details on the process resource limit concept, bestie โ€” it really does explain the whole thing ๐Ÿซถ

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 for LimitCPU the default unit of seconds is implied, while for LimitRTTIME the default unit of microseconds is implied ๐Ÿ•ฐ๏ธ 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 regular Linux nice value in the range -20โ€ฆ19. If not prefixed like this the value is understood as 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 โ€” like, at all, it's giving decorative ๐Ÿซ  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 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 ๐ŸŒŸ

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) ๐ŸŒฑ

For system units these resource limits may be chosen freely. When these settings are configured in a user service (i.e. a service run by the per-user instance of the service manager) they cannot be used to raise the limits above those set for the user manager itself when it was first invoked, as the user's service manager generally lacks the privileges to do so ๐Ÿ˜ญ In user context these configuration options are hence only useful to lower the limits passed in or to raise the soft limit to the maximum of the hard limit as configured for the user. To raise the user's limits further, the available configuration mechanisms differ between operating systems, but typically require privileges. In most cases it is possible to configure higher per-user resource limits via PAM or by setting limits on the system service encapsulating the user's service manager, i.e. the user's instance of user@.service ๐ŸŒธ After making such changes, make sure to restart the user's service manager. Optional. Type uniline.


LimitNOFILE ๐Ÿ’œ

Set soft and hard limits on various resources for executed processes, slay ๐Ÿ’… See setrlimit(2) for details on the process resource limit concept.

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 for LimitCPU the default unit of seconds is implied, while for LimitRTTIME the default unit of microseconds is implied โณ 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 regular Linux nice value in the range -20โ€ฆ19. If not prefixed like this the value is understood as raw resource limit parameter in the range 0โ€ฆ40 (with 0 being equivalent to 1).

okay so about all of these process resource limits you can set with these options ๐Ÿ’… โ€” most of them are per-process. Which means a process can just fork to grab itself a whole new set of resources that get accounted independently of the original process, and likeโ€ฆ escape the limits you set ๐Ÿ˜ญ. the audacity, honestly. also note that LimitRSS is not implemented on Linux, and setting it has no effect โ€” nothing. zero. not me setting a limit that literally does nothing ๐Ÿ™ˆ.


Often it is advisable to prefer the resource controls listed in systemd.resource-control(5) over these per-process limits, because 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 ๐Ÿ”ฅ. glow up fr.

Note that LimitNPROC limits 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. also please note that 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. iykyk.

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) ๐Ÿซถ.

For system units these resource limits may be chosen freely ๐ŸŒˆ. But when these settings are configured in a user service (i.e. a service run by the per-user instance of the service manager) they cannot be used to raise the limits above those set for the user manager itself when it was first invoked, because the user's service manager generally lacks the privileges to do so ๐Ÿ˜ญ. In user context these configuration options are hence only useful to lower the limits passed in, or to raise the soft limit to the maximum of the hard limit as configured for the user.

To raise the user's limits further, the available configuration mechanisms differ between operating systems, but typically require privileges (privileges!! respect them). In most cases it is possible to configure higher per-user resource limits via PAM or by setting limits on the system service encapsulating the user's service manager, i.e. the user's instance of user@.service ๐ŸŽ€. After making such changes, make sure to restart the user's service manager โ€” don't forget, bestie, or you'll be sitting there like "why isn't it working" ๐Ÿ™ˆ.

Optional. Type uniline.

๐Ÿฆ‹ LimitAS ๐Ÿฆ‹

Set soft and hard limits on various resources for executed processes ๐ŸŒธ. See setrlimit(2) for details on the process resource limit concept.

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 for LimitCPU the default unit of seconds is implied, while for LimitRTTIME the default unit of microseconds is implied ๐Ÿ‘€. 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 regular Linux nice value in the range -20โ€ฆ19
  • if not prefixed like this, the value is understood as 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 ๐Ÿ’€. 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 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.

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).

For system units these resource limits may be chosen freely ๐ŸŒˆ. When these settings are configured in a user service (i.e. a service run by the per-user instance of the service manager) they cannot be used to raise the limits above those set for the user manager itself when it was first invoked, as the user's service manager generally lacks the privileges to do so. In user context these configuration options are hence only useful to lower the limits passed in or to raise the soft limit to the maximum of the hard limit as configured for the user. To raise the user's limits further, the available configuration mechanisms differ between operating systems, but typically require privileges. In most cases it is possible to configure higher per-user resource limits via PAM or by setting limits on the system service encapsulating the user's service manager, i.e. the user's instance of user@.service ๐ŸŽ€. After making such changes, make sure to restart the user's service manager.

Optional. Type uniline.

๐Ÿ’œ LimitNPROC ๐Ÿ’œ

Set soft and hard limits on various resources for executed processes ๐ŸŒธ. See setrlimit(2) for details on the process resource limit concept.

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 for LimitCPU the default unit of seconds is implied, while for LimitRTTIME the default unit of microseconds is implied ๐Ÿ‘€. 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 regular Linux nice value in the range -20โ€ฆ19
  • if not prefixed like this, the value is understood as 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 ๐Ÿ’€. 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 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.

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).

For system units these resource limits may be chosen freely ๐ŸŒˆ. When these settings are configured in a user service (i.e. a service run by the per-user instance of the service manager) they cannot be used to raise the limits above those set for the user manager itself when it was first invoked, as the user's service manager generally lacks the privileges to do so. In user context these configuration options are hence only useful to lower the limits passed in or to raise the soft limit to the maximum of the hard limit as configured for the user. To raise the user's limits further, the available configuration mechanisms differ between operating systems, but typically require privileges. In most cases it is possible to configure higher per-user resource limits via PAM or by setting limits on the system service encapsulating the user's service manager, i.e. the user's instance of user@.service ๐ŸŽ€. After making such changes, make sure to restart the user's service manager.

Optional. Type uniline.

๐Ÿ’– LimitMEMLOCK

Ok bestie, gather round, because every single one of these Limit* entries says the same gorgeous thing and imma tell you all of it. Starting with the basics: this sets the soft and hard limits on various resources for executed processes. If you're sitting there like "girl what even IS a process resource limit," go read setrlimit(2), it explains the whole concept, no cap. ๐Ÿ“–

So there's two formats you can use:

  • a single value โ€” sets a specific soft and hard limit to that same value
  • a colon-separated pair soft:hard โ€” sets both limits individually (e.g. LimitAS=4G:16G) โœจ

Wanna configure no limit on a specific resource? Just write the string infinity and vibe. ๐Ÿฆ‹

For stuff measured in bytes, the multiplicative suffixes K, M, G, T, P and E (to the base 1024) are yours to play with (e.g. LimitAS=16G). ๐ŸŒˆ

For the limits that refer to time values, the usual time units ms, s, min, h and so on may be used (peep systemd.time(7) for the details). Now listen carefully, this is the sneaky part ๐Ÿ™ˆ: if no time unit is specified for LimitCPU, the default unit of seconds is implied โ€” but for LimitRTTIME, the default unit of microseconds is implied. Different defaults, same energy, don't mix them up. Also, 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. So don't come crying about your half-second precision, it was never happening. ๐Ÿ’€

For LimitNICE, the value may be specified in two syntaxes: if it's prefixed with + or -, the value is understood as a regular Linux nice value in the range -20โ€ฆ19. If it's not prefixed like that, the value is understood as a raw resource limit parameter in the range 0โ€ฆ40 (with 0 being equivalent to 1). Two lanes, pick one. ๐Ÿ‘‘

Now the part where I hold your hand, because it matters ๐Ÿซถ:

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. Yes bestie, they can just dip. Also note that LimitRSS is not implemented on Linux, and setting it has no effect. Iconic in the worst way. ๐Ÿ’€

Often it's advisable to prefer the resource controls listed in systemd.resource-control(5) over these per-process limits, since 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. That's the glow-up. ๐Ÿ”ฅ

LimitNPROC tea specifically: it 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 LimitNPROC will not be enforced if the service is running as root (and not dropping privileges). Because of these limitations, TasksMax (see systemd.resource-control(5)) is typically a better choice than LimitNPROC. Periodt.

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 it's not configured there either, then the kernel or per-user defaults, as defined by the OS (that last bit only for user services, see below). ๐Ÿ‘‡

For system units these resource limits may be chosen freely, live your truth. But when these settings are configured in a user service (i.e. a service run by the per-user instance of the service manager), they cannot be used to raise the limits above those set for the user manager itself when it was first invoked โ€” because the user's service manager generally lacks the privileges to do so. In user context these configuration options are hence only useful to lower the limits passed in, or to raise the soft limit to the maximum of the hard limit as configured for the user. To raise the user's limits further, the available configuration mechanisms differ between operating systems, but typically require privileges. In most cases it's possible to configure higher per-user resource limits via PAM or by setting limits on the system service encapsulating the user's service manager, i.e. the user's instance of user@.service. After making such changes, make sure to restart the user's service manager. ๐Ÿ”

Optional. Type uniline.

๐ŸŽ€ LimitLOCKS

Same deal, bestie, let me run it back for anyone who scrolled past the last one. ๐Ÿ’…

Set soft and hard limits on various resources for executed processes. See setrlimit(2) for the details on the process resource limit concept โ€” that's the lore. ๐Ÿ“š

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 for LimitCPU the default unit of seconds is implied, while for LimitRTTIME the default unit of microseconds is implied. ๐ŸŒธ

Also โ€” and this is the quiet part โ€” 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. (Yes, again. It's not a typo, it's just how it be. ๐Ÿ™ˆ)

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. 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 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. ๐ŸŒŸ

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).

For system units these resource limits may be chosen freely. When these settings are configured in a user service (i.e. a service run by the per-user instance of the service manager) they cannot be used to raise the limits above those set for the user manager itself when it was first invoked, as the user's service manager generally lacks the privileges to do so. In user context these configuration options are hence only useful to lower the limits passed in or to raise the soft limit to the maximum of the hard limit as configured for the user. To raise the user's limits further, the available configuration mechanisms differ between operating systems, but typically require privileges. In most cases it is possible to configure higher per-user resource limits via PAM or by setting limits on the system service encapsulating the user's service manager, i.e. the user's instance of user@.service. After making such changes, make sure to restart the user's service manager. ๐Ÿ’ซ

Optional. Type uniline.

๐Ÿฆ‹ LimitSIGPENDING

Okay one more time, and then we move on for real. ๐Ÿ˜ญ

Set soft and hard limits on various resources for executed processes. See setrlimit(2) for details on the process resource limit concept. That's the foundation, bestie, everything else is decoration. โœจ

Process resource limits may be specified in two formats: either as single value to set a specific soft and hard limit to the same value, or as 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 for LimitCPU the default unit of seconds is implied, while for LimitRTTIME the default unit of microseconds is implied. ๐ŸŒˆ

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 regular Linux nice value in the range -20โ€ฆ19. If not prefixed like this the value is understood as 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. 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. ๐Ÿ‘‘

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).

For system units these resource limits may be chosen freely, bestie โ€” do whatever you want ๐Ÿ’…. But when these settings get configured in a user service (aka a service run by the per-user instance of the service manager), they cannot be used to raise the limits above the ones set for the user manager itself when it was first invoked, because the user's service manager generally lacks the privileges to do that. So in user context these configuration options are only useful for lowering the limits that got passed in, or for raising the soft limit up to the max of the hard limit as configured for the user. To push the user's limits even higher you'd need different config mechanisms depending on the OS, and those typically require privileges. In most cases you can configure higher per-user resource limits via PAM, or 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? Restart the user's service manager. Yes, for real.

Optional ๐ŸŒธ Type uniline.


๐ŸŒˆ LimitMSGQUEUE ๐ŸŒˆ

Set soft and hard limits on various resources for executed processes, queen ๐Ÿ‘‘. See setrlimit(2) for the whole deal on the process resource limit concept.

Process resource limits can be written in two formats:

  • one single value, which sets a specific soft and hard limit to the same value
  • a colon-separated pair soft:hard to set both limits individually (like LimitAS=4G:16G)

Use the string infinity to configure no limit at all 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 that refer to time values, the usual time units ms, s, min, h and so on may be used (see systemd.time(7) for the deets).

Heads up ๐Ÿซถ: if no time unit is specified for LimitCPU the default unit of seconds is implied, while for LimitRTTIME the default unit of microseconds is implied. 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 it's not prefixed like that, 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 the limits set ๐Ÿ˜ญ. Also note that LimitRSS is not implemented on Linux, and setting it has no effect. Often it's advisable to prefer the resource controls listed in systemd.resource-control(5) over these per-process limits, since 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. So the limit is cumulative for all processes running under the same UID ๐Ÿ™ˆ. Please also note that LimitNPROC will not be enforced if the service is running as root (and not dropping privileges). Because of all these limitations, TasksMax (see systemd.resource-control(5)) is typically a better choice than LimitNPROC.

Resource limits that aren't 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).

For system units these resource limits may be chosen freely. When these settings are configured in a user service (i.e. a service run by the per-user instance of the service manager) they cannot be used to raise the limits above those set for the user manager itself when it was first invoked, as the user's service manager generally lacks the privileges to do so. In user context these configuration options are hence only useful to lower the limits passed in or to raise the soft limit to the maximum of the hard limit as configured for the user. To raise the user's limits further, the available configuration mechanisms differ between operating systems, but typically require privileges. In most cases it is possible to configure higher per-user resource limits via PAM or by setting limits on the system service encapsulating the user's service manager, i.e. the user's instance of user@.service. After making such changes, make sure to restart the user's service manager.

Optional โœจ Type uniline.


๐Ÿฆ‹ LimitNICE ๐Ÿฆ‹

Set soft and hard limits on various resources for executed processes ๐Ÿ’–. See setrlimit(2) for details on the process resource limit concept.

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 the tea โ˜•).

Note that if no time unit is specified for LimitCPU the default unit of seconds is implied, while for LimitRTTIME the default unit of microseconds is implied. 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 the limits set ๐Ÿ’€. Also note that LimitRSS is not implemented on Linux, and setting it has no effect. 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 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.

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).

For system units these resource limits may be chosen freely. When these settings are configured in a user service (i.e. a service run by the per-user instance of the service manager) they cannot be used to raise the limits above those set for the user manager itself when it was first invoked, as the user's service manager generally lacks the privileges to do so. In user context these configuration options are hence only useful to lower the limits passed in or to raise the soft limit to the maximum of the hard limit as configured for the user. To raise the user's limits further, the available configuration mechanisms differ between operating systems, but typically require privileges. In most cases it is possible to configure higher per-user resource limits via PAM or by setting limits on the system service encapsulating the user's service manager, i.e. the user's instance of user@.service. After making such changes, make sure to restart the user's service manager.

Optional ๐ŸŽ€ Type uniline.


๐Ÿงš LimitRTPRIO ๐Ÿงš

Set soft and hard limits on various resources for executed processes โ€” you got this, bestie ๐ŸŒธ. See setrlimit(2) for details on the process resource limit concept.

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 for LimitCPU the default unit of seconds is implied, while for LimitRTTIME the default unit of microseconds is implied. 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. 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.

โœจ real talk about LimitNPROC though โœจ โ€” it limits the number of processes coming from one (real) UID, not the number of processes actually started (forked) by the service. So bestie, that limit is cumulative for every process running under the same UID ๐Ÿ˜ญ. Also please note: LimitNPROC will not be enforced if the service is running as root (and not dropping privileges) ๐Ÿ’€. Because of all these lil limitations, TasksMax (peep systemd.resource-control(5)) is typically a better choice than LimitNPROC. periodt.

Resource limits that you don't configure explicitly for a unit just default to whatever value is configured in the various DefaultLimitCPU, DefaultLimitFSIZE, โ€ฆ options available in systemd-system.conf(5), and โ€” if it's not configured there either โ€” the kernel or per-user defaults, as defined by the OS (that last one only for user services, see below ๐Ÿ‘€).

For system units these resource limits may be chosen freely, no cap, main character energy ๐Ÿ‘‘. But when these settings are configured in a user service (i.e. a service run by the per-user instance of the service manager) they cannot be used to raise the limits above those set for the user manager itself when it was first invoked, because the user's service manager generally lacks the privileges to do so ๐Ÿ™ˆ. In user context these configuration options are hence only useful to lower the limits passed in, or to raise the soft limit to the maximum of the hard limit as configured for the user. To raise the user's limits further, the available configuration mechanisms differ between operating systems, but they typically require privileges. In most cases it's possible to configure higher per-user resource limits via PAM or by setting limits on the system service encapsulating the user's service manager, i.e. the user's instance of user@.service. After making such changes, make sure to restart the user's service manager ๐Ÿ”„. Optional. Type uniline.

๐ŸŒˆ LimitRTTIME ๐ŸŒˆ

Set soft and hard limits on various resources for executed processes. See setrlimit(2) for the whole lore on the process resource limit concept ๐Ÿ“œ.

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 for LimitCPU the default unit of seconds is implied, while for LimitRTTIME the default unit of microseconds is implied ๐Ÿ‘€. 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. 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. slay.

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.

(okay so yes, this manpage literally says the same paragraph twice and I am not gatekeeping that from you bestie, you get the whole thing, every word, no cap ๐Ÿ’…โœจ)

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).

For system units these resource limits may be chosen freely. When these settings are configured in a user service (i.e. a service run by the per-user instance of the service manager) they cannot be used to raise the limits above those set for the user manager itself when it was first invoked, as the user's service manager generally lacks the privileges to do so. In user context these configuration options are hence only useful to lower the limits passed in or to raise the soft limit to the maximum of the hard limit as configured for the user. To raise the user's limits further, the available configuration mechanisms differ between operating systems, but typically require privileges. In most cases it is possible to configure higher per-user resource limits via PAM or by setting limits on the system service encapsulating the user's service manager, i.e. the user's instance of user@.service. After making such changes, make sure to restart the user's service manager. Optional. Type uniline.

๐ŸŽ€ UMask ๐ŸŽ€

Controls the file mode creation mask. Takes an access mode in octal notation ๐Ÿ”ข. See umask(2) for details. Defaults to 0022 for system units. For user units the default value is inherited from the per-user service manager (whose default is in turn inherited from the system service manager, and thus typically also is 0022 โ€” unless overridden by a PAM module ๐Ÿซถ). In order to change the per-user mask for all user services, 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 (https://systemd.io/USER_RECORD) (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) ๐ŸŒธ. Optional. Type uniline.

๐Ÿฆ‹ CoredumpFilter ๐Ÿฆ‹

Controls which types of memory mappings will be saved if the process dumps core (using the /proc/pid/coredump_filter file ๐Ÿฉธ). Takes a whitespace-separated combination of mapping type names or numbers (with the default base 16). Mapping type names are private-anonymous, shared-anonymous, private-file-backed, shared-file-backed, elf-headers, private-huge, shared-huge, private-dax, shared-dax, and the special values all (all types) and default (the kernel default of private-anonymous shared-anonymous elf-headers private-huge). See core(5) for the meaning of the mapping types ๐Ÿ“–. 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. Optional. Type uniline.

๐Ÿ‘‘ KeyringMode ๐Ÿ‘‘

Controls how the kernel session keyring is set up for the service (see session-keyring(7) for details on the session keyring ๐Ÿ”‘). Takes one of inherit, private, shared.

  • inherit โ†’ no special keyring setup is done, and the kernel's default behaviour is applied.
  • 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, as this ensures that multiple services running under the same system user ID (in particular the root user) do not share their key material among each other ๐Ÿ”’.
  • shared โ†’ a new session keyring is allocated as for private, but the user keyring of the user configured with User 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 ๐Ÿค.

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. Optional. Type enum. choice: 'inherit', 'private', 'shared'.

๐Ÿ’€ OOMScoreAdjust ๐Ÿ’€

Sets the adjustment value for the Linux kernel's Out-Of-Memory (OOM) killer score for executed processes. Takes an integer between -1000 (to disable OOM killing of processes of this unit) and 1000 (to make killing of processes of this unit under memory pressure very likely ๐Ÿ˜ต). See The /proc Filesystem (https://docs.kernel.org/filesystems/proc.html) for details. If not specified, defaults to the OOM score adjustment level of the service manager itself, which is normally at 0.

Use the OOMPolicy setting of service units to configure how the service manager shall react to the kernel OOM killer or systemd-oomd terminating a process of the service. See systemd.service(5) for details ๐Ÿ‘€. Optional. Type integer.

โฑ๏ธ TimerSlackNSec โฑ๏ธ

Sets the timer slack in nanoseconds for the executed processes. The timer slack controls the accuracy of wake-ups triggered by timers โฐ. See prctl(2) for more information. Note that 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. Optional. Type uniline.

๐ŸŒˆ Personality ๐ŸŒˆ

Controls which kernel architecture uname(2) shall report, when invoked by unit processes ๐Ÿ‘พ. Takes one of the 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. 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 as m68k (32-bit only) or alpha (64-bit only). Optional. Type enum. choice: 'arm', 'arm-be', 'arm64', 'arm64-be', 'ppc', 'ppc-le', 'ppc64', 'ppc64-le', 's390', 's390x', 'x86', 'x86-64'.

๐Ÿ’… IgnoreSIGPIPE

Okay so this one takes a boolean argument, bestie. If it's true, SIGPIPE gets ignored in the executed process. And it defaults to true because honestly SIGPIPE is generally only useful in shell pipelines anyway. ๐Ÿ’€

Optional. Type boolean.


๐ŸŒˆ Nice

Sets the default nice level (scheduling priority) for executed processes. Takes an integer between -20 (highest priority) and 19 (lowest priority). ๐Ÿซถ

Now the vibe check on resource contention: smaller values mean more resources get made available to the unit's processes, larger values mean less resources get made available. See setpriority(2) for details.

Optional. Type integer.


โœจ CPUSchedulingPolicy

Sets the CPU scheduling policy for executed processes. Takes one of other, batch, idle, fifo, rr or ext. Go read sched_setscheduler(2) for the details, iykyk. ๐Ÿ‘‘

Optional. Type enum. choice: 'batch', 'ext', 'fifo', 'idle', 'other', 'rr'.


๐Ÿ”ฅ CPUSchedulingPriority

Sets the CPU scheduling priority for executed processes. The available priority range depends on the selected CPU scheduling policy (the one above, bestie โ€” they're a package deal ๐Ÿ’–). For real-time scheduling policies an integer between 1 (lowest priority) and 99 (highest priority) can be used.

In case of CPU resource contention, smaller values mean less CPU time is made available to the service, larger values mean more. See sched_setscheduler(2) for details.

Optional. Type uniline.


๐Ÿฆ‹ CPUSchedulingResetOnFork

Takes a boolean argument. If true, elevated CPU scheduling priorities and policies get reset when the executed processes call fork(2), and can hence not leak into child processes. See sched_setscheduler(2) for details. Defaults to false.

Optional. Type boolean.

upstream_default value :

no

๐ŸŽ€ 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 a special numa value, in which case systemd automatically derives the allowed CPU range based on the value of the NUMAMask option. CPU ranges are specified by the lower and upper CPU indices separated by a dash.

This option may be specified more than once, in which case the specified CPU affinity masks get merged. And if the empty string is assigned, the mask is reset โ€” all assignments prior to this will have no effect, fr fr.

See sched_setaffinity(2) for details.

Optional. Type list of uniline.


๐ŸŒธ NUMAPolicy

Controls the NUMA memory policy of the executed processes. Takes a policy type, one of: default, preferred, bind, interleave and local. 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 overall overview of NUMA support in Linux see, numa(7).

Optional. Type uniline.


๐Ÿ’œ NUMAMask

Controls the NUMA node list which will be applied alongside with 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 all value which will include all available NUMA nodes in the mask. ๐ŸŒˆ

Heads up bestie: the list of NUMA nodes is not required for default and local policies, and for preferred policy we expect a single NUMA node.

Optional. Type uniline.


๐Ÿ“ IOSchedulingClass

Sets the I/O scheduling class for executed processes. Takes one of the strings realtime, best-effort or idle. 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.

See ioprio_set(2) for details.

Optional. Type enum. choice: '0', '1', '2', '3', 'none', 'realtime', 'best-effort', 'idle'.


โญ 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, it's giving teamwork ๐Ÿ’…).

If the empty string is assigned to this option, all prior assignments to both IOSchedulingClass and IOSchedulingPriority have no effect. For the kernel's default scheduling class (best-effort) this defaults to 4. See ioprio_set(2) for details.

Optional. Type integer.

upstream_default value :

4

๐Ÿงš ProtectSystem

Takes a boolean argument or the special values full or strict. Let me break this down because it's lowkey the main character here ๐ŸŽ€

  • If true, it mounts the /usr/ and the boot loader directories (/boot and /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 using PrivateDevices, 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. ๐ŸŒธ

Optional. Type enum. choice: 'full', 'no', 'strict', 'yes'.

upstream_default value :

no

๐Ÿ’– ProtectHome

Takes a boolean argument or the special values read-only or tmpfs.

  • If true, the directories /home/, /root, and /run/user are 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.

Optional. Type enum. choice: 'no', 'read-only', 'tmpfs', 'yes'.


๐Ÿ‘‘ RuntimeDirectory

Okay so this is a whole squad of options, not just one, bestie. They take a whitespace-separated list of directory names. The specified directory names must be relative, and may not include ... ๐Ÿ’€

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 <https://lwn.net/Articles/896255/>, 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. ๐Ÿซถ

In case of RuntimeDirectory the innermost subdirectories are removed when the unit is stopped. It is possible to 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.

Except in case of ConfigurationDirectory, the innermost specified directories will be owned by the user and group specified in User and Group. 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. 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. When combined 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. โœจ

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 can optionally take two more parameters, and they're separated by a :. Bestie, here's the tea:

  • The second parameter is interpreted as a destination path that gets created as a symlink to the directory. These symlinks get created after any BindPaths or TemporaryFileSystem options have been set up โ€” so that ephemeral symlinking is possible. โœจ
  • The same source can have multiple symlinks: just use the same first parameter with a different second parameter. Obsessed.
  • The third parameter is a flags field, and since v257 it 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.

If you wanna pass a flag but don't want a destination symlink, the second parameter can just be empty, like this:

ConfigurationDirectory=foo::ro

The directories these options define are always created under the standard paths used by systemd (/var/, /run/, /etc/, โ€ฆ). If your service needs directories somewhere else, you gotta use a different mechanism to create them. No cap. ๐Ÿซถ

tmpfiles.d(5) provides functionality that overlaps with these options, but using these options is recommended, because the lifetime of the directories is tied directly to the lifetime of the unit โ€” and you don't have to make sure the tmpfiles.d configuration is executed before the unit is started. It's giving low-effort, high-reward. ๐Ÿ’…

To remove any of the directories created by these settings, run 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

then 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 they get removed when the service is stopped. Periodt.

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

then the service manager creates /run/foo (if it does not exist), and /run/bar plus /run/baz as symlinks to /run/foo. Optional. Type uniline. ๐ŸŽ€


StateDirectory ๐ŸŒธ

These options take a whitespace-separated list of directory names. The specified directory names must be relative, and may not include ... If set, then when the unit is started, one or more directories with the specified names get created (including their parents) below the locations defined in the following table. Also, the corresponding environment variable gets defined with the full paths of the directories. If multiple directories are set, then in the environment variable the paths are concatenated with a colon (:).

If DynamicUser is used, and if the kernel version supports id-mapped mounts (https://lwn.net/Articles/896255/), then 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. Fr fr. For backward compatibility, existing directories created without id-mapped mounts will be kept untouched.

In case of RuntimeDirectory, the innermost subdirectories are removed when the unit is stopped. It is possible to 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.

Except in case of ConfigurationDirectory, the innermost specified directories will be owned by the user and group specified in User and Group. If the specified directories already exist and their owning user or group don't match the configured ones, then all files and directories below the specified directories โ€” as well as the directories themselves โ€” will have their file ownership recursively changed to match what's configured. As an optimization, if the specified directories are already owned by the right user and group, files and directories below them are left as-is, even if they don't match what is requested. ๐Ÿ˜Œ The innermost specified directories will have their access mode adjusted to what is specified in RuntimeDirectoryMode, StateDirectoryMode, CacheDirectoryMode, LogsDirectoryMode and ConfigurationDirectoryMode.

These options imply BindPaths for the specified paths. When combined 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 get created below /var/cache/private, /var/log/private and /var/lib/private, respectively โ€” which are host directories made inaccessible to unprivileged users, so this 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 /var/cache, /var/log and /var/lib. Sneaky but iconic. ๐Ÿ’–

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 can't create runtime directories in /run/ due to lack of privileges, and to make sure the runtime directory gets 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.

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. Optional. Type uniline.


CacheDirectory ๐Ÿฆ‹

These options take a whitespace-separated list of directory names. The specified directory names must be relative, and may not include ... 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 (https://lwn.net/Articles/896255/), 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. ๐Ÿซถ

In case of RuntimeDirectory the innermost subdirectories are removed when the unit is stopped. It is possible to 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.

Except in case of ConfigurationDirectory, the innermost specified directories will be owned by the user and group specified in User and Group. 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. 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. โœจ

Okay so these options imply BindPaths for the paths you name ๐Ÿ’… and when you combine them with RootDirectory or RootImage, those paths always live on the host and get mounted from there into the unit's file system namespace. no cap ๐Ÿซถ

If DynamicUser is used, the logic for CacheDirectory, LogsDirectory and StateDirectory gets slightly altered: the directories get created below /var/cache/private, /var/log/private and /var/lib/private, respectively ๐Ÿซฃ and those are host directories made inaccessible to unprivileged users โ€” which is the whole point, because it ensures access to these directories cannot be gained through dynamic user ID recycling. Symbolic links are created to hide this difference in behaviour. So both from the perspective of the host and from inside the unit, the relevant directories always appear directly below /var/cache, /var/log and /var/lib. iconic โœจ

Use RuntimeDirectory to manage one or more runtime directories for the unit and bind their lifetime to the daemon runtime ๐Ÿ’– this is especially useful for unprivileged daemons that cannot create runtime directories in /run/ due to lack of privileges, and it makes sure the runtime directory is cleaned up automatically after use. For runtime directories that need more complex or different configuration or lifetime guarantees, please consider using tmpfiles.d(5) instead.

Now the fun part ๐ŸŽ€ RuntimeDirectory, StateDirectory, CacheDirectory and LogsDirectory optionally support two more parameters, separated by ":".

  • The second parameter gets interpreted as a destination path that will be created as a symlink to the directory. These symlinks are created after any BindPaths or TemporaryFileSystem options have been set up, so ephemeral symlinking is 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 it 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. sorry bestie, that's just how it is ๐Ÿ˜ญ

tmpfiles.d(5) provides functionality that overlaps with these options. Using these options is recommended though, 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. one less thing to babysit, we love that for us ๐Ÿ’…

To remove any of the directories created by these settings, use the systemctl clean โ€ฆ command on the relevant units, see systemctl(1) for details. go clean up your mess ๐Ÿงน


Example time ๐ŸŒธ 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. periodt ๐Ÿ‘‘

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. multi-value env vars, we love to see it ๐Ÿฆ‹

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. Optional. Type uniline.


๐Ÿ“ LogsDirectory ๐Ÿ“

These options take a whitespace-separated list of directory names. The specified directory names must be relative, and may not include "..". 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 (":"). iykyk ๐Ÿ’…

If DynamicUser is used, and if the kernel version supports id-mapped mounts (https://lwn.net/Articles/896255/), 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. lowkey thoughtful ngl

In case of RuntimeDirectory the innermost subdirectories are removed when the unit is stopped. It is possible to 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 ๐Ÿ™ˆ they stay put.

Except in case of ConfigurationDirectory, the innermost specified directories will be owned by the user and group specified in User and Group. 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 ๐Ÿ˜ณ As an optimization, if the specified directories are already owned by the right user and group, files and directories below 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 what is specified in RuntimeDirectoryMode, StateDirectoryMode, CacheDirectoryMode, LogsDirectoryMode and ConfigurationDirectoryMode. so many Modes bestie, collect them all ๐ŸŽ€

These options imply BindPaths for the specified paths ๐Ÿ’ซ When combined 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. (yes I know I said this already, the manpage really do be like that ๐Ÿ’€)

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.

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. Optional. Type uniline.


๐Ÿ’œ ConfigurationDirectory ๐Ÿ’œ

These options take a whitespace-separated list of directory names โœจ The specified directory names must be relative, and may not include "..". 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 (":"). config girlies rise up ๐ŸŒธ

If DynamicUser is used, and if the kernel version supports id-mapped mounts (https://lwn.net/Articles/896255/), 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.

Okay so โ€” with RuntimeDirectory specifically, the innermost subdirectories get removed the moment the unit stops ๐Ÿงนโœจ. Lowkey you can keep them around though, if RuntimeDirectoryPreserve is set to restart or yes (we'll get to that below, hold on). But the directories you make with StateDirectory, CacheDirectory, LogsDirectory, and ConfigurationDirectory? Those do not get removed when the unit is stopped. They stay. Periodt ๐Ÿ’….

Except for ConfigurationDirectory, the innermost directories you list get owned by the user and group that User and Group say. And here's the tea โ˜•: if the directory already exists and its owning user or group doesn't match what you configured, then every file and directory underneath it โ€” plus the directories themselves โ€” gets its file ownership recursively changed to match. As an optimization tho, if the dirs are already owned by the right user and group, everything below them is left exactly as-is, even if it doesn't match what you asked for ๐Ÿ™ˆ. And the innermost specified directories get their access mode adjusted to whatever RuntimeDirectoryMode, StateDirectoryMode, CacheDirectoryMode, LogsDirectoryMode and ConfigurationDirectoryMode say. iconic.

These options imply BindPaths for the paths you name ๐Ÿ‘€. And when you pair them with RootDirectory or RootImage, those paths always live on the host and get mounted from there into the unit's file system namespace.

Now, if DynamicUser is in play, the logic for CacheDirectory, LogsDirectory and StateDirectory shifts a little: those dirs get created under /var/cache/private, /var/log/private and /var/lib/private, respectively โ€” host directories that are locked away from unprivileged users, which makes sure nobody can slide into them through dynamic user ID recycling ๐Ÿ”’. Symlinks are created to hide this little difference in behaviour. So both from the host's perspective and from inside the unit, the relevant directories always look like they're sitting directly under /var/cache, /var/log and /var/lib. no cap.

Use RuntimeDirectory to manage one or more runtime directories for the unit and tie their lifetime to the daemon runtime ๐ŸŒ€. It's especially your bestie for unprivileged daemons that can't create runtime directories in /run/ because they just don't have the privileges, and it makes sure the runtime directory gets cleaned up automatically after use. For runtime directories that need more complex or different configuration or lifetime guarantees, please consider using tmpfiles.d(5) instead ๐Ÿซถ.

RuntimeDirectory, StateDirectory, CacheDirectory and LogsDirectory optionally support two more parameters, separated by :. The second parameter gets interpreted as a destination path that will be created as a symlink to the directory ๐Ÿ”—. Those symlinks get created after any BindPaths or TemporaryFileSystem options have been set up, which makes ephemeral symlinking possible. The same source can have multiple symlinks too โ€” same first parameter, different second parameter. The third parameter is a flags field, and since v257 it can take the value ro, which makes the directory read only for the service ๐Ÿฅถ. This is also supported for ConfigurationDirectory. If you set up multiple symlinks, the directory will be read only if at least one of them is configured to be read only. And to pass a flag without a destination symlink, the second parameter can just be empty, like this:

ConfigurationDirectory=foo::ro

The directories defined by these options are always created under the standard paths systemd uses (/var/, /run/, /etc/, โ€ฆ). If your service needs directories somewhere else entirely, you're gonna have to use a different mechanism to create them, bestie ๐Ÿ’€.

tmpfiles.d(5) does provide functionality that overlaps with these options, but using these options is the recommended move โ€” because the lifetime of the directories is tied directly to the lifetime of the unit, and you don't have to make sure the tmpfiles.d configuration gets executed before the unit is started. way less stress โœจ.

To remove any of the directories created by these settings, use the systemctl clean โ€ฆ command on the relevant units โ€” see systemctl(1) for the 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 ๐Ÿช„. Optional. Type uniline.


๐ŸŽ€ RuntimeDirectoryMode

Sets the access mode of the directories named in RuntimeDirectory, StateDirectory, CacheDirectory, LogsDirectory, or ConfigurationDirectory, respectively, as an octal number ๐Ÿ”ข. Defaults to 0755. Want the whole explanation of what permission bits actually mean? See "Permissions" in path_resolution(7). Optional. Type uniline.

๐ŸŽ€ StateDirectoryMode

Same deal โ€” this is the access mode for the directories listed in RuntimeDirectory, StateDirectory, CacheDirectory, LogsDirectory, or ConfigurationDirectory, respectively, written as an octal number ๐Ÿ’œ. Defaults to 0755, and "Permissions" in path_resolution(7) has the full discussion of permission bits if you want it. Optional. Type uniline.

๐ŸŽ€ CacheDirectoryMode

The access mode for the dirs specified in RuntimeDirectory, StateDirectory, CacheDirectory, LogsDirectory, or ConfigurationDirectory, respectively โ€” octal number, bestie ๐Ÿ”ขโœจ. It defaults to 0755. See "Permissions" in path_resolution(7) for what those permission bits are actually doing. Optional. Type uniline.

๐ŸŽ€ LogsDirectoryMode

Specifies the access mode of the directories specified in RuntimeDirectory, StateDirectory, CacheDirectory, LogsDirectory, or ConfigurationDirectory, respectively, as an octal number ๐ŸŒˆ. 0755 is the default. The meaning of permission bits gets discussed in "Permissions" in path_resolution(7). Optional. Type uniline.

๐ŸŽ€ ConfigurationDirectoryMode

One more time, iconically: this is the access mode of the directories specified in RuntimeDirectory, StateDirectory, CacheDirectory, LogsDirectory, or ConfigurationDirectory, respectively, as an octal number ๐Ÿ‘‘. Defaults to 0755. "Permissions" in path_resolution(7) has the whole discussion of permission bits. Optional. Type uniline.

๐Ÿ’พ StateDirectoryQuota

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 (https://man7.org/linux/man-pages/man2/quotactl.2.html). It 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 gets 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, and in that case no storage limits will be set at all.

Heads up: only hard limits are set, not soft limits ๐Ÿšจ. And if the underlying filesystem for the specified directories doesn't support project quotas, the storage limits you asked for just won't be set. On top of enabling per-unit quotas with these settings, you also need to enable prjquota on the file system level (i.e. tune2fs -Q prjquota). Quotas must also be turned on with quotaon (https://linux.die.net/man/8/quotaon). Optional. Type uniline.

๐Ÿ’พ CacheDirectoryQuota

Sets the storage limits for the directories specified in StateDirectory, CacheDirectory, or LogsDirectory respectively ๐ŸŒธ.

Quota here is defined in terms of disk blocks and inodes, as per quotactl (https://man7.org/linux/man-pages/man2/quotactl.2.html). Takes an absolute size limit in bytes, and if you suffix it with K, M, G or T, the size is parsed as Kilobytes, Megabytes, Gigabytes, or Terabytes (base 1024), respectively. When an absolute size limit is given, only the block quota is set (rounded up to the nearest block). Or you can give a percentage value instead, which applies that same percent quota to both blocks and inodes ๐ŸŽฏ. Defaults to off โ€” meaning no storage limits get set.

Only hard limits, never soft limits, bestie ๐Ÿ’€. If the underlying filesystem for those directories doesn't do project quotas, the limits you specified won't be set. And besides enabling per-unit quotas with these settings, you gotta enable prjquota on the file system level too (i.e. tune2fs -Q prjquota). Quotas also need to be turned on with quotaon (https://linux.die.net/man/8/quotaon). Optional. Type uniline.

๐Ÿ’พ 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 (https://man7.org/linux/man-pages/man2/quotactl.2.html). It takes an absolute size limit in bytes. Suffix the value with K, M, G or T and 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, fr fr ๐Ÿšจ. If the underlying filesystem for the specified directories does not support project quotas, the storage limits you specified will not be set. And beyond enabling per-unit quotas with these settings, you have to enable prjquota on the file system level as well (i.e. tune2fs -Q prjquota). Quotas must also be turned on with quotaon (https://linux.die.net/man/8/quotaon). Optional. Type uniline.

๐Ÿ“ˆ StateDirectoryAccounting

Takes a boolean argument ๐Ÿซถ. If true, a project ID is assigned to the directories specified in StateDirectory, CacheDirectory, or LogsDirectory respectively, and that project ID is what gets used for tracking disk usage when disk quotas are turned on (see repquota (https://man7.org/linux/man-pages/man8/repquota.8.html)). Defaults to false.

Okay so quick recap while we're mid-thought here โ€” to actually set and enforce disk quotas you gotta name at least one of StateDirectoryQuota, CacheDirectoryQuota, or LogsDirectoryQuota. Optional. Type boolean.

upstream_default value: no


CacheDirectoryAccounting ๐Ÿ’–

Takes a boolean argument. If true, a project ID gets assigned to the directories you listed in StateDirectory, CacheDirectory, or LogsDirectory (respectively), and that project ID is what tracks disk usage when disk quotas are turned on (go peek at repquota over here: https://man7.org/linux/man-pages/man8/repquota.8.html ). Defaults to false.

And again, bestie, to set and enforce disk quotas, StateDirectoryQuota, CacheDirectoryQuota, or LogsDirectoryQuota must be specified. Optional. Type boolean.

upstream_default value: no


LogsDirectoryAccounting ๐ŸŒธ

Takes a boolean argument. If true, a project ID gets 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 at https://man7.org/linux/man-pages/man8/repquota.8.html ). Defaults to false.

To set and enforce disk quotas, StateDirectoryQuota, CacheDirectoryQuota, or LogsDirectoryQuota must be specified. Optional. Type boolean.

upstream_default value: no


RuntimeDirectoryPreserve ๐Ÿฆ‹

Takes a boolean argument or the string restart. Let's break it down:

  • Set it to no (that's the default) and the directories named in RuntimeDirectory are always removed whenever the service stops. bye bye โœจ
  • Set it to restart and those directories get preserved when the service is restarted โ€” both automatically and manually. And just so we're clear on the vocab: "automatic restart" means whatever operation is specified in Restart, and "manual restart" means the one you trigger with systemctl restart foo.service.
  • Set it to yes and the directories are simply not removed when the service is stopped.
Heads up: since the runtime directory /run/ is a mount point of tmpfs, for system services the directories in RuntimeDirectory get removed anyway when the system is rebooted. rip ๐Ÿ™ˆ

Optional. Type enum. choice: 'no', 'restart', 'yes'.


TimeoutCleanSec โณ

Configures a timeout on the clean-up operation you request through systemctl clean โ€ฆ โ€” see systemctl(1) for the deets. It takes the usual time values and defaults to infinity, meaning by default no timeout is applied at all. But if you do set a timeout, then when that timeout is reached the clean operation gets aborted forcibly, which can potentially leave resources sitting on disk. ๐Ÿ˜ญ Optional. Type uniline.


ReadWritePaths ๐Ÿ‘‘

Sets up a new file system namespace for executed processes. These options let you limit how much access a process has to the file system. Each setting takes a space-separated list of paths, and those paths are relative to the host's root directory (aka the system that's running the service manager). One lil thing to note: if the paths contain symlinks, they get resolved relative to the root directory you set with RootDirectory/RootImage.

So, like, here's the tea. Paths listed in ReadWritePaths are accessible from inside the namespace with the same access modes as from outside of it. Paths listed in ReadOnlyPaths are accessible for reading only โ€” writing gets refused even if the usual file access controls would've said yes. Want writable subdirectories inside read-only directories? Nest ReadWritePaths inside of ReadOnlyPaths. And use ReadWritePaths to allow-list specific paths for write access if ProtectSystem=strict is what you're on. One caveat: ReadWritePaths can not 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 aren't marked read-only. ReadWritePaths only controls the former, not the latter โ€” so a read-only file system superblock stays protected, periodt. ๐Ÿ’…

Paths listed in InaccessiblePaths get made inaccessible for processes inside the namespace, along with everything below them in the file system hierarchy. This might be more restrictive than you actually wanted, because you can't nest ReadWritePaths, ReadOnlyPaths, BindPaths, or BindReadOnlyPaths inside it. Want something more flexible? Go look at TemporaryFileSystem.

Content in paths listed in NoExecPaths is not executable, even if the usual file access controls would permit it. Nest ExecPaths inside of NoExecPaths to provide executable content within non-executable directories.

Non-directory paths can be specified too, bestie. These options can be specified more than once, and if you do, all the listed paths get limited access from within the namespace. And if you assign the empty string to this option, that specific list gets reset and all your prior assignments have no effect. ๐ŸŒˆ

Paths in ReadWritePaths, ReadOnlyPaths, InaccessiblePaths, ExecPaths, and NoExecPaths can be prefixed with -, in which case they'll be ignored when they don't 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). And when you're combining - and + on the same path, make sure you specify - first and + second. order matters, iykyk.

okay this next part is a big vibe check, so listen up ๐Ÿ‘‡

Note that these settings will disconnect propagation of mounts from the unit's processes to the host. Which means this setting may not be used for services that are supposed to be able to install mount points in the main mount namespace. For ReadWritePaths and ReadOnlyPaths, propagation in the other direction isn't 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: mount propagation from host to unit will result in unmodified mounts being 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! So restricting access with these options does not extend to submounts of a directory that are created later on. Which means the lock-down this setting offers is not complete, and does not offer full protection. no cap. ๐Ÿ’€

Note that the effect of these settings may be undone by privileged processes. So to set up an effective sandboxed environment for a unit, it's 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

Optional. Type list of uniline.


ReadOnlyPaths ๐ŸŽ€

Sets up a new file system namespace for executed processes. These options may be used to limit access a process has to the file system. 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 with RootDirectory/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.

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.

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.

โš ๏ธ bestie heads up, these settings can literally get undone by privileged processes, so if you actually want a sandbox that holds, pair them with either CapabilityBoundingSet=~CAP_SYS_ADMIN or SystemCallFilter=~@mount. otherwise you're just doing vibes-based security ๐Ÿ’€

And pls pls pls be careful slapping these onto API file systems (the full list lives in MountAPIVPS) because some of them are kinda load-bearing for basic system functionality, okay?? Also "/run/" has to be writable or setting up the mount namespace and propagation just won't happen. ๐Ÿซถ

Here's the simple allow-list example using these directives, chef's kiss:

[Service]
ReadOnlyPaths=/
ReadWritePaths=/var /run
InaccessiblePaths=-/lost+found
NoExecPaths=/
ExecPaths=/usr/sbin/my_daemon /usr/lib /usr/lib64

. I< Optional. Type list of uniline. >


๐Ÿšซ InaccessiblePaths

Okay so this one sets up a new file system namespace for the executed processes. The whole point of these options is to limit how much of the file system a process can even see. โœจ

Each setting takes a space-separated list of paths that are relative to the host's root directory (aka the system that's running the service manager, not the unit). And lowkey important: if any of those paths contain symlinks, they get resolved relative to the root directory you set with RootDirectory/RootImage. ๐Ÿฆ‹

Quick rundown of the whole squad, because they play off each other:

  • Paths in ReadWritePaths are reachable from inside the namespace with the same access modes as from outside of it.
  • Paths in ReadOnlyPaths are read-only โ€” writes get refused, even if the regular file access controls would've said yes.
  • You nest ReadWritePaths inside ReadOnlyPaths when you want writable subdirectories living inside a read-only directory. ๐Ÿ’…
  • Use ReadWritePaths to allow-list specific paths for write access when ProtectSystem=strict is doing its thing.
  • But ngl, ReadWritePaths cannot be used to gain write access to a file system whose superblock is mounted read-only. On Linux, write access is granted for a mount point only if both the mount point itself and the file system superblock backing it aren't marked read-only. ReadWritePaths only controls the first one, not the second, so a read-only file system superblock stays protected. no cap. ๐Ÿ”ฅ

Now, paths in InaccessiblePaths get made inaccessible for processes inside the namespace, along with everything below them in the file system hierarchy. This might be more restrictive than you actually wanted, because you can't nest ReadWritePaths, ReadOnlyPaths, BindPaths, or BindReadOnlyPaths inside it. Want something more flexible? Go look at TemporaryFileSystem. ๐Ÿงš

Also on the squad:

  • Content in paths listed in NoExecPaths is not executable, even if the usual file access controls would permit it. Nest ExecPaths inside of NoExecPaths to give yourself executable content inside non-executable directories.

Non-directory paths are allowed too, btw. ๐Ÿ‘‘

These options can be specified more than once, and then all the paths you listed get limited access from within the namespace. And if you assign the empty string to the option, that specific list gets reset and every prior assignment you made has no effect anymore. poof, gone. ๐Ÿ™ˆ

Paths in ReadWritePaths, ReadOnlyPaths, InaccessiblePaths, ExecPaths and NoExecPaths may be prefixed with -, and in that case they get ignored when they don't 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). And when you're combining - and + on the same path, make sure to specify - first and + second. order matters bestie โœจ

๐Ÿšจ Okay this next part is a big deal. These settings will disconnect propagation of mounts from the unit's processes to the host. Which means: this setting may not be used for services that need to install mount points in the main mount namespace. like, at all.

For ReadWritePaths and ReadOnlyPaths, propagation in the other direction is not affected โ€” mounts created on the host generally show up in the unit processes' namespace, and mounts removed on the host also vanish there too. But here's the tea: mount propagation from host to unit creates unmodified mounts in the unit's namespace, meaning a writable mount on the host will be writable in the unit's namespace too, even when it's propagated below a path marked with ReadOnlyPaths! ๐Ÿ˜ญ

So restricting access with these options does not extend to submounts of a directory that get created later on. Which means the lock-down this setting offers is not complete, and does not offer full protection. periodt.

โš ๏ธ and again: the effect of these settings may be undone by privileged processes. If you want an effective sandboxed environment for a unit, combine them with either CapabilityBoundingSet=~CAP_SYS_ADMIN or SystemCallFilter=~@mount. ๐Ÿ’–

And once more for the people in the back โ€” be extra careful applying these to API file systems (a list of them is in MountAPIVPS), because they might be required for basic system functionalities. Also, "/run/" needs to be writable for setting up the 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

. I< Optional. Type list of uniline. >


๐ŸŽฏ ExecPaths

Yep, same squad, different member. This sets up a new file system namespace for executed processes, and these options are how you limit how much of the file system a process gets to touch. โœจ

Each setting = a space-separated list of paths relative to the host's root directory (i.e. the system running the service manager). If the paths contain symlinks, they're resolved relative to the root directory set with RootDirectory/RootImage. ๐Ÿฆ‹

  • Paths in ReadWritePaths are accessible from within the namespace with the same access modes as from outside of it.
  • Paths in ReadOnlyPaths are read-only; writing gets refused even if the usual file access controls would permit it.
  • Nest ReadWritePaths inside of ReadOnlyPaths when you want writable subdirectories inside read-only directories.
  • Use ReadWritePaths to allow-list specific paths for write access when ProtectSystem=strict is in play.
  • Caveat time: 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 โ€” so a read-only file system superblock remains protected. ๐Ÿ’…

Paths in InaccessiblePaths get made inaccessible for processes inside the namespace plus everything below them in the file system hierarchy. That might be way more restrictive than you wanted, because it's not possible to nest ReadWritePaths, ReadOnlyPaths, BindPaths, or BindReadOnlyPaths inside it. For a more flexible option, see TemporaryFileSystem. ๐Ÿงš

  • Content in paths listed in NoExecPaths is not executable even if the usual file access controls would permit it. Nest ExecPaths inside of NoExecPaths to provide executable content within non-executable directories.

Non-directory paths may be specified as well. This option can be specified more than once, and then all paths listed will have limited access from within the namespace. If the empty string is assigned, that 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'll be ignored when they don't 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, specify - first and + second. don't fight me on this โœจ

๐Ÿšจ These settings disconnect propagation of mounts from the unit's processes to the host. So this setting may not be used for services that shall be able to install mount points in the main mount namespace.

For ReadWritePaths and ReadOnlyPaths, propagation the other way is not affected: mounts created on the host generally appear in the unit processes' namespace, and mounts removed on the host disappear there too. In particular โ€” mount propagation from host to unit results in unmodified mounts being 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! ๐Ÿ˜ญ So restricting access with these options doesn't extend to submounts of a directory created later on. The lock-down this setting offers is not complete and doesn't offer full protection. ๐Ÿ’€

โš ๏ธ Reminder: the effect of these settings may be undone by privileged processes. For an effective sandboxed environment for a unit, combine them with either CapabilityBoundingSet=~CAP_SYS_ADMIN or SystemCallFilter=~@mount. ๐Ÿ’–

And pleaaase be extra careful 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

. I< Optional. Type list of uniline. >


๐Ÿšท NoExecPaths

Okay new namespace, who dis โ€” this sets up a new file system namespace for executed processes. These options may be used to limit access a process has to the file system. ๐Ÿ‘‘

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 with RootDirectory/RootImage.

Paths that show up in ReadWritePaths are reachable from inside the namespace with exactly the same access modes as from outside of it, no cap. Paths in ReadOnlyPaths are read-only bestie โ€” writing will be refused, even if the usual file access controls would've said yes. So nest ReadWritePaths inside of ReadOnlyPaths when you want writable subdirectories chillin' inside read-only directories ๐Ÿ’…. And use ReadWritePaths to allow-list specific paths for write access if ProtectSystem=strict is on. Big note though: ReadWritePaths can never 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 only granted if the mount point itself and the file system superblock backing it are not marked read-only. ReadWritePaths only controls the first one, not the second, so a read-only file system superblock stays protected, periodt. ๐Ÿ”’

Paths listed in InaccessiblePaths get made inaccessible for processes inside the namespace โ€” along with everything below them in the file system hierarchy, like the whole vibe is gone ๐ŸŒซ๏ธ. Heads up: this may be more restrictive than you actually wanted, because it's just not possible to nest ReadWritePaths, ReadOnlyPaths, BindPaths, or BindReadOnlyPaths inside it. Want something more flexible? See TemporaryFileSystem below bestie.

Content in paths listed in NoExecPaths is not executable, even if the usual file access controls would permit it ๐Ÿ™…โ€โ™€๏ธ. Nest ExecPaths inside of NoExecPaths to give executable content within non-executable directories โ€” it's giving exception list energy.

Non-directory paths may be specified too, no biggie. These options may be specified more than once, in which case all paths listed will have limited access from within the namespace. And if the empty string is assigned to this option, that specific list gets reset, and all prior assignments have no effect. Poof, gone. ๐Ÿ’€

Paths in ReadWritePaths, ReadOnlyPaths, InaccessiblePaths, ExecPaths and NoExecPaths may be prefixed with -, in which case they'll be ignored when they don't exist (no drama, no error ๐Ÿ’โ€โ™€๏ธ). 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 you're combining - and + on the same path, make sure to specify - first and + second. Order matters, iykyk. โœจ

Okay this next bit is important so pay attention bestie: these settings will disconnect propagation of mounts from the unit's processes to the host. Which means this setting may not be used for services that 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. And in particular, note that mount propagation from host to unit will result in unmodified mounts being 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 get created later on. So the lock-down offered by that setting is not complete, and does not offer full protection. Keep that in mind, fr fr.

And ngl, the effect of these settings may be undone by privileged processes. So for an actually effective sandboxed environment for a unit, it's 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, iconic behaviour:

[Service]
ReadOnlyPaths=/
ReadWritePaths=/var /run
InaccessiblePaths=-/lost+found
NoExecPaths=/
ExecPaths=/usr/sbin/my_daemon /usr/lib /usr/lib64

Optional. Type list of uniline. ๐ŸŽ€


๐ŸŒธ 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 that aren't relevant to the processes invoked by the unit, while necessary files or directories can still be accessed by combining with BindPaths or BindReadOnlyPaths. It's giving selective โœจ.

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. Obsessed with the precision. ๐Ÿ’… Optional. Type list of uniline.


๐Ÿซถ 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 for ReadOnlyPaths and related calls, see above. This setting is useful to secure access to temporary files of the process, but it makes sharing between processes via /tmp/ or /var/tmp/ impossible. If DynamicUser is enabled, disconnected is implied. Otherwise, defaults to false.

If true, the backing storage of the private temporary directories will remain on the host's /tmp/ and /var/tmp/ directories. It's 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 get 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.

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 ๐Ÿ˜ณ.

Optional. Type enum. choice: 'disconnected', 'no', 'yes'.


๐Ÿ’– PrivateDevices ๐Ÿ’–

Takes a boolean argument. If true, sets up a new /dev/ mount for the executed processes and only adds API pseudo devices such as /dev/null, /dev/zero or /dev/random (as well as the pseudo TTY subsystem) to it, but no physical devices such as /dev/sda, system memory /dev/mem, system ports /dev/port and others. This is useful to turn 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). Note that using this setting will disconnect propagation of mounts from the service to the host (propagation in the opposite direction continues to work). This 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.

When access to some but not all devices must be possible, the DeviceAllow setting might be used instead. See systemd.resource-control(5). Optional. Type boolean.

upstream_default value :

  • no

๐Ÿฆ‹ PrivateNetwork ๐Ÿฆ‹

Takes a boolean argument. If true, 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. It's 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. Main character energy honestly. ๐Ÿ‘‘

When you slap this on a socket unit, any sockets bound on behalf of this unit get bound inside a private network namespace. You can combo it with JoinsNamespaceOf if you wanna listen on sockets inside the network namespaces of other services. Optional. Type boolean.

  • upstream_default: no

UserNamespacePath

Takes an absolute file system path that points at a Linux user namespace pseudo-file โ€” like a file such as /proc/$PID/ns/user, or a bind mount, or a symlink to one. Set it and the invoked processes get added to the user namespace that path refers to. The path has to point at a valid namespace file at the exact moment the processes are forked off, no exceptions, it's giving precise. If this option is used then PrivateUsers does literally nothing.

This one is only available for system services. Optional. Type uniline.

NetworkNamespacePath

Takes an absolute file system path pointing at a Linux network namespace pseudo-file โ€” a file like /proc/$PID/ns/net, or a bind mount or symlink to one. When set, the invoked processes get added to the network namespace that path references. Again: the path has to point at a valid namespace file at the moment the processes are forked off. If this option is used, PrivateNetwork has no effect. And if you use this together with JoinsNamespaceOf, it only has an effect if this unit is started before any of the listed units that have PrivateNetwork or NetworkNamespacePath configured โ€” otherwise the network namespace of those units gets reused instead. The way that works is lowkey important, don't sleep on it ๐Ÿซถ

When this option is enabled, PrivateMounts is implied unless it's explicitly disabled, and /sys gets remounted to associate it with the new network namespace.

And when you use it on a socket unit, any sockets bound on behalf of this unit get bound within the specified network namespace. Optional. Type uniline.

PrivateIPC

Takes a boolean argument. If true, it sets up a brand new IPC namespace for the executed processes. Every IPC namespace gets its own set of System V IPC identifiers and its own POSIX message queue file system. Super useful for dodging name clashes between IPC identifiers. Defaults to false. You can also run two or more units inside the same private IPC namespace using the JoinsNamespaceOf directive โ€” check systemd.unit(5) for the details bestie.

Heads up: IPC namespacing does not do anything to AF_UNIX sockets, which are literally the most common form of IPC used on Linux. Instead, AF_UNIX sockets in the file system are subject to mount namespacing, and the ones in the abstract namespace are subject to network namespacing. IPC namespacing only has an effect on SysV IPC (which is mostly legacy anyway) and POSIX message queues (for which AF_UNIX/SOCK_SEQPACKET sockets are typically a better replacement). It also has zero effect on POSIX shared memory (that one's subject to mount namespacing too). See ipc_namespaces(7) for the full details.

Also note the implementation of this setting might be impossible โ€” like if IPC namespaces just aren't available โ€” so write your units in a way that doesn't solely rely on this setting for security, fr fr. Optional. Type boolean.

  • upstream_default: no

IPCNamespacePath

Takes an absolute file system path referring to a Linux IPC namespace pseudo-file (a file like /proc/$PID/ns/ipc, or a bind mount or a 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, PrivateIPC has no effect. And if you use this together with JoinsNamespaceOf, then it only has an effect if this unit is started before any of the listed units that have PrivateIPC or IPCNamespacePath configured, as otherwise the network namespace of those units is reused. Optional. Type uniline.

MemoryKSM

Takes a boolean argument. When set, it enables KSM (kernel samepage merging) for the processes. KSM is a memory-saving de-duplication feature: 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, okay? For details, the kernel documentation has a whole page on Kernel Samepage Merging at https://docs.kernel.org/admin-guide/mm/ksm.html.

Note that this functionality might not be available โ€” for example if KSM is disabled in the kernel, or the kernel doesn't support controlling KSM at the process level through prctl(2). Optional. Type boolean.

MemoryTHP

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, like memory regression and latency spikes ๐Ÿ˜ญ

THP policy is governed for the entire system via /sys/kernel/mm/transparent_hugepage/enabled. However, it can be overridden for individual workloads via prctl(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 no prctl(2) PR_SET_THP_DISABLE call is made.
  • When set to disable, MemoryTHP disables THPs completely for the process, irrespective of global THP controls.
  • When set to madvise, MemoryTHP disables THPs for the process except when specifically requested via madvise(2) by the process with MADV_HUGEPAGE or MADV_COLLAPSE.
  • When set to system, MemoryTHP resets the THP policy to the system wide policy. This can be used when the process that starts systemd has already disabled THPs via PR_SET_THP_DISABLE, and we want to restore the system default THP setting at process invocation time.

For details, see Transparent Hugepage Support in the kernel documentation at https://docs.kernel.org/admin-guide/mm/transhuge.html. ๐Ÿฆ‹

Note that this functionality might not be available โ€” for example if THP is disabled in the kernel, or the kernel doesn't support controlling THP at the process level through prctl(2). Optional. Type uniline.

PrivatePIDs

Takes a boolean argument. Defaults to false. If enabled, it sets up a new PID namespace for the executed processes. Every executed process is now PID 1 โ€” the init process โ€” in the new namespace. /proc/ gets mounted such that only processes in the PID namespace are visible. And if PrivatePIDs is set, MountAPIVFS=yes is implied. ๐Ÿ‘‘

PrivatePIDs is only supported for service units. It is not supported with Type=forking, since the kernel will kill all processes in the PID namespace if the init process terminates. It'll also just be ignored if the kernel doesn't support PID namespaces.

One more gotcha: unprivileged user services (i.e. a service run by the per-user instance of the service manager) will fail with PrivatePIDs=yes if /proc/ is masked (like when /proc/kmsg is over-mounted with tmpfs, the way systemd-nspawn(1) does it). That's due to a kernel restriction that doesn't allow unprivileged user namespaces to mount a less restrictive instance of /proc/. Not us, it's the kernel ๐Ÿ’€

Optional. Type boolean.

  • upstream_default: no

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 you set it to a true value or self, a minimal user and group mapping gets configured โ€” one that maps the root user and group, plus the unit's own user and group, to themselves, and maps everything else to the nobody user and group. This is useful to securely detach the user and group databases the unit uses 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 that don't equal root or the unit's own stay visible from inside the unit, but they appear owned by the nobody user and group. Sneaky but in a slay way โœจ

  • 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 get mapped to the nobody user and group, respectively. While this doesn't provide UID/GID isolation, since all UIDs/GIDs are chosen identically, it does provide process capability isolation โ€” so it's often a good choice if proper user namespacing with distinct UID maps isn't 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 lets the unit call setgroups() system calls (by setting /proc/pid/setgroups to allow). Similar to identity, this doesn't provide UID/GID isolation, but it does provide process capability isolation. If this mode is enabled, all unit processes run without privileges in the host user namespace (regardless of whether the unit's own user/group is root or not). Specifically, this means the process will have zero process capabilities on the host's user namespace, but full capabilities within the service's user namespace. Settings like CapabilityBoundingSet will affect only the latter, and there's no way to acquire additional capabilities in the host's user namespace. Periodt.
  • 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 that 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 runs as is different depending on whether you're looking from the host side (where it'll be a high, dynamically assigned UID) or from inside the unit (where it'll be 0). Also note this mode enables 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. โœจ

And 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 gets 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. Main character namespace energy tbh ๐Ÿ’…

That means the only users and groups that actually need to line up are root, nobody, and the unit's own user and group โ€” so you get all the isolation without babysitting a whole second user database. Lowkey a huge win for containers and anything with its own root dir. โœจ Optional. Type enum. choice: 'full', 'identity', 'managed', 'no', 'self', 'yes'.

upstream_default value :
    no

ProtectHostname ๐Ÿท๏ธ

Takes a boolean argument or "private". If enabled, it sets up a brand-new UTS namespace for the executed processes. Spicy detail: you can also hand it a hostname right after a colon (so like yes:foo or private:host.example.com) and that name gets set inside the new UTS namespace for the unit. If it's set to a true value, then changing hostname or domainname via sethostname() and setdomainname() system calls is prevented. If it's set to "private", changing hostname or domainname is allowed but only affects the unit's UTS namespace. Defaults to off.

Heads up bestie: the implementation of this setting might honestly be impossible (for example if UTS namespaces just aren't available on your kernel), so write the unit in a way that doesn't rely solely on this setting for security. No cap. ๐Ÿ˜ญ

Also note: when this option is enabled for a service, hostname changes no longer propagate from the system into the service โ€” so it is not suitable for services that need to take notice of system hostname changes dynamically. ๐Ÿ’€

And one more: this option does not prevent changing the system hostname via hostnamectl. However, User and Group may be used to run as an unprivileged user to disallow changing the system hostname. See SetHostname() in org.freedesktop.hostname1(5) for more details. Optional. Type enum. choice: 'no', 'private', 'yes'.

upstream_default value :
    no

ProtectClock โฐ

Takes a boolean argument. If set, writes to the hardware clock or the system clock will be denied. Defaults to off.

Enabling this option does a whole little routine, and it's iconic:

  • removes CAP_SYS_TIME and CAP_WAKE_ALARM from the capability bounding set for this unit
  • installs a system call filter to block calls that can set the clock
  • DeviceAllow=char-rtc r is 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. See systemd.resource-control(5) for the details about DeviceAllow.

It is recommended to turn this on for most services that do not need to modify the clock or check its state. Optional. Type boolean.

upstream_default value :
    no

ProtectKernelTunables ๐Ÿ”ง

Takes a boolean argument. If true, kernel variables accessible through /proc/sys/, /sys/, /proc/sysrq-trigger, /proc/latency_stats, /proc/acpi, /proc/timer_stats, /proc/fs and /proc/irq will be made read-only, and /proc/kallsyms as well as /proc/kcore will be inaccessible to all processes of the unit.

Usually, tunable kernel variables should be initialized only at boot-time, for example with the sysctl.d(5) mechanism. Few services need to write to these at runtime, so it is hence recommended to turn this on for most services. ๐Ÿซถ For this setting the same restrictions regarding mount propagation and privileges apply as for ReadOnlyPaths and related calls, see above. Defaults to off.

Important caveat, ngl: this option does not prevent indirect changes to kernel tunables affected by IPC calls to other processes. However, InaccessiblePaths may be used to make relevant IPC file system objects inaccessible. Also, if ProtectKernelTunables is set, MountAPIVFS=yes is implied. Optional. Type boolean.

upstream_default value :
    no

ProtectKernelModules ๐Ÿ“ฆ

Takes a boolean argument. If true, explicit module loading will be denied. This lets module load and unload operations be turned off on modular kernels. It is recommended to turn this on for most services that do not need special file systems or extra kernel modules to work. Defaults to off.

Enabling this option:

  • removes CAP_SYS_MODULE from the capability bounding set for the unit
  • installs a system call filter to block module system calls
  • /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.

Note that limited automatic module loading due to user configuration or kernel mapping tables might still happen as a side effect of requested user operations, both privileged and unprivileged. ๐Ÿ™ˆ To disable the module auto-load feature, please see the sysctl.d(5) "kernel.modules_disabled" mechanism and the /proc/sys/kernel/modules_disabled documentation. Optional. Type boolean.

upstream_default value :
    no

ProtectKernelLogs ๐Ÿ“œ

Takes a boolean argument. If true, access to the kernel log ring buffer will be denied. It is recommended to turn this on for most services that do not need to read from or write to the kernel log 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 not to be confused with the libc API syslog(3) for userspace logging, bestie, they are literally different things. ๐Ÿ˜ญ

The kernel exposes its log buffer to userspace via /dev/kmsg and /proc/kmsg. If enabled, these are made inaccessible to all the processes in the unit. Optional. Type boolean.


ProtectControlGroups ๐ŸŽ›๏ธ

Takes a boolean argument or the special values "private" or "strict".

  • If true, the Linux Control Groups (cgroups(7)) hierarchies accessible through /sys/fs/cgroup/ will be made read-only to all processes of the unit.
  • If set to "private", the unit will run in a cgroup namespace with a private writable mount of /sys/fs/cgroup/.
  • If set to "strict", the unit will run in a cgroup namespace with a private read-only mount of /sys/fs/cgroup/.

Defaults to off. If ProtectControlGroups is set, MountAPIVFS=yes is implied.

Note that "private" and "strict" are downgraded to false and true respectively unless the system is using the unified control group hierarchy and the kernel supports cgroup namespaces. Slightly rude of the kernel honestly, but okay. ๐Ÿ’…

Except for container managers, no services should require write access to the control groups hierarchies; it is hence 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. Optional. Type enum. choice: 'no', 'private', 'strict', 'yes'.

upstream_default value :
    no

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 as AF_UNIX, AF_INET or AF_INET6. See address_families(7) for all possible options. When "none" is specified, then all address families will be denied. When prefixed with ~ the listed address families will be applied as a deny list, otherwise as an allow list.

By default, no restrictions apply, all address families are accessible to processes. If assigned the empty string, any previous address family restriction changes are undone. This setting does not affect commands prefixed with +.

Use this option to limit exposure of processes to remote access, in particular via exotic and sensitive network protocols, such as AF_PACKET. Note that in most cases, the local AF_UNIX address family should be included in the configured allow list, as it is frequently used for local communication, including for syslog(2) logging. ๐Ÿซถ

Note that this restricts access to the socket(2) system call only. Sockets passed into the process by other means (for example, by using socket activation with socket units, see systemd.socket(5)) are unaffected. Also, sockets created with socketpair() (which creates connected AF_UNIX sockets) or the io_uring(7) functions are not affected. So like... it's not a vibe-check on everything. Combine it with SystemCallFilter=@service to only allow a limited subset of system calls. โœจ

Note that 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 ignored. On systems supporting multiple ABIs (such as x86/x86-64) it is recommended to turn off alternative ABIs for services, so that they cannot be used to circumvent the restrictions of this option. Specifically, it is recommended to combine this option with SystemCallArchitectures=native or similar. Optional. Type uniline.


RestrictFileSystems ๐Ÿ—‚๏ธ

Restricts the set of filesystems processes of this unit can open files on. Takes a space-separated list of filesystem names. Any filesystem listed is made accessible to the unit's processes, access to filesystem types not listed is prohibited (allow-listing). If the first character of the list is ~, the effect is inverted: access to the filesystems listed is prohibited (deny-listing). If the empty string is assigned, access to filesystems is not restricted.

If you specify both types of this option (i.e. allow-listing and deny-listing), the first encountered will take precedence and will dictate the default action (allow access to the filesystem or deny it). Then the next occurrences of this option will add or delete the listed filesystems from the set of the restricted filesystems, depending on its type and the default action. Order matters, bestie, it's giving โœจfirst-come-first-servedโœจ.

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 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 @ character, followed by name of the set. ๐Ÿ‘‘

Use systemd-analyze(1)'s filesystems command to retrieve a list of filesystems defined on the local system.

Note that this setting might not be supported on some systems (for example if the LSM eBPF hook is not enabled in the underlying kernel or if not using the unified control group hierarchy). In that case this setting has no effect. ๐Ÿ’€ Optional. Type uniline.

๐Ÿ”’ RestrictNamespaces

Okay bestie, this one is a whole bouncer for Linux namespaces, fr. It restricts access to Linux namespace functionality for the processes of this unit. For the deets about Linux namespaces, go peek at namespaces(7).

It either takes a boolean argument, or a space-separated list of namespace type identifiers. So:

  • If it's false (the default), no restrictions on namespace creation and switching are made. Live your life, bestie.
  • If it's true, access to any kind of namespacing is prohibited. Full lockdown. Periodt.
  • Otherwise, you give it a space-separated list of namespace type identifiers, made of any combo of: "cgroup", "ipc", "net", "mnt", "pid", "user", "uts", and "time".

Here's the tea on how that list works: any namespace type you list is made accessible to the unit's processes, and access to namespace types you don't list is prohibited โ€” that's an allow-listing situation. But if you prepend the list with a single tilde character ("~"), the whole effect inverts: only the listed namespace types become inaccessible, and all the unlisted ones are permitted โ€” that's deny-listing. Sneaky, iconic. ๐ŸŒˆ

If the empty string is assigned, the default namespace restrictions get applied, which is equivalent to false.

This option may appear more than once, in which case the namespace types are merged by "OR", or by "AND" if the lines are prefixed with "~" (examples below, keep scrolling ๐Ÿ’…).

Under the hood, internally, this setting limits access to the unshare(2), clone(2) and setns(2) system calls, taking the specified flags parameters into account. And heads up โ€” if this option is used, in addition to restricting creation and switching of the specified types of namespaces (or all of them, if true), access to the setns() system call with a zero flags parameter is prohibited. No sneaky loopholes. ๐Ÿ™ˆ

๐Ÿซถ Architecture note: this setting is only supported on x86, x86-64, mips, mips-le, mips64, mips64-le, mips64-n32, mips64-le-n32, ppc64, ppc64-le, s390 and s390x, and it enforces no restrictions on other architectures.

Example: if a unit has the following,

RestrictNamespaces=cgroup ipc
RestrictNamespaces=cgroup net

then "cgroup", "ipc", and "net" are set. If the second line is prefixed with "~", e.g.,

RestrictNamespaces=cgroup ipc
RestrictNamespaces=~cgroup net

then, only "ipc" is set.

Optional. Type uniline.


๐ŸŽ€ DelegateNamespaces

Now we're giving away ownership like it's concert tickets. This delegates ownership of the given namespace types to the user namespace of the processes of this unit. For the lore about Linux namespaces, see namespaces(7).

Either it takes a boolean argument, or a space-separated list of namespace type identifiers. Let's break it down:

  • If 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.
  • If true, ownership of all namespace types โ€” except for user namespaces, where the concept doesn't apply โ€” created during setup of the unit's sandboxed environment is delegated to the unit's processes' user namespace.
  • Otherwise, you specify a space-separated list of namespace type identifiers, any combo of: "cgroup", "ipc", "net", "mnt", "pid", and "uts". 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 โœจ).

By prepending the list with a single tilde character ("~") the effect may be inverted: 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). Deny-list girlies rise up. ๐Ÿ’…

If the empty string is assigned, the default namespace ownership is applied, which is equivalent to false.

This option may appear more than once, in which case the namespace types are merged by "OR", or by "AND" if the lines are prefixed with "~" (see examples below).

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. Yeah, it said that twice, we love a bit of emphasis. ๐Ÿ˜ญ

Delegating any namespace with "DelegateNamespaces" implies "PrivateUsers=self" unless "PrivateUsers" is explicitly enabled already by the unit. And 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".

Note that some namespace sandboxing options might entail mount namespace for private API VFS instances, such as "PrivatePIDs", "ProtectControlGroups=private/strict", or "PrivateNetwork". If any of the mentioned options are enabled, mount namespace is implicitly delegated.

Optional. Type uniline.


๐Ÿ PrivateBPF

Takes a boolean argument. If set, mount a private instance of the BPF filesystem on /sys/fs/bpf/, effectively hiding the host bpffs 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 needs 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 (https://lwn.net/Articles/947173/). ๐Ÿ‘‘

Optional. Type boolean.

upstream_default value : no


๐Ÿ“œ BPFDelegateCommands

Accepts a list of BPF commands to allow or "any" to allow everything. Defaults to none. The accepted values are:

(This is where the manpage just... stops, bestie ๐Ÿ˜ญ the promised list of accepted values isn't there. Not me leaving it exactly how I found it.)

This will set the "delegate_cmds" bpffs mount option.

Requires "PrivateBPF=yes" to be effective, see "PrivateBPF" for more details.

Optional. Type uniline.


๐Ÿ—บ๏ธ BPFDelegateMaps

Accepts a list of BPF maps to allow or "any" to allow everything. Defaults to none. The accepted values are:

(Same situation, the list vibe-checks itself out of existence. ๐Ÿฆ‹)

This will set the "delegate_maps" bpffs mount option.

Requires "PrivateBPF=yes" to be effective, see "PrivateBPF" for more details.

Optional. Type uniline.


๐Ÿงš BPFDelegatePrograms

Accepts a list of BPF programs to allow or "any" to allow everything. Defaults to none. The accepted values are:

(You guessed it โ€” empty. The list said "not today". ๐Ÿ™ˆ)

This will set the "delegate_progs" bpffs mount option.

Requires "PrivateBPF=yes" to be effective, see "PrivateBPF" for more details.

Optional. Type uniline.


๐Ÿ“ BPFDelegateAttachments

Accepts a list of BPF attach points to allow or "any" to allow everything. Defaults to none. The accepted values are:

(Still nothing! It's giving mystery. ๐Ÿ’€)

This will set the "delegate_attachs" bpffs mount option.

Requires "PrivateBPF=yes" to be effective, see "PrivateBPF" for more details.

Optional. Type uniline.


๐Ÿ” LockPersonality

Takes a boolean argument. If set, it locks down the personality(2) system call so that the kernel execution domain may not be changed from the default or the personality selected with the "Personality" directive.

Why bother? This may be useful to improve security, because odd personality emulations may be poorly tested and a source of vulnerabilities. Basically, weird emulation modes are the sketchy back alley of kernel features, and we're locking the door. ๐Ÿซถ

Optional. Type boolean.


๐Ÿง  MemoryDenyWriteExecute

Takes a boolean argument. If set, these things are prohibited:

  • attempts to create memory mappings that are writable and executable at the same time
  • changing existing memory mappings to become executable
  • mapping shared memory segments as executable

Specifically, a system call filter is added (or preferably, an equivalent kernel check is enabled with prctl(2)) that rejects:

โš ๏ธ Note that this option is incompatible with programs and libraries that generate program code dynamically at runtime, including JIT execution engines, executable stacks, and the code "trampoline" feature of various C compilers. So if your fave runtime does JIT things, this is gonna be a bad time. ๐Ÿ˜ญ

This option improves service security, as it makes it harder for software exploits to change running code dynamically. However, the protection can be circumvented if the service can write to a filesystem which is not mounted with "noexec" (such as /dev/shm), or it can use memfd_create(). This can be prevented by making such file systems inaccessible to the service (e.g. "InaccessiblePaths=/dev/shm") and installing further system call filters ("SystemCallFilter=~memfd_create").

Note that this feature is fully available on x86-64, and partially on x86. Specifically, the shmat() protection is not available on x86.

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 cannot be used to circumvent the restrictions of this option. Specifically, it is recommended to combine this option with "SystemCallArchitectures=native" or similar. Close every door, bestie. ๐Ÿšชโœจ

Optional. Type boolean.


โฐ RestrictRealtime

Takes a boolean argument. If set, any attempts to enable realtime scheduling in a process of the unit are refused. This restricts access to realtime task scheduling policies such as "SCHED_FIFO", "SCHED_RR" or "SCHED_DEADLINE". See sched(7) for the details about these scheduling policies.

Realtime scheduling policies may be used to monopolize CPU time for longer periods of time, and may hence be used to lock up or otherwise trigger Denial-of-Service situations on the system. It is hence recommended to restrict access to realtime scheduling to the few programs that actually require them. Like, share the CPU, it's not that deep. ๐Ÿ’…

Defaults to off.

Optional. Type boolean.

upstream_default value : no


๐Ÿ‘‘ RestrictSUIDSGID

Takes a boolean argument. If set, any attempts to set the set-user-ID (SUID) or set-group-ID (SGID) bits on files or directories will be denied (for details on these bits see inode(7)).

As the SUID/SGID bits are mechanisms to elevate privileges, and allow users to acquire the identity of other users, it is recommended to restrict creation of SUID/SGID files to the few programs that actually require them. Note that this restricts marking of any type of file system object with these bits, including both regular files and directories (where the SGID has a different meaning than for files, see 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.

Optional. Type boolean.

RemoveIPC ๐Ÿ’…

Okay so this one takes a boolean parameter, bestie. If you set it, all System V and POSIX IPC objects that are owned by the user and group the processes of this unit run as get removed when the unit is stopped. But listen: this setting only does anything if at least one of User, Group, and DynamicUser is actually 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 are removed when the last of these units is stopped. This setting is implied if DynamicUser is set. Optional. Type boolean.

PrivateMounts ๐Ÿฆ‹

Takes a boolean parameter. If set, the processes of this unit will 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 will be private to them and not visible to the host. BUT โ€” and this is important โ€” file system mount points established or removed on the host will be propagated to the unit's processes. See mount_namespaces(7) for details on file system namespaces. Defaults to off.

When turned on, this executes three operations for each invoked process:

  1. A new CLONE_NEWNS namespace is created.
  2. After which all existing mounts are remounted to MS_SLAVE to disable propagation from the unit's processes to the host (but leaving propagation in the opposite direction in effect).
  3. 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. Mounts established in the namespace of the process created by ExecStartPre will hence be cleaned up automatically as soon as that process exits and will not 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 enable file system namespacing in a fashion equivalent to this option. Hence it is primarily useful to explicitly request this behaviour if none of the other settings are used. Optional. Type boolean.

  • upstream_default value: no

MountFlags ๐ŸŽ€

Takes a mount propagation setting: shared, slave or private, which controls whether file system mount points in the file system namespaces set up for this unit's processes will receive or propagate mounts and unmounts from other file system namespaces. See mount(2) for details on mount propagation, and the three propagation flags in particular.

This setting only controls the final propagation setting in effect on all mount points of the file system namespace created for each process of this unit. Other file system namespacing unit settings (see the discussion 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. Setting this option to shared does not reestablish propagation in that case.

If not set โ€“ but file system namespaces are enabled through another file system namespace unit setting โ€“ shared mount propagation is used, but โ€” as mentioned โ€” as slave is applied first, propagation from the unit's processes to the host is still turned off.

Not recommended: using private mount propagation for units, because this means temporary mounts (such as removable media) of the host will stay mounted and thus indefinitely busy in forked off processes, as unmount propagation events will not be received by the file system namespace of the unit. ๐Ÿ’€

Usually, it is best to leave this setting unmodified, and use higher level file system namespacing options instead, in particular PrivateMounts, see above. Optional. Type uniline.

SystemCallFilter ๐Ÿ”ฅ

Takes a space-separated list of system call names or system call groups. If this setting is used, system calls executed by the unit processes except for the listed ones will result in the system call being denied (allow-listing). If the first character of the list is ~, the effect is inverted: only the listed system calls will be denied (deny-listing). 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.

Commands prefixed with + are not subject to filtering. The execve(), exit(), exit_group(), getrlimit(), rt_sigreturn(), sigreturn() system calls and the system calls for querying time and sleeping are implicitly allow-listed and do not need to be listed explicitly.

The default action when a system call is denied is to terminate the processes with a SIGSYS signal. This can be changed using SystemCallErrorNumber, see below. In addition, deny-listed system calls and system call groups may optionally be suffixed with a colon (:) and an argument in the same format as SystemCallErrorNumber, to take this action when the matching system call is made. This takes precedence over the action specified in SystemCallErrorNumber.

This feature makes use of the Secure Computing Mode 2 interfaces of the kernel ('seccomp filtering') and is useful for enforcing a minimal sandboxing environment.

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 cannot 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 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 of its type and the default action. (For example, if you have started with an allow list rule for read() and write(), and right after it add a deny list rule for write(), then write() will be removed from the set.)

Okay so bestie, here's the tea on how seccomp gets manageable: because the number of possible system calls is astronomically large ๐Ÿ”ฅ, predefined groups of system calls are provided. A group starts with an @ character, followed by name of the set. Currently predefined system call sets:

  • @aio โ€” Asynchronous I/O (io_setup(2), io_submit(2), and related calls)
  • @basic-io โ€” System calls for basic I/O: reading, writing, seeking, file descriptor duplication and closing (read(2), write(2), and related calls)
  • @chown โ€” Changing file ownership (chown(2), fchownat(2), and related calls)
  • @clock โ€” System calls for changing the system clock (adjtimex(2), settimeofday(2), and related calls)
  • @cpu-emulation โ€” System calls for CPU emulation functionality (vm86(2) and related calls)
  • @debug โ€” Debugging, performance monitoring and tracing functionality (ptrace(2), perf_event_open(2) and related calls)
  • @file-system โ€” File system operations: opening, creating files and directories for read and write, renaming and removing them, reading file properties, or creating hard and symbolic links
  • @io-event โ€” Event loop system calls (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 of memory in RAM (mlock(2), mlockall(2) and related calls)
  • @module โ€” Loading and unloading of kernel modules (init_module(2), delete_module(2) and related calls)
  • @mount โ€” Mounting and unmounting of file systems (mount(2), chroot(2), and related calls)
  • @network-io โ€” Socket I/O (including local AF_UNIX): socket(7), unix(7)
  • @obsolete โ€” Unusual, obsolete or unimplemented (create_module(2), gtty(2), โ€ฆ)
  • @pkey โ€” System calls that deal with memory protection keys (pkeys(7))
  • @privileged โ€” All system calls which need super-user capabilities (capabilities(7))
  • @process โ€” Process control, execution, namespacing operations (clone(2), kill(2), namespaces(7), โ€ฆ)
  • @raw-io โ€” Raw I/O port access (ioperm(2), iopl(2), pciconfig_read(), โ€ฆ)
  • @reboot โ€” System calls for rebooting and reboot preparation (reboot(2), kexec(), โ€ฆ)
  • @resources โ€” System calls for changing resource limits, memory and scheduling parameters (setrlimit(2), setpriority(2), โ€ฆ)
  • @sandbox โ€” System calls for sandboxing programs (seccomp(2), Landlock system calls, โ€ฆ)
  • @setuid โ€” System calls for changing user ID and group ID credentials, (setuid(2), setgid(2), setresuid(2), โ€ฆ)
  • @signal โ€” System calls for manipulating and handling process signals (signal(2), sigprocmask(2), โ€ฆ)
  • @swap โ€” System calls for enabling/disabling swap devices (swapon(2), swapoff(2))
  • @sync โ€” Synchronizing files and memory to disk (fsync(2), msync(2), and related calls)
  • @system-service โ€” A reasonable set of system calls used by common system services, excluding any special purpose calls. This is the recommended starting point for allow-listing system calls for system services, as it contains what is typically needed by system services, but excludes overly specific interfaces. For example, the following APIs are excluded: @clock, @mount, @swap, @reboot.
  • @timer โ€” System calls for scheduling operations by time (alarm(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.

Not me about to break some news tho: 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

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. Optional. Type list of uniline.

๐ŸŒˆ 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. Optional. Type uniline.

๐Ÿฆ‹ 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. Optional. Type uniline.

โœจ 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 "+". Optional. Type list of uniline.

๐Ÿ“ Environment

Sets environment variables for executed processes. Each line is unquoted using the rules described in "Quoting" section in systemd.syntax(7) and becomes a list of variable assignments. If you need to assign a value containing spaces or the equals sign to a variable, put quotes around the whole assignment. Variable expansion is not performed inside the strings and the "$" character has no special meaning. Specifier expansion is performed, see the "Specifiers" section in systemd.unit(5). ๐Ÿ‘‘

This option may be specified more than once, in which case all listed variables will be set. If the same variable is listed twice, the later setting will override the earlier setting. If the empty string is assigned to this option, the list of environment variables is reset, all prior assignments have no effect.

The names of the variables can contain ASCII letters, digits, and the underscore character. Variable names cannot be empty or start with a digit. In variable values, most characters are allowed, but non-printable characters are currently rejected.

Example:

Environment="VAR1=word1 word2" VAR2=word3 "VAR3=$word 5 6"

gives three variables "VAR1", "VAR2", "VAR3" with the values "word1 word2", "word3", "$word 5 6".

See environ(7) for details about environment variables.

Note that environment variables are not suitable for passing secrets (such as passwords, key material, โ€ฆ) to service processes. Environment variables set for a unit are exposed to unprivileged clients via D-Bus IPC, and generally not understood as being data that requires protection. Moreover, environment variables are propagated down the process tree, including across security boundaries (such as setuid/setgid executables), and hence might leak to processes that should not have access to the secret data. Use "LoadCredential", "LoadCredentialEncrypted" or "SetCredentialEncrypted" (see below) to pass data to unit processes securely. ๐Ÿ˜ญ๐Ÿ”

Optional. Type list of uniline.

๐ŸŽ€ EnvironmentFile ๐ŸŽ€

Okay bestie, this is Environment's slightly extra sibling: same energy, but it reads the environment variables out of a text file instead of making you type them inline. ๐Ÿ’–

The text file should contain newline-separated variable assignments. Lines that are empty, lines with no = separator, and lines starting with ; or # all get ignored โ€” which is lowkey iconic because that's literally how you comment in there. The file must be encoded with UTF-8. Valid characters are unicode scalar values (https://www.unicode.org/glossary/#unicode_scalar_value) other than unicode noncharacters (https://www.unicode.org/glossary/#noncharacter), U+0000 NUL, and U+FEFF unicode byte order mark (https://www.unicode.org/glossary/#byte_order_mark). Control codes other than NUL are allowed. โœจ

In the file, an unquoted value after the = is parsed with the same backslash-escape rules as POSIX shell unquoted text (https://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_02_01), but โ€” and this is the plot twist โ€” unlike in a shell, interior whitespace is preserved and quotes after the first non-whitespace character are preserved too. Leading and trailing whitespace (space, tab, carriage return) gets discarded, but interior whitespace within the line stays preserved verbatim. A line ending with a backslash gets continued onto the following one, and the newline itself is discarded. A backslash \ followed by any character other than newline preserves that following character, so \\ will become the value \.

In the file, a '-quoted value after the = can span multiple lines and contain any character verbatim other than a single quote, just like POSIX shell single-quoted text (https://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_02_02). No backslash-escape sequences are recognized here, none, zero, nope. 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 (https://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_02_03). A backslash (\) followed by any of ", \, `, or $ preserves 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.

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. This means you can generate these files in one unit state, and read it 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 will be read in the order they are specified and the later setting will override the earlier setting. Optional. Type list of uniline.


๐Ÿฆ‹ PassEnvironment ๐Ÿฆ‹

This one passes environment variables that are set for the system service manager over to the executed processes. It takes a space-separated list of variable names. You can specify this option more than once, and then all listed variables get 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 you specify that aren't set for the system manager won't be passed and will be silently ignored. ๐Ÿ™ˆ

Heads up though: 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. But 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. ๐Ÿ’…

Variables set for invoked processes because of 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. Optional. Type list of uniline.


๐Ÿงš UnsetEnvironment ๐Ÿงš

This one explicitly unsets environment variable assignments that would normally be passed from the service manager to invoked processes of this unit. It 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.

Here's the deets on how it decides what to nuke:

  • 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). It's giving final boss energy, fr fr. ๐Ÿ‘‘

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. Optional. Type list of uniline.


๐Ÿ’œ 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.

  • null โ€” standard input will be connected to /dev/null, i.e. all read attempts by the process will result in an immediate EOF.
  • tty โ€” standard input is connected to a TTY (as configured by TTYPath, 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.
  • tty-force โ€” similar to tty, but the executed process is forcefully and immediately made the controlling process of the terminal, potentially removing previous controlling processes from the terminal.
  • tty-fail โ€” similar to tty, but if the terminal already has a controlling process, start-up of the executed process fails.
  • data โ€” for configuring arbitrary textual or binary data to pass via standard input to the executed process. The data to pass is configured via StandardInputText/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.
  • file:path โ€” connects a specific file system object to standard input. An absolute path following the : character is expected, which may refer to a regular file, a FIFO or special file. If an AF_UNIX socket in the file system is specified, a stream socket is connected to it. The latter is useful for connecting standard input of processes to arbitrary system services.
  • socket โ€” valid in socket-activated services only, and requires the relevant socket unit file (see systemd.socket(5) for details) to have Accept=yes set, or to specify a single socket only. If this option is set, standard input will be connected to the socket the service was activated from, which is primarily useful for compatibility with daemons designed for use with the traditional inetd(8) socket activation daemon ($LISTEN_FDS (and related) environment variables are not passed when socket value is configured).
  • fd:name โ€” connects standard input to a specific, named file descriptor provided by a socket unit. The name may be specified as part of this option, following a : character (e.g. fd:foobar). If no name is specified, the name stdin is implied (i.e. fd is equivalent to fd:stdin). At least one socket unit defining the specified name must be provided via the Sockets 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. See FileDescriptorName in systemd.socket(5) for more details about named file descriptors and their ordering.

This setting defaults to null, unless StandardInputText/StandardInputData are set, in which case it defaults to data. Optional. Type enum. choice: 'data', 'null', 'socket', 'tty', 'tty-fail', 'tty-force'.


๐ŸŒˆ 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.
  • null โ€” connects standard output to /dev/null, i.e. everything written to it will be lost.
  • tty โ€” connects standard output to a tty (as configured via TTYPath, see below). If the TTY is used for output only, the executed process will not become the controlling process of the terminal, and will not fail or wait for other processes to release the terminal.
Note: if a unit tries to print multiple lines to a TTY during bootup or shutdown, then there's a chance that those lines will be broken up by status messages. SetShowStatus() can be used to prevent this problem. See org.freedesktop.systemd1(5) for details.
  • "journal" โ€” hooks standard output up to the journal, and bestie you can go peek at it with journalctl(1) ๐Ÿ’–. Heads up tho: literally everything that gets written to kmsg (see below, it's coming) is implicitly stored in the journal too, so that kmsg option is genuinely a superset of this one. Also also โ€” any external, additional syslog daemons get their log data from the journal as well, so this is the option you wanna use when your logging is gonna be processed by one of those daemons. iykyk ๐ŸŒธ
  • "kmsg" โ€” connects standard output to the kernel log buffer, which you can reach via dmesg(1), in addition to the journal. Tiny plot twist: the journal daemon might already be configured to send all logs to kmsg anyway, and in that case this option is literally no different from "journal". ๐Ÿคทโ€โ™€๏ธ
  • "journal+console" and "kmsg+console" โ€” same vibe as the two above, but they also copy the output to the system console. โœจ
  • "file:path" โ€” this one connects a specific file system object to standard output. The semantics are similar to the same option of "StandardInput", see above. If path refers to a regular file on the filesystem, it gets opened (and created if it doesn't exist yet, using the privileges of the user executing the systemd process) for writing at the beginning of the file, but without truncating it. And here's the cute part ๐ŸŽ€: 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 file system, because in that case only a single stream connection gets created for both input and output. Obsessed fr fr.
  • "append:path" โ€” similar to "file:path" above, but it opens the file in append mode. ๐Ÿ’…
  • "truncate:path" โ€” also similar to "file:path" above, but it truncates the file when opening it. Now listen, for units with multiple command lines โ€” like "Type=oneshot" services with multiple "ExecStart", or services with "ExecCondition", "ExecStartPre" or "ExecStartPost" โ€” the output file gets reopened and therefore re-truncated for each command line. And if the output file is truncated while another process still has the file open, e.g. by an "ExecReload" running concurrently with an "ExecStart", and that 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. ๐Ÿ’€ So basically, "truncate:path" is typically only useful for units where only one process runs at a time โ€” like services with a single "ExecStart" and no "ExecStartPost", "ExecReload", "ExecStop" or similar. Not me warning you twice bestie.
  • "socket" โ€” connects standard output to a socket acquired via socket activation. Semantics are similar to the same option of "StandardInput", see above. ๐Ÿฆ‹
  • "fd:name" โ€” the moment we've all been waiting for ๐Ÿ‘‘: this option 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 the "Sockets" 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. See "FileDescriptorName" in systemd.socket(5) for more details about named descriptors and their ordering. ๐ŸŽ€

Okay so here's the big-girl caveat section, and it matterrrrs:

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. Slay, problem solved. โœจ

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). Optional. Type enum. choice: 'inherit', 'journal', 'journal+console', 'kmsg', 'kmsg+console', 'null', 'socket', 'tty'.


๐ŸŒˆ 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, bestie, so don't zone out ๐Ÿ™ˆ: 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). Optional. Type uniline.


๐ŸŒธ StandardInputText ๐ŸŒธ

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. Vibes only, no external files. ๐Ÿ’œ

"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 (thus 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 <https://tools.ietf.org/html/rfc2045#section-6.8>. 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
โ€ฆ

Optional. Type uniline. ๐Ÿซถ


๐Ÿ’– 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.

"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 (thus 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 <https://tools.ietf.org/html/rfc2045#section-6.8>. 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
โ€ฆ

Optional. Type uniline. ๐Ÿ˜Œ


๐Ÿ‘‘ LogLevelMax ๐Ÿ‘‘

Sets the maximum log level for log messages generated by this unit. It takes a syslog log level, one of:

  • "emerg" โ€” lowest log level, only highest priority messages ๐Ÿ’€
  • "alert"
  • "crit"
  • "err"
  • "warning"
  • "notice"
  • "info"
  • "debug" โ€” highest log level, also lowest priority messages ๐Ÿ—ฃ๏ธ

See syslog(3) for details. By default, the maximum log level is not overridden. โญ

Alright so continuing on LogLevelMax โ€” the level you set gets applied to any log message written by any process that belongs to this unit, and also any log messages the system or user manager processes write about this unit, no matter which supported logging protocol they came through. The override kicks in super early in the logging pipeline, before anything else gets done to it. And here's the tea: messages that make it through this filter can still get dropped by filters that live later in the logging subsystem. Like, MaxLevelStore in journald.conf(5) might straight up forbid higher log levels from being stored on disk, even though the per-unit LogLevelMax said it was fine to process. Optional. Type uniline. ๐Ÿ’…


โœจ LogExtraFields โœจ

This one configures extra log metadata fields to be included in all log records generated by processes associated with this unit, systemd included. The setting takes one or more journal field assignments in the format FIELD=VALUE, separated by whitespace. Peep systemd.journal-fields(7) for the whole journal field concept. Even though the underlying journal implementation is out here allowing binary field values, this setting only accepts valid UTF-8 values, periodt. If you wanna include space characters in a journal field value, wrap the assignment in double quotes ("). The usual specifiers get expanded in all assignments (see below). ๐Ÿซถ

And ngl, this isn't just for slapping extra metadata onto a unit's log records โ€” because all fields and values are indexed, you can also use it to implement cross-unit log record matching. Assign an empty string to reset the list.

Heads up bestie: this functionality is currently only available in system services, not in per-user services. Optional. Type uniline.

โœจ LogRateLimitIntervalSec โœจ

Configures the rate limiting that gets applied to log messages generated by this unit. So if, inside the time interval defined by LogRateLimitIntervalSec, more messages get logged by a service than what's specified in LogRateLimitBurst, all further messages within that interval get dropped until the interval is over. A message about the number of dropped messages gets generated. ๐Ÿ—‘๏ธ

The time specification for LogRateLimitIntervalSec may be given in the following units: s, min, h, ms, us. See systemd.time(7) for the details. The default settings are set by RateLimitIntervalSec and RateLimitBurst configured in journald.conf(5).

Note that this only applies to log messages that get processed by the logging subsystem โ€” i.e. by systemd-journald.service(8). So if you connect a service's stderr directly to a file via StandardOutput=file:โ€ฆ or something similar, the rate limiting will not be applied to messages written that way (but it will be enforced for messages generated via syslog(3) and similar functions). Optional. Type uniline.

โœจ LogRateLimitBurst โœจ

Configures the rate limiting that gets applied to log messages generated by this unit. If, in the time interval defined by LogRateLimitIntervalSec, more messages than what's specified in LogRateLimitBurst are logged by a service, all further messages within the interval get dropped until the interval is over. A message about the number of dropped messages gets generated. ๐Ÿ—‘๏ธ

The time specification for LogRateLimitIntervalSec may be specified in the following units: s, min, h, ms, us. See systemd.time(7) for details. The default settings are set by RateLimitIntervalSec and RateLimitBurst configured in journald.conf(5).

Note that this only applies to log messages that are processed by the logging subsystem, i.e. by systemd-journald.service(8). This means that if you connect a service's stderr directly to a file via StandardOutput=file:โ€ฆ or a similar setting, the rate limiting will not be applied to messages written that way (but it will be enforced for messages generated via syslog(3) and similar functions). Optional. Type uniline.

โœจ LogFilterPatterns โœจ

Define an extended regular expression to filter log messages based on the MESSAGE field of the structured message. If the first character of the pattern is ~, log entries matching that pattern should be discarded. This option takes a single pattern as an argument, but you can use it multiple times to build up a list of allowed and denied patterns. ๐ŸŒˆ

If the empty string is assigned, the filter gets reset, and all prior assignments have no effect whatsoever.

Now the sneaky part: because the ~ character is what defines denied patterns, you have to replace it with \x7e if you want to allow a message that 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.

Here's the order of operations, bestie. Log messages are tested against denied patterns (if any), then against allowed patterns (if any). If a log message matches any of the denied patterns, it is discarded immediately, without even considering the allowed patterns. Remaining log messages get tested against allowed patterns. Messages that match 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. And 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. Optional. Type uniline.

โœจ 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 this, the processes of the service run in the default journal namespace, i.e. their log stream is collected and processed by systemd-journald.service.

If this option is used, any log data generated by processes of this unit (regardless of whether it came via the syslog(), journal native logging or stdout/stderr logging) gets collected and processed by an instance of the systemd-journald@.service template unit, which manages the specified namespace. The log data is stored in a data store independent from the default log namespace's data store. See systemd-journald.service(8) for the details about 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 used, this setting disconnects propagation of mounts from the unit's processes to the host โ€” similar to how ReadOnlyPaths and similar settings described above work. So journal namespaces may hence not be used for services that need to establish mount points on the host. ๐ŸŒธ

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're automatically established prior to the unit starting up. And note: when this option is used, log output of this service does not show up in the regular journalctl(1) output, unless the --namespace= option is used. Optional. Type uniline.


โœจ SyslogIdentifier โœจ

Sets the process name (syslog tag) to prefix log lines sent to the logging system or the kernel log buffer with. If it's not set, it defaults to the process name of the executed process. This option is only useful when StandardOutput or StandardError are set to journal or kmsg (or to the same settings in combination with +console), and it only applies to log messages written to stdout or stderr. Optional. Type uniline. ๐ŸŽ€


โœจ SyslogFacility โœจ

Sets the syslog facility identifier to use when logging. One of kern, user, mail, daemon, auth, syslog, lpr, news, uucp, cron, authpriv, ftp, local0, local1, local2, local3, local4, local5, local6 or local7. See syslog(3) for the details. This option is only useful when StandardOutput or StandardError are set to journal or kmsg (or to the same settings in combination with +console), and only applies to log messages written to stdout or stderr. Defaults to daemon. Optional. Type uniline. โœจ


โœจ SyslogLevel โœจ

The default syslog log level to use when logging to the logging system or the kernel log buffer. One of emerg, alert, crit, err, warning, notice, info, debug. See syslog(3) for details. This option is only useful when StandardOutput or StandardError are set to journal or kmsg (or to the same settings in combination with +console), and only applies to log messages written to stdout or stderr.

Also fyi: 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 those prefixes may be disabled with SyslogLevelPrefix, see below. For details, peep sd-daemon(3). Defaults to info. Optional. Type uniline. ๐Ÿ‘‘


โœจ SyslogLevelPrefix โœจ

Takes a boolean argument. If true, and StandardOutput or StandardError are set to journal or kmsg (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 that log level set but the prefix removed. If set to false, the interpretation of these prefixes is disabled and the logged lines get passed on as-is. This only applies to log messages written to stdout or stderr. For details about this prefixing, see sd-daemon(3). Defaults to true. Optional. Type boolean.

  • upstream_default value : yes

โœจ TTYPath โœจ

Sets the terminal device node to use if standard input, output, or error are connected to a TTY (see above). Defaults to /dev/console. Optional. Type uniline. ๐Ÿฆ‹


โœจ TTYReset โœจ

Reset the terminal device specified with TTYPath before and after execution. And jsyk, this does not erase the screen (see TTYVTDisallocate below for that one). Defaults to no. Optional. Type uniline. ๐Ÿ’ซ


โœจ TTYVHangup โœจ

Disconnect all clients which have opened the terminal device specified with TTYPath before and after execution. Defaults to no. Optional. Type uniline. ๐Ÿซถ

TTYColumns ๐ŸŽ€

Ok bestie, this one sets the size of the TTY you named over in TTYPath. Specifically the columns โ€” the width, the horizontal situation. If you leave it unset or set it to the empty string, it tries to grab the dimensions of the terminal screen via ANSI sequences, and if that flops, the kernel defaults (typically 80x24) get used. Optional. Type uniline.

TTYRows ๐ŸŽ€

Same energy, other axis: this configures the size of the TTY specified with TTYPath, but for the rows โ€” the height. If unset or set to the empty string, it tries to retrieve the terminal screen dimensions via ANSI sequences, and if that fails, kernel defaults (typically 80x24) it is. Optional. Type uniline.

TTYVTDisallocate ๐Ÿ’…

If the terminal device you gave in TTYPath is a virtual console terminal, it'll try to deallocate the TTY before and after execution. Why do we care? Because that clears the screen and the scrollback buffer โ€” no leftover vibes, no receipts. ๐Ÿ”ฅ If the terminal device is any other type of TTY, it makes an attempt to clear the screen via ANSI sequences instead. Defaults to no. Optional. Type uniline.


LoadCredential ๐Ÿ”‘โœจ

Pass a credential to the unit, periodt. So what even is a credential? They're limited-size binary or textual objects that may be passed to unit processes. Their main character purpose is passing cryptographic keys (both public and private) or certificates, user account information, or identity information from host to services โ€” but honestly they can be freely used to pass any kind of limited-size information to a service. The data is accessible from the unit's processes via the file system, at a read-only location that (if possible and permitted) is backed by non-swappable memory. That data is only accessible to the user associated with the unit, via the User/DynamicUser settings (plus the superuser, obviously ๐Ÿ‘‘). When available, the location of credentials gets exported as the $CREDENTIALS_DIRECTORY environment variable to the unit's processes. ๐Ÿซถ

The LoadCredential setting takes a textual ID to use as a name for a credential plus a file system path, separated by a colon. The ID must be a short ASCII string suitable as a filename in the filesystem, and you may choose it freely. If the specified path is absolute it's opened as a regular file and the credential data is read from it. And if that absolute path refers to an AF_UNIX stream socket in the file system? A connection is made to it (once at process invocation) and the credential data gets read from the connection โ€” which is basically an easy IPC integration point for dynamically transferring credentials from other services. โœจ

If the specified path is not absolute and itself qualifies as a valid credential identifier, it attempts to find a credential that the service manager itself received under the specified name โ€” which may be used 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 will search these directories for files under the credential's name โ€” which hence are the recommended locations for credential data on disk:

  • /etc/credstore/
  • /run/credstore/
  • /usr/lib/credstore/

If LoadCredentialEncrypted is used, then these get searched as well:

  • /run/credstore.encrypted/
  • /etc/credstore.encrypted/
  • /usr/lib/credstore.encrypted/

Meanwhile the per-user service manager searches these instead:

  • $XDG_CONFIG_HOME/credstore/
  • $XDG_RUNTIME_DIR/credstore/
  • $HOME/.local/lib/credstore/

(and 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 it's chosen identical to the credential name โ€” i.e. this is a terse way to declare credentials to inherit from the service manager or credstore directories into a service. This option may be used multiple times, each time defining an additional credential to pass to the unit. No cap on vibes. ๐Ÿ’…

Heads up bestie: if the path is not specified or a valid credential identifier is given โ€” i.e. in the above two cases โ€” a missing credential is not considered fatal.

If an absolute path referring to a directory is specified, every file in that directory (recursively, yes, all of it ๐ŸŒ€) will be loaded as a separate credential. The ID for each credential will be the provided ID suffixed with _$FILENAME (e.g., Key_file1). When loading from a directory, symlinks will be ignored. ๐Ÿšซ๐Ÿ”—

The contents of the file/socket may be arbitrary binary or textual data, including newline characters and NUL bytes. Anything goes fr fr.

The LoadCredentialEncrypted setting is identical to LoadCredential, except that the credential data is decrypted and authenticated before being passed on to the executed processes. Specifically, the referenced path should refer to 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, in the 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 improves security because credentials are not stored in plaintext, and only authenticated and decrypted into plaintext the moment a service requiring them is started. Moreover, credentials may be bound to the local hardware and installations, so that they cannot easily be analyzed offline, or be generated externally. See systemd.resource-control(5) for the details about DevicePolicy or DeviceAllow. ๐Ÿ”

Note note note: 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 the system as a whole, and it is ensured that per-user service managers cannot decrypt secrets intended for the system or for other users. โœ‹

The credential files/IPC sockets must 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 particularly useful in combination with DynamicUser, because this 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. Slick, honestly. ๐ŸŒˆ

So how do you actually reference the path a credential may be read from? If you're inside a ExecStart command line, use ${CREDENTIALS_DIRECTORY}/mycred, e.g.:

ExecStart=cat ${CREDENTIALS_DIRECTORY}/mycred

If you're inside 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 does not 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. Yes, one megabyte. Budget accordingly bestie. ๐Ÿ˜ญ

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 documentation at https://systemd.io/CONTAINER_INTERFACE for details about the former. For the latter, pass DMI/SMBIOS OEM string table entries (field type 11) โ€” that's https://www.dmtf.org/standards/smbios โ€” 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 binary case, the right-hand side is Base64 decoded when parsed (thus permitting binary data to be passed in). Example qemu switch (https://www.qemu.org/docs/master/system/index.html):

-smblos...

wait no, exact:

-sm bios type=11,value=io.systemd.credential:xx=yy

ok for real, verbatim:

-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 NULRANDOM /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 the System and Service Credentials documentation at https://systemd.io/CREDENTIALS. Optional. Type uniline.

LoadCredentialEncrypted ๐Ÿ”๐Ÿ’–

Pass a credential to the unit. Credentials are limited-size binary or textual objects that may be passed to unit processes. They are primarily intended for passing cryptographic keys (both public and private) or certificates, user account information or identity information from host to services, but can be freely used to pass any kind of limited-size information to a service. The data is accessible from the unit's processes via the file system, at a read-only location that (if possible and permitted) is backed by non-swappable memory. The data is only accessible to the user associated with the unit, via the User/DynamicUser settings (as well as the superuser). When available, the location of credentials is exported as the $CREDENTIALS_DIRECTORY environment variable to the unit's processes. ๐Ÿฆ‹

So LoadCredential takes a textual ID (that's the name, bestie ๐Ÿ’–) plus a file system path, and they're glued together with a colon. The ID must be a short ASCII string that would work as a filename in the filesystem โ€” but like, you get to pick it, it's your moment, choose freely ๐Ÿ’….

  • If the path you give is absolute, it gets opened as a regular file and the credential data is read straight out of it. Easy.
  • If that absolute path points at an AF_UNIX stream socket sitting in the file system? A connection is made to it (once, at process invocation, okay, so no reconnecting shenanigans) and the credential data is read from the connection. This is genuinely such a slay feature because it's an easy IPC integration point for dynamically transferring credentials from other services. ๐Ÿฆ‹

If the path is not absolute and it itself qualifies as a valid credential identifier, we try to find a credential that the service manager itself received under that name โ€” which is how you propagate credentials from an invoking environment (say, a container manager that invoked the service manager) into a service. Level-up energy. ๐Ÿ‘‘

If no matching passed credential is found, the system service manager goes digging in these directories for files under the credential's name:

  • /etc/credstore/
  • /run/credstore/
  • /usr/lib/credstore/

โ€ฆwhich, for the record, are the recommended locations for credential data on disk. ๐ŸŒธ

If LoadCredentialEncrypted is used, these get searched too:

  • /run/credstore.encrypted/
  • /etc/credstore.encrypted/
  • /usr/lib/credstore.encrypted/

Meanwhile the per-user service manager searches these instead:

  • $XDG_CONFIG_HOME/credstore/
  • $XDG_RUNTIME_DIR/credstore/
  • $HOME/.local/lib/credstore/

(and the counterparts ending with โ€ฆ/credstore.encrypted/, iykyk). And if you want to know the precise credential store search path, the systemd-path(1) tool can query it for you. Go ask it. โœจ


Okay so, if you omit the file system path entirely? It gets chosen identical to the credential name. That's the terse little shortcut way to declare credentials to inherit from the service manager or the credstore directories into a service. Chef's kiss. ๐Ÿ˜Œ

This option may be used multiple times, each time defining an additional credential to pass to the unit. Stack 'em up.

Note: if the path is not specified, or a valid credential identifier is given โ€” those are the two cases above โ€” then a missing credential is not considered fatal. Chill. It's fine. ๐Ÿซถ

If an absolute path referring to a directory is specified, then every single file in that directory (recursively, we're going deep ๐Ÿ•ณ๏ธ) will be loaded as a separate credential. The ID for each credential will be the provided ID suffixed with _$FILENAME โ€” like, for example, Key_file1. And when loading from a directory? Symlinks will be ignored. Not today, symlink.

The contents of the file/socket may be arbitrary binary or textual data, including newline characters and NUL bytes. Wild, right? It just does that.


โœจ LoadCredentialEncrypted โœจ

LoadCredentialEncrypted is identical to LoadCredential, except that the credential data is decrypted and authenticated before being passed on to the executed processes. Specifically: the path you reference should point to a file or socket with an encrypted credential, as implemented by systemd-creds(1). That credential is loaded, decrypted, authenticated, and then handed to the application in plaintext form, in exactly the same way a regular credential specified via LoadCredential would be. Same vibe, extra security soup. ๐Ÿ”

A credential configured this way may be symmetrically encrypted/authenticated with:

  • a secret key derived from the system's TPM2 security chip, or
  • a secret key stored in /var/lib/systemd/credential.secret, or
  • both (go big or go home)

Using encrypted and authenticated credentials improves security because credentials are not stored in plaintext and only get authenticated and decrypted into plaintext the exact moment a service requiring them is started. Moreover, credentials may be bound to the local hardware and installations, so that they cannot easily be analyzed offline, or be generated externally. Iconic behavior, truly. ๐Ÿ”ฅ

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 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. No snooping. ๐Ÿšซ๐Ÿ‘€

The credential files/IPC sockets must be accessible to the service manager, but they do not have to be directly accessible to the unit's processes. Why? Because the credential data is read and copied into separate, read-only copies for the unit that are accessible to appropriately privileged processes. Very demure, very mindful. ๐Ÿ’…

This is particularly useful in combination with DynamicUser, because this 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. Security arc: complete. ๐ŸŒˆ

In order to reference the path a credential may be read from within a 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 a 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 โ€” like, say, configuration files of software that doesn't yet support credentials natively. ๐Ÿ™ˆ That said, $CREDENTIALS_DIRECTORY is considered the primary interface to look for credentials, though, since it also works for user services. Primary. Main character. โญ

Currently, an accumulated credential size limit of 1 MB per unit is enforced. So no, you cannot smuggle the entire internet in there. ๐Ÿ˜ญ

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 <https://systemd.io/CONTAINER_INTERFACE> documentation for details about the former. For the latter, pass DMI/SMBIOS <https://www.dmtf.org/standards/smbios> 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 <https://www.qemu.org/docs/master/system/index.html> 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 ๐Ÿ‘€ yikes).

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. Efficiency queen. ๐Ÿ‘‘

For further information see System and Service Credentials <https://systemd.io/CREDENTIALS> documentation.

Optional. Type uniline.


ImportCredential

Pass one or more credentials to the unit. ๐ŸŽ€

It takes a credential name, and for that name we will attempt to find a credential that the service manager itself received under the specified name โ€” which may be used to propagate credentials from an invoking environment (e.g. a container manager that invoked the service manager) into a service. Bestie, it's the handoff. ๐Ÿค

If the credential name is a glob, all credentials matching the glob are 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/
  • /usr/lib/credstore.encrypted/

โ€ฆin that order. When multiple credentials of the same name are found, the first one found is used. First come, first served, 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 likeโ€ฆ behave. ๐Ÿ™ˆ

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 achieved. โœจ

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. Sorry to the later ones. ๐Ÿ’€

When multiple credentials of the same name are found, credentials found by LoadCredential and LoadCredentialEncrypted take priority over credentials found by ImportCredential. Hierarchy exists.

Note that if decryption or authentication of a credential picked up as a result of ImportCredential 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 via SetCredentialEncrypted/LoadCredentialEncrypted, where failed decryption/authentication will result in service failure. So likeโ€ฆ one of these is a lot more dramatic than the other. ๐Ÿ˜ญ

Optional. Type uniline.

SetCredential ๐ŸŽ€

okay so SetCredential is giving LoadCredential's chaotic cousin โœจ same energy, different delivery โ€” instead of pointing at a file system path to read the data from, it just takes a literal value and uses that as the credential data. ๐Ÿ’…

๐Ÿšจ bestie listen: do NOT use this option for data that is supposed to be secret. It's accessible to unprivileged processes via IPC, fr fr. It's only safe to use this for user IDs, public key material and similar non-sensitive data. For everything else use LoadCredential. ๐Ÿซถ

and if you need to embed binary data into the credential data, you use C-style escaping โ€” like "\n" to embed a newline, or "\x00" to embed a "NUL" byte. iconic little escape hatch, iykyk. ๐Ÿฆ‹

The SetCredentialEncrypted setting is identical to SetCredential but it expects an encrypted credential in literal form as its value. This is the way to embed confidential credentials securely directly in unit files. ๐ŸŒˆ Use systemd-creds(1)'s -p switch to generate suitable SetCredentialEncrypted lines directly from plaintext credentials. For further details see LoadCredentialEncrypted above. โญ

Now the priority drama: when multiple credentials of the same name are found, credentials found by LoadCredential, LoadCredentialEncrypted and ImportCredential take priority over credentials found by SetCredential. So SetCredential is basically the understudy โ€” it'll act as default if no credentials are found by any of the former. ๐Ÿ‘‘ And in this case, not being able to retrieve the credential from the path specified in LoadCredential or LoadCredentialEncrypted is not considered fatal. slay, no crashouts.

Optional. Type uniline.

SetCredentialEncrypted ๐Ÿ”

okay so SetCredential is similar to LoadCredential but accepts a literal value to use as data for the credential, instead of a file system path to read the data from. ๐Ÿ’ซ

๐Ÿ’€ literally do not use this option for data that is supposed to be secret, because 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. and if you wanna 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)'s -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. ๐ŸŒธ

Optional. Type uniline.


UtmpIdentifier ๐Ÿงš

Takes a four character identifier string for a utmp(5) and wtmp entry for this service. bestie, this should only be set for services such as getty implementations (such as agetty(8)) where utmp/wtmp entries must be created and cleared before and after execution, or for services that shall be executed as if they were run by a getty process (see below). ๐Ÿ‘‘

If the configured string is longer than four characters, it is truncated and the terminal four characters are used. This setting interprets %I style string replacements. ๐Ÿ’… This setting is unset by default, i.e. no utmp/wtmp entries are created or cleaned up for this service. not me leaving it blank on purpose.

Optional. Type uniline.

UtmpMode ๐ŸŒ™

Takes one of "init", "login" or "user". If UtmpIdentifier is set, this controls which type of utmp(5)/wtmp entries for this service are generated. This setting has no effect unless UtmpIdentifier is set too โ€” they're a package deal, fr fr. ๐Ÿซถ

  • If "init" is set, only an "INIT_PROCESS" entry is generated and the invoked process must implement a getty-compatible utmp/wtmp logic.
  • If "login" is set, first an "INIT_PROCESS" entry, followed by a "LOGIN_PROCESS" entry is generated. In this case, the invoked process must implement a login(1)-compatible utmp/wtmp logic.
  • If "user" is set, first an "INIT_PROCESS" entry, then a "LOGIN_PROCESS" entry and finally a "USER_PROCESS" entry is generated. In this case, the invoked process may be any process that is suitable to be run as session leader. main character energy fr. โœจ

Defaults to "init". Optional. Type enum. choice: 'init', 'login', 'user'.

KillMode ๐Ÿ’€

Specifies how processes of this unit shall be killed. One of "control-group", "mixed", "process", "none".

  • If set to "control-group", all remaining processes in the control group of this unit will be killed on unit stop (for services: after the stop command is executed, as configured with ExecStop). ๐Ÿ”ฅ
  • If set to "mixed", the "SIGTERM" signal (see below) is sent to the main process while the subsequent "SIGKILL" signal (see below) is sent to all remaining processes of the unit's control group. ๐Ÿ’ฅ
  • If set to "process", only the main process itself is killed (not recommended!). ๐Ÿ™ˆ
  • If set to "none", no process is killed (strongly recommended against!). ๐Ÿ’€ In this case, only the stop command will be executed on unit stop, but no process will be killed otherwise. Processes remaining alive after stop are left in their control group and the control group continues to exist after stop unless empty.
๐Ÿ˜ญ note that it is not recommended to set KillMode to "process" or even "none", as this allows processes to escape the service manager's lifecycle and resource management, and to remain running even while their service is considered stopped and is assumed to not consume any resources. like... why would you.

Processes will first be terminated via "SIGTERM" (unless the signal to send is changed via KillSignal or RestartKillSignal). Optionally, this is immediately followed by a "SIGHUP" (if enabled with SendSIGHUP). ๐Ÿฆ‹ If processes still remain after:

  • the main process of a unit has exited (applies to KillMode: "mixed")
  • the delay configured via the TimeoutStopSec has passed (applies to KillMode: "control-group", "mixed", "process")

โ€ฆthe termination request is repeated with the "SIGKILL" signal or the signal specified via FinalKillSignal (unless this is disabled via the SendSIGKILL option). See kill(2) for more information. ๐Ÿ‘‘

Defaults to "control-group". Optional. Type uniline.

KillSignal ๐ŸŒธ

Specifies which signal to use when stopping a service. This controls the signal that is sent as first step of shutting down a unit (see above), and is usually followed by "SIGKILL" (see above and below). For a list of valid signals, see signal(7). ๐Ÿ’… Defaults to "SIGTERM".

Note that, right after sending the signal specified in this setting, systemd will always send "SIGCONT", to ensure that even suspended tasks can be terminated cleanly. so thoughtful of it honestly. ๐Ÿซถ Optional. Type uniline.

RestartKillSignal ๐Ÿ”„

Specifies which signal to use when restarting a service. The same as KillSignal described above, with the exception that this setting is used in a restart job. Not set by default, and the value of KillSignal is used. โœจ Optional. Type uniline.

SendSIGHUP ๐ŸŒป

Specifies whether to send "SIGHUP" to remaining processes immediately after sending the signal configured with KillSignal. This is useful to indicate to shells and shell-like programs that their connection has been severed. ๐Ÿ˜ญ Takes a boolean value. Defaults to "no". Optional. Type boolean.

upstream_default value: no

SendSIGKILL โšก

Specifies whether to send "SIGKILL" (or the signal specified by FinalKillSignal) to remaining processes after a timeout, if the normal shutdown procedure left processes of the service around. ๐Ÿฉท When disabled, a KillMode of "control-group" or "mixed" service will not restart if processes from prior services exist within the control group. Takes a boolean value. Defaults to "yes". Optional. Type boolean.

upstream_default value: yes

FinalKillSignal ๐Ÿชฆ

Specifies which signal to send to remaining processes after a timeout if SendSIGKILL is enabled. The signal configured here should be one that is not typically caught and processed by services ("SIGTERM" is not suitable). ๐Ÿšซ Developers can find it useful to use this to generate a coredump to troubleshoot why a service did not terminate upon receiving the initial "SIGTERM" signal. This can be achieved by configuring LimitCORE and setting FinalKillSignal to either "SIGQUIT" or "SIGABRT". ๐Ÿ’€ Defaults to "SIGKILL". Optional. Type uniline.

WatchdogSignal ๐Ÿ•

Specifies which signal to use to terminate the service when the watchdog timeout expires (enabled through WatchdogSec). Defaults to "SIGABRT". Optional. Type uniline.

Type ๐Ÿ‘‘ โœจ

Configures the mechanism via which the service notifies the manager that the service start-up has finished. One of "simple", "exec", "forking", "oneshot", "dbus", "notify", "notify-reload", or "idle":

It is recommended to use Type="exec" for long-running services, as it ensures that process setup errors (e.g. errors such as a missing service executable, or missing user) are properly tracked. ๐Ÿ” However, as this service type will not propagate the failures in the service's own startup code (as opposed to failures in the preparatory steps the service manager executes before execve()) and does not allow ordering of other units against completion of initialization of the service code itself (which for example is useful if clients need to connect to the service through some form of IPC, and the IPC channel is only established by the service itself โ€” in contrast to doing this ahead of time through socket or bus activation or similar), it might not be sufficient for many cases. ๐Ÿ˜ญ

If so, "notify", "notify-reload", or "dbus" (the latter only in case the service provides a D-Bus interface) are the preferred options as they allow service program code to precisely schedule when to consider the service started up successfully and when to proceed with follow-up units. ๐Ÿ’… The "notify"/"notify-reload" service types require explicit support in the service codebase (as sd_notify() or an equivalent API needs to be invoked by the service at the appropriate time) โ€” if it is not supported, then "forking" is an alternative: it supports the traditional heavy-weight UNIX service start-up protocol. ๐Ÿ‹๏ธ

Note that using any type other than "simple" possibly delays the boot process, as the service manager needs to wait for at least some service initialization to complete. (Also note it is generally not recommended to use "idle" or "oneshot" for long-running services.) ๐Ÿ™ˆ

okay so a lil PSA first, bestie, and it's lowkey important: some service settings โ€” think "User", "Group" getting resolved through libc NSS โ€” can sneak in "hidden" blocking IPC calls to other services when you use them. ๐Ÿ™ˆ Sometimes it's genuinely advisable to just use the "simple" service type so that the service manager's transaction logic doesn't get dragged around by those potentially slow operations and hidden dependencies, because "simple" is literally the only service type where the service manager will not wait for all that service execution setup stuff to finish before it proceeds. Optional. Type uniline.

๐Ÿ‘‹ ExitType

Specifies when the manager should consider the service to be finished, periodt. One of "main" or "cgroup":

Generally recommended: use "ExitType"="main" when a service has a known forking model and a main process can reliably be determined. "ExitType"="cgroup" is meant for applications whose forking model is not known ahead of time and which might not have a specific main process. It's so well suited for transient or automatically generated services โ€” like graphical applications inside a desktop environment, the way that โœจ. Optional. Type uniline.

๐Ÿซถ RemainAfterExit

Takes a boolean value saying whether the service shall be considered active even when all its processes have exited. Defaults to "no". Optional. Type boolean.

  • upstream_default value :
no

๐Ÿ”ฎ GuessMainPID

Takes a boolean value specifying whether systemd should try to guess the main PID of a service if it can't be determined reliably. Heads up though: this option is ignored unless "Type=forking" is set and "PIDFile" is unset, because for the other types or when there's an explicitly configured PID file, the main PID is always known. The guessing algorithm might come to incorrect conclusions if a daemon consists of more than one process. And if the main PID can't be determined, failure detection and automatic restarting of a service will not work reliably. ๐Ÿ˜ญ Defaults to "yes". Optional. Type boolean.

  • upstream_default value :
yes

๐Ÿ“ PIDFile

Takes a path referring to the PID file of the service. Usage of this option is recommended for services where "Type" is set to "forking". The path you specify typically points to a file below /run/. If a relative path is specified for a system service, it gets prefixed with /run/, and it's prefixed with $XDG_RUNTIME_DIR if specified in a user service. The service manager will read the PID of the main process of the service from this file after start-up of the service. It will not write to the file configured here โ€” though it will remove the file after the service has shut down if it still exists. The PID file doesn't need to be owned by a privileged user, but if it is owned by an unprivileged user, extra safety restrictions get enforced: the file may not be a symlink to a file owned by a different user (neither directly nor indirectly), and the PID file must refer to a process already belonging to the service. ๐Ÿ”’

ngl, PID files should be avoided in modern projects. Use "Type=notify", "Type=notify-reload" or "Type=simple" where possible โ€” none of those need PID files to figure out the main process of a service, and they avoid needless forking. iconically efficient.

Optional. Type uniline.

๐ŸŒˆ BusName

Takes a D-Bus destination name that this service shall use. This option is mandatory for services where "Type" is set to "dbus". It's recommended to always set this property if known, so it's easy to map the service name to the D-Bus destination. In particular, systemctl service-log-level/service-log-target verbs make use of it. Optional. Type uniline.

๐Ÿ’… ExecStart

Commands that are executed when this service is started.

Unless "Type" is "oneshot", exactly one command must be given. When "Type=oneshot" is used, this setting may be used multiple times to define multiple commands to execute. If the empty string is assigned to this option, the list of commands to start gets reset and prior assignments of this option will have no effect. And if no "ExecStart" is specified, then the service must have "RemainAfterExit=yes" and at least one "ExecStop" line set. (Yeah bestie, services lacking both "ExecStart" and "ExecStop" are simply not valid.) ๐Ÿ’€

If more than one command is configured, the commands are invoked sequentially in the order they appear in the unit file. If one of the commands fails (and is not prefixed with -), the other lines are not executed, and the unit is considered failed.

Unless "Type=forking" is set, the process started via this command line will be considered the main process of the daemon. Optional. Type list of uniline.

โœจ ExecStartPre

Additional commands that are executed before or after the command in "ExecStart", respectively. Syntax is the same as for "ExecStart". Multiple command lines are allowed, regardless of the service type (i.e. "Type"), and the commands are executed one after the other, serially.

If any of those commands (not prefixed with -) fail, the rest are not executed and the unit is considered failed.

"ExecStart" commands are only run after all "ExecStartPre" commands that were not prefixed with a - exit successfully.

"ExecStartPost" commands are only run after the commands specified in "ExecStart" have been invoked successfully, as determined by "Type" (i.e. the process has been started for "Type=simple" or "Type=idle", the last "ExecStart" process exited successfully for "Type=oneshot", the initial process exited successfully for "Type=forking", "READY=1" is sent for "Type=notify"/"Type=notify-reload", or the "BusName" has been taken for "Type=dbus").

not me having to say this but: "ExecStartPre" may not be used to start long-running processes. All processes forked off by processes invoked via "ExecStartPre" will be killed before the next service process is run. ๐Ÿซ 

Also note: if any of the commands specified in "ExecStartPre", "ExecStart", or "ExecStartPost" fail (and are not prefixed with -, see above) or time out before the service is fully up, execution continues with commands specified in "ExecStopPost", and the commands in "ExecStop" are skipped.

And the execution of "ExecStartPost" is taken into account for the purpose of "Before"/"After" ordering constraints. Optional. Type list of uniline.

๐Ÿ”ฅ ExecStartPost

Additional commands that are executed before or after the command in "ExecStart", respectively. Syntax is the same as for "ExecStart". Multiple command lines are allowed, regardless of the service type (i.e. "Type"), and the commands are executed one after the other, serially.

If any of those commands (not prefixed with -) fail, the rest are not executed and the unit is considered failed.

"ExecStart" commands are only run after all "ExecStartPre" commands that were not prefixed with a - exit successfully.

"ExecStartPost" commands are only run after the commands specified in "ExecStart" have been invoked successfully, as determined by "Type" (i.e. the process has been started for "Type=simple" or "Type=idle", the last "ExecStart" process exited successfully for "Type=oneshot", the initial process exited successfully for "Type=forking", "READY=1" is sent for "Type=notify"/"Type=notify-reload", or the "BusName" has been taken for "Type=dbus").

Note that "ExecStartPre" may not be used to start long-running processes. All processes forked off by processes invoked via "ExecStartPre" will be killed before the next service process is run.

Note that if any of the commands specified in "ExecStartPre", "ExecStart", or "ExecStartPost" fail (and are not prefixed with -, see above) or time out before the service is fully up, execution continues with commands specified in "ExecStopPost", the commands in "ExecStop" are skipped.

Note that the execution of "ExecStartPost" is taken into account for the purpose of "Before"/"After" ordering constraints. Optional. Type list of uniline.

๐ŸŒธ ExecCondition

Optional commands that are executed before the commands in "ExecStartPre". Syntax is the same as for "ExecStart". Multiple command lines are allowed, regardless of the service type (i.e. "Type"), and the commands are executed one after the other, serially.

The behavior is like an "ExecStartPre" and condition check hybrid: when an "ExecCondition" command exits with exit code 1 through 254 (inclusive), the remaining commands are skipped and the unit is not marked as failed. But if an "ExecCondition" command exits with 255 or abnormally (e.g. timeout, killed by a signal, etc.), the unit will be considered failed (and the remaining commands will be skipped). Exit code of 0 or those matching "SuccessExitStatus" will continue execution to the next commands. ๐ŸŽ€

The same recommendations about not running long-running processes in "ExecStartPre" also apply to "ExecCondition". And "ExecCondition" will also run the commands in "ExecStopPost", as part of stopping the service, in the case of any non-zero or abnormal exits, like the ones described above. Optional. Type list of uniline.

๐Ÿ’œ ExecReload

Commands to execute to trigger a configuration reload in the service. This setting may take multiple command lines, following the same scheme as described for "ExecStart" above. Use of this setting is optional. Specifier and environment variable substitution is supported here, following the same scheme as for "ExecStart".

One extra, special environment variable gets set: if known, $MAINPID is set to the main process of the daemon, and you can use it for command lines like this:

ExecReload=kill -HUP $MAINPID
okay but heads up bestie: reloading a daemon by enqueuing a signal without completion notification (like the example line above) is usually not a good choice, because that's an asynchronous operation and hence not suitable when you're ordering reloads of multiple services against each other. ๐Ÿ˜ฌ It is thus strongly recommended to either use "Type=notify-reload", or to set "ExecReload" to a command that not only triggers a configuration reload of the daemon, but also synchronously waits for it to complete. For example, dbus-broker(1) uses the following:
ExecReload=busctl call org.freedesktop.DBus \
/org/freedesktop/DBus org.freedesktop.DBus \
ReloadConfig

This setting can be combined with "Type=notify-reload", in which case the service main process is signaled after all specified command lines finish execution. Specially, if a "RELOADING=1" notification is received before "ExecReload" completes, the signaling is skipped and the service manager immediately starts listening for "READY=1". Optional. Type list of uniline.

๐Ÿฆ‹ ExecReloadPost

Commands to execute after a successful reload operation. Syntax for this setting is exactly the same as "ExecReload". Optional. Type list of uniline.

๐Ÿ›‘ ExecStop

Commands to execute to stop the service that got started via ExecStart ๐Ÿ’…. This argument takes multiple command lines, following the same scheme as described for ExecStart above. Use of this setting is optional. After the commands configured in this option are run, it's implied that the service is stopped, and any processes remaining for it are terminated according to the KillMode setting (see systemd.kill(5)). If this option is not specified, the process is terminated by sending the signal specified in KillSignal or RestartKillSignal when service stop is requested. Specifier and environment variable substitution is supported (including $MAINPID, see above) โœจ.

โš ๏ธ Real talk bestie: it's usually not sufficient to specify a command for this setting that only asks the service to terminate (like, sends it some form of termination signal) but does not wait for it to actually do so. Because the remaining processes of the services are killed according to KillMode and KillSignal or RestartKillSignal as described above immediately after the command exited, this may not result in a clean stop ๐Ÿ˜ญ. The specified command should hence be a synchronous operation, not an asynchronous one. periodt.
Note that the commands specified in ExecStop are only executed when the service started successfully first ๐Ÿ‘‘. They are not invoked if the service was never started at all, or in case its start-up failed โ€” for example because any of the commands specified in ExecStart, ExecStartPre or ExecStartPost failed (and were not prefixed with -, see above) or timed out. Use ExecStopPost to invoke commands when a service failed to start up correctly and is shut down again. Also note that the stop operation is always performed if the service started successfully, even if the processes in the service terminated on their own or were killed ๐Ÿ’€. The stop commands must be prepared to deal with that case. $MAINPID will be unset if systemd knows that the main process exited by the time the stop commands are called.

Service restart requests are implemented as stop operations followed by start operations. Which means ExecStop and ExecStopPost are executed during a service restart ๐Ÿ”.

It is recommended to use this setting for commands that communicate with the service requesting clean termination ๐Ÿ’–. For post-mortem clean-up steps use ExecStopPost instead.

Optional. Type list of uniline.


๐Ÿงน ExecStopPost

Additional commands that are executed after the service is stopped. This includes cases where the commands configured in ExecStop were used, where the service does not have any ExecStop defined, or where the service exited unexpectedly ๐Ÿ™ˆ. This argument takes multiple command lines, following the same scheme as described for ExecStart. Use of these settings is optional. Specifier and environment variable substitution is supported. Note that โ€” unlike ExecStop โ€” commands specified with this setting are invoked when a service failed to start up correctly and is shut down again ๐ŸŽ€.

It is recommended to use this setting for clean-up operations that shall be executed even when the service failed to start up correctly. Commands configured with this setting need to be able to operate even if the service failed starting up half-way and left incompletely initialized data around ๐Ÿ˜ญ. As the service's processes have likely exited already when the commands specified with this setting are executed, they should not attempt to communicate with them.

Note that all commands that are configured with this setting are invoked with the result code of the service, as well as the main process' exit code and status, set in the $SERVICE_RESULT, $EXIT_CODE and $EXIT_STATUS environment variables โ€” see systemd.exec(5) for details ๐Ÿ’ซ.

Note that the execution of ExecStopPost is taken into account for the purpose of Before/After ordering constraints.

Optional. Type list of uniline.


๐Ÿ˜ด RestartSec

Configures the time to sleep before restarting a service (as configured with Restart). Takes a unit-less value in seconds, or a time span value such as 5min 20s. Defaults to 100ms โฐ.

Optional. Type uniline.


๐Ÿ“ˆ RestartSteps

Configures the number of exponential steps to take to increase the interval of auto-restarts from RestartSec to RestartMaxDelaySec. Takes a positive integer or 0 to disable it. Defaults to 0. Hint: values between 3 and 5 are good choices when exponential backoff is desired ๐Ÿ”ฅ.

Example:

RestartSec=10s
RestartSteps=4
RestartMaxDelaySec=160s

This will produce the following restart intervals: 10s, 20s, 40s, 80s, 160s, 160s, 160s, etc. Notice the geometric interpolation and the resulting constant ratio between intervals; here it is 2 ๐ŸŒˆ. The formula for the ratio is

("RestartMaxDelaySec" / "RestartSec")^(1 / "RestartSteps") .

A (repeating) delay equal to RestartMaxDelaySec is always reached after RestartSteps + 1 steps.

This setting is effective only if RestartMaxDelaySec is also set and RestartSec is not zero ๐Ÿ‘€.

Optional. Type uniline.


๐Ÿ›‘ RestartMaxDelaySec

Configures the longest time to sleep before restarting a service as the interval goes up with RestartSteps ๐Ÿง—. Takes a value in the same format as RestartSec, or infinity to disable the setting. Defaults to infinity.

This setting is effective only if RestartSteps is also set and RestartSec is not zero.

Optional. Type uniline.


โณ TimeoutStartSec

Configures the time to wait for start-up. If a daemon service does not signal start-up completion within the configured time, the service will be considered failed and will be shut down again ๐Ÿ’€. The precise action depends on the TimeoutStartFailureMode option. Takes a unit-less value in seconds, or a time span value such as 5min 20s. Pass infinity to disable the timeout logic ๐Ÿšซ. Defaults to DefaultTimeoutStartSec set in the manager, except when Type=oneshot is used, in which case the timeout is disabled by default (see systemd-system.conf(5)).

If a service of Type=notify/Type=notify-reload sends EXTEND_TIMEOUT_USEC=โ€ฆ, this may cause the start time to be extended beyond TimeoutStartSec โฑ๏ธ. The first receipt of this message must occur before TimeoutStartSec is exceeded, and once the start time has extended beyond TimeoutStartSec, the service manager will allow the service to continue to start, provided the service repeats EXTEND_TIMEOUT_USEC=โ€ฆ within the interval specified until the service startup status is finished by READY=1 (see sd_notify(3)).

Note that the start timeout is also applied to service reloads, regardless of whether implemented through ExecReload or via the reload logic enabled via Type=notify-reload ๐Ÿ‘‘. If the reload does not complete within the configured time, the reload will be considered failed and the service will continue running with the old configuration. This will not affect the running service, but will be logged and will cause e.g. systemctl reload to fail ๐Ÿ˜ฌ.

Optional. Type uniline.


๐ŸงŠ TimeoutStopSec

This option serves two purposes, bestie:

  1. First, it configures the time to wait for each ExecStop command. If any of them times out, subsequent ExecStop commands are skipped and the service will be terminated by SIGTERM โšก. If no ExecStop commands are specified, the service gets the SIGTERM immediately. This default behavior can be changed by the TimeoutStopFailureMode option.
  2. Second, it configures the time to wait for the service itself to stop. If it does not terminate in the specified time, it will be forcibly terminated by SIGKILL (see KillMode in systemd.kill(5)) ๐Ÿ’€.

Takes a unit-less value in seconds, or a time span value such as 5min 20s. Pass infinity to disable the timeout logic. Defaults to DefaultTimeoutStopSec from the manager configuration file (see systemd-system.conf(5)).

If a service of Type=notify/Type=notify-reload sends EXTEND_TIMEOUT_USEC=โ€ฆ, this may cause the stop time to be extended beyond TimeoutStopSec โฑ๏ธ. The first receipt of this message must occur before TimeoutStopSec is exceeded, and once the stop time has extended beyond TimeoutStopSec, the service manager will allow the service to continue to stop, provided the service repeats EXTEND_TIMEOUT_USEC=โ€ฆ within the interval specified, or terminates itself (see sd_notify(3)).

Optional. Type uniline.


๐Ÿ’ฅ TimeoutAbortSec

This option configures the time to wait for the service to terminate when it was aborted due to a watchdog timeout (see WatchdogSec). If the service has a short TimeoutStopSec, this option can be used to give the system more time to write a core dump of the service ๐Ÿ“. Upon expiration the service will be forcibly terminated by SIGKILL (see KillMode in systemd.kill(5)). The core file will be truncated in this case ๐Ÿ˜ญ. Use TimeoutAbortSec to set a sensible timeout for the core dumping per service that is large enough to write all expected data while also being short enough to handle the service failure in due time.

Takes a unit-less value in seconds, or a time span value such as 5min 20s. Pass an empty value to skip the dedicated watchdog abort timeout handling and fall back to TimeoutStopSec. Pass infinity to disable the timeout logic. Defaults to DefaultTimeoutAbortSec from the manager configuration file (see systemd-system.conf(5)).

If a service of Type=notify/Type=notify-reload handles SIGABRT itself (instead of relying on the kernel to write a core dump) it can send EXTEND_TIMEOUT_USEC=โ€ฆ to extended the abort time beyond TimeoutAbortSec ๐Ÿซถ. The first receipt of this message must occur before TimeoutAbortSec is exceeded, and once the abort time has extended beyond TimeoutAbortSec, the service manager will allow the service to continue to abort, provided the service repeats EXTEND_TIMEOUT_USEC=โ€ฆ within the interval specified, or terminates itself (see sd_notify(3)).

Optional. Type uniline.


โœจ TimeoutSec

A shorthand for configuring both TimeoutStartSec and TimeoutStopSec to the specified value ๐Ÿ’….

Optional. Type uniline.


๐Ÿšจ TimeoutStartFailureMode

These options configure the action that is taken in case a daemon service does not signal start-up within its configured TimeoutStartSec, respectively if it does not stop within TimeoutStopSec. Takes one of terminate, abort and kill. Both options default to terminate.

okay so if this is set to terminate, your service gets the โœจgentleโœจ treatment:

  • terminate โ€” the service is gracefully terminated by sending the signal specified in KillSignal (which defaults to SIGTERM, peep systemd.kill(5) if you want the lore). And if the service doesn't terminate? Then the FinalKillSignal is sent after TimeoutStopSec. It said what it said ๐Ÿ’…
  • abort โ€” WatchdogSignal is sent instead, and TimeoutAbortSec applies before sending FinalKillSignal. This setting may be used to analyze services that fail to start-up or shut-down intermittently. like, detective mode fr.
  • kill โ€” the service is immediately terminated by sending FinalKillSignal without any further timeout. No waiting era. This setting can be used to expedite the shutdown of failing services.

Optional. Type enum. choice: 'abort', 'kill', 'terminate'.


๐Ÿ›‘ TimeoutStopFailureMode

These options configure the action that is taken in case a daemon service does not signal start-up within its configured TimeoutStartSec, respectively if it does not stop within TimeoutStopSec. Takes one of terminate, abort and kill. Both options default to terminate. so like, if your service is being a flop about starting or stopping, this decides the vibe of the response ๐Ÿ‘‘

  • terminate โ€” the service will be gracefully terminated by sending the signal specified in KillSignal (defaults to SIGTERM, see systemd.kill(5)). If the service does not terminate the FinalKillSignal is sent after TimeoutStopSec.
  • abort โ€” WatchdogSignal is sent instead and TimeoutAbortSec applies before sending FinalKillSignal. This setting may be used to analyze services that fail to start-up or shut-down intermittently.
  • kill โ€” the service is immediately terminated by sending FinalKillSignal without any further timeout. This setting can be used to expedite the shutdown of failing services.

Optional. Type enum. choice: 'abort', 'kill', 'terminate'.


โณ RuntimeMaxSec

Configures a maximum time for the service to run. and if you actually use it and the service has been active for longer than the time you set? boom โ€” it is terminated and put into a failure state. no cap. ๐Ÿ˜ญ

heads up bestie: this setting does not have any effect on Type=oneshot services, because they terminate immediately after activation completed (use TimeoutStartSec to limit their activation instead). different tool for that job fr.

Pass infinity (the default) to configure no runtime limit. unlimited runtime, unlimited vibes ๐Ÿฆ‹

And here's the fun loophole: if a service of Type=notify/Type=notify-reload sends EXTEND_TIMEOUT_USEC=โ€ฆ, this may cause the runtime to be extended beyond RuntimeMaxSec. The first receipt of this message must occur before RuntimeMaxSec is exceeded, and once the runtime has extended beyond RuntimeMaxSec, the service manager will allow the service to continue to run, provided the service repeats EXTEND_TIMEOUT_USEC=โ€ฆ within the interval specified until the service shutdown is achieved by STOPPING=1 (or termination). (see sd_notify(3)). likeโ€ฆ it has to keep asking nicely, every time, until it's done ๐Ÿ’€

Optional. Type uniline.


๐ŸŽฒ RuntimeRandomizedExtraSec

This option modifies RuntimeMaxSec by increasing the maximum runtime by an evenly distributed duration between 0 and the specified value (in seconds). If RuntimeMaxSec is unspecified, then this feature will be disabled. so no runtime cap = this lil extra does nothing at all, she's off duty ๐ŸŽ€

Optional. Type uniline.


๐Ÿ• WatchdogSec

Configures the watchdog timeout for a service. The watchdog is activated when the start-up is completed. From there, the service must call sd_notify(3) regularly with WATCHDOG=1 (i.e. the "keep-alive ping"). it's literally a u up? text for daemons ๐Ÿซถ

If the time between two such calls is larger than the configured time, then the service is placed in a failed state and it will be terminated with SIGABRT (or the signal specified by WatchdogSignal). By setting Restart to on-failure, on-watchdog, on-abnormal or always, the service will be automatically restarted. glow-up via auto-restart โœจ

The time configured here will be passed to the executed service process in the WATCHDOG_USEC environment variable. This allows daemons to automatically enable the keep-alive pinging logic if watchdog support is enabled for the service. If this option is used, NotifyAccess (see below) should be set to open access to the notification socket provided by systemd. If NotifyAccess is not set, it will be implicitly set to main. Defaults to 0, which disables this feature. The service can check whether the service manager expects watchdog keep-alive notifications. See sd_watchdog_enabled(3) for details. sd_event_set_watchdog(3) may be used to enable automatic watchdog notification support. iykyk ๐Ÿ‘‘

Optional. Type uniline.


๐Ÿ” Restart

Configures whether the service shall be restarted when the service process exits, is killed, or a timeout is reached. The service process may be the main service process, but it may also be one of the processes specified with ExecStartPre, ExecStartPost, ExecStop, ExecStopPost, or ExecReload. And important: when the death of the process is a result of systemd operation (e.g. service stop or restart), the service will not be restarted. Timeouts include missing the watchdog "keep-alive ping" deadline and a service start, reload, and stop operation timeouts.

Takes one of no, on-success, on-failure, on-abnormal, on-watchdog, on-abort, or always. Here's the whole menu, bestie:

  • no (the default) โ€” the service will not be restarted. it's giving rest? never heard of her.
  • on-success โ€” it will be restarted only when the service process exits cleanly. In this context, a clean exit means any of the following: exit code of 0; for types other than Type=oneshot, one of the signals SIGHUP, SIGINT, SIGTERM, or SIGPIPE; exit statuses and signals specified in SuccessExitStatus.
  • on-failure โ€” the service will be restarted when the process exits with a non-zero exit code, is terminated by a signal (including on core dump, but excluding the aforementioned four signals), when an operation (such as service reload) times out, and when the configured watchdog timeout is triggered.
  • on-abnormal โ€” the service will be restarted when the process is terminated by a signal (including on core dump, excluding the aforementioned four signals), when an operation times out, or when the watchdog timeout is triggered.
  • on-abort โ€” the service will be restarted only if the service process exits due to an uncaught signal not specified as a clean exit status.
  • on-watchdog โ€” the service will be restarted only if the watchdog timeout for the service expires.
  • always โ€” the service will be restarted regardless of whether it exited cleanly or not, got terminated abnormally by a signal, or hit a timeout. main character energy, zero chill ๐Ÿ•บ
ngl, note that Type=oneshot services will never be restarted on a clean exit status, i.e. always and on-success are rejected for them. they're built diff, don't fight it ๐Ÿ’€

As exceptions to the setting above, the service will not be restarted if the exit code or signal is specified in RestartPreventExitStatus (see below) or the service is stopped with systemctl stop or an equivalent operation. Also, the services will always be restarted if the exit code or signal is specified in RestartForceExitStatus (see below).

Note that service restart is subject to unit start rate limiting configured with StartLimitIntervalSec and StartLimitBurst, see systemd.unit(5) for details. so go too hard too fast and the manager will simply stop picking up the phone ๐Ÿ™ˆ

Setting this to on-failure is the recommended choice for long-running services, in order to increase reliability by attempting automatic recovery from errors. For services that shall be able to terminate on their own choice (and avoid immediate restarting), on-abnormal is an alternative choice. that's the bestie-approved rec, periodt ๐Ÿ’–

Optional. Type enum. choice: 'always', 'no', 'on-abnormal', 'on-abort', 'on-failure', 'on-success', 'on-watchdog'.


๐ŸŒ€ RestartMode

Takes a string value that specifies how a service should restart:

  • normal (the default) โ€” the service restarts by going through a failed/inactive state. the classic route, she walks the whole path ๐Ÿšถโ€โ™€๏ธ
  • direct โ€” the service transitions to the activating state directly during auto-restart, skipping failed/inactive state. ExecStopPost is still invoked. OnSuccess and OnFailure are skipped. This option is useful in cases where a dependency can fail temporarily but we do not want these temporary failures to make the dependent units fail. Dependent units are not notified of these temporary failures. sneaky little shortcut, no drama carried downstream ๐ŸŒˆ
  • debug โ€” the service manager will log messages that are related to this unit at debug level while automated restarts are attempted, until either the service hits the rate limit or it succeeds, and the $DEBUG_INVOCATION=1 environment variable will be set for the unit. This is useful to be able to get additional information when a service fails to start, without needing to proactively or permanently enable debug level logging in systemd, which is very verbose. This is otherwise equivalent to normal mode. like turning on the subtitles for one scene instead of for the entire movie ๐Ÿ“

Optional. Type uniline.


โœ… SuccessExitStatus

Takes a list of exit status definitions that, when returned by the main service process, will be considered successful termination, in addition to the normal successful exit status 0 and, except for Type=oneshot, the signals SIGHUP, SIGINT, SIGTERM, and SIGPIPE. Exit status definitions can be numeric termination statuses, termination status names, or termination signal names, separated by spaces. See the Process Exit Codes section in systemd.exec(5) for a list of termination status names (for this setting only the part without the EXIT_ or EX_ prefix should be used). See signal(7) for a list of signal names. so you're adding to the success list, not replacing it, okurrr ๐Ÿ’…

bestie do NOT get confused: this setting does not change the mapping between numeric exit statuses and their names, i.e. regardless how this setting is used 0 will still be mapped to SUCCESS (and thus typically shown as 0/SUCCESS in tool outputs) and 1 to FAILURE (and thus typically shown as 1/FAILURE), and so on. It only controls what happens as effect of these exit statuses, and how it propagates to the state of the service as a whole.

This option may appear more than once, in which case the list of successful exit statuses is merged. If the empty string is assigned to this option, the list is reset, all prior assignments of this option will have no effect. full clean slate behaviour ๐Ÿงผ

Note: systemd-analyze exit-status may be used to list exit statuses and translate between numerical status values and names. the decoder ring fr ๐Ÿ”ฅ

Optional. Type uniline.

๐Ÿ’… RestartPreventExitStatus

Bestie, this one takes a list of exit status definitions that, when they come back from the main service process, will block automatic service restarts โ€” no matter what restart setting you configured with Restart. Rude little veto, honestly. ๐Ÿ›‘

Each exit status definition can be a numeric termination status, a termination status name, or a termination signal name, all separated by spaces. Defaults to the empty list, so by default nothing gets excluded from the configured restart logic.

So a service with the RestartPreventExitStatus setting like:

RestartPreventExitStatus=TEMPFAIL 250 SIGKILL Exit status 75

โ€ฆmeans "TEMPFAIL", 250, and the termination signal "SIGKILL" will not result in automatic service restarting. โœจ

This option may appear more than once, in which case the list of restart-preventing statuses is merged. And if you assign the empty string to this option, the list is reset and all prior assignments of this option have no effect.

Heads up: this setting has no effect on processes configured via "ExecStartPre", "ExecStartPost", "ExecStop", "ExecStopPost" or "ExecReload" โ€” it only touches the main service process, i.e. either the one invoked by "ExecStart" or (depending on "Type", "PIDFile", โ€ฆ) the otherwise configured main process. Like, main character only, side characters stay unbothered. ๐Ÿ’…

Optional. Type uniline.


๐Ÿ”ฅ RestartForceExitStatus

Okay so this is the opposite energy: it takes a list of exit status definitions that, when returned by the main service process, will force automatic service restarts, regardless of the restart setting configured with "Restart". The argument format is similar to "RestartPreventExitStatus".

But wait โ€” for "Type=oneshot" services, a success exit status will prevent them from auto-restarting, no matter whether the corresponding exit statuses are listed in this option or not. That's just how oneshot rolls, fr fr.

Optional. Type uniline.


๐Ÿ  RootDirectoryStartOnly

Takes a boolean argument. If true, the root directory, as configured with the "RootDirectory" option (see systemd.exec(5) for more information), is only applied to the process started with "ExecStart", and not to the various other "ExecStartPre", "ExecStartPost", "ExecReload", "ExecReloadPost", "ExecStop", and "ExecStopPost" commands. If false, the setting is applied to all configured commands the same way.

Defaults to false. Optional. Type boolean.

  • upstream_default value:
    • no

๐Ÿš€ NonBlocking

Set the "O_NONBLOCK" flag for all file descriptors passed via socket-based activation. If true, all file descriptors >= 3 (i.e. all except stdin, stdout, stderr), excluding those passed in via the file descriptor storage logic (see "FileDescriptorStoreMax" for details), will have the "O_NONBLOCK" flag set and hence are in non-blocking mode. ๐Ÿƒโ™€๏ธ๐Ÿ’จ

This option is only useful in conjunction with a socket unit, as described in systemd.socket(5), and has no effect on file descriptors which were previously saved in the file-descriptor store for example. Defaults to false.

โš ๏ธ Okay this part matters: if the same socket unit is configured to be passed to multiple service units (via the "Sockets" setting, see below), and these services have different "NonBlocking" configurations, the precise state of "O_NONBLOCK" depends on the order in which these services are invoked, and will possibly change after service code already took possession of the socket file descriptor โ€” simply because the "O_NONBLOCK" state of a socket is shared by all file descriptors referencing it. Hence it is essential that all services sharing the same socket use the same "NonBlocking" configuration, and do not change the flag in service code either. Like, coordinate, besties. It's giving shared custody of a socket. ๐Ÿ˜ญ

Optional. Type uniline.


๐Ÿ“ฃ NotifyAccess

Controls access to the service status notification socket, as accessible via the sd_notify(3) call. Takes one of "none" (the default), "main", "exec" or "all".

  • If "none", no daemon status updates are accepted from the service processes, all status update messages are ignored. ๐Ÿ™ˆ
  • If "main", only service updates sent from the main process of the service are accepted.
  • If "exec", only service updates sent from any of the main or control processes originating from one of the "Exec*=" commands are accepted.
  • If "all", all services updates from all members of the service's control group are accepted. ๐ŸŒˆ

This option should be set to open access to the notification socket when using "Type=notify"/"Type=notify-reload" or "WatchdogSec" (see above). If those options are used but "NotifyAccess" is not configured, it will be implicitly set to "main".

Note that sd_notify() notifications may be attributed to units correctly only if either the sending process is still around at the time PID 1 processes the message, or if the sending process is explicitly runtime-tracked by the service manager. The latter is the case if the service manager originally forked off the process, i.e. on all processes that match "main" or "exec". Conversely, if an auxiliary process of the unit sends an sd_notify() message and immediately exits, the service manager might not be able to properly attribute the message to the unit, and thus will ignore it, even if "NotifyAccess"="all" is set for it. Byeeeee message, nobody saw it. ๐Ÿ’€

Hence, to eliminate all race conditions involving lookup of the client's unit and attribution of notifications to units correctly, sd_notify_barrier() may be used. This call acts as a synchronization point and ensures all notifications sent before this call have been picked up by the service manager when it returns successfully. Use of sd_notify_barrier() is needed for clients which are not invoked by the service manager, otherwise this synchronization mechanism is unnecessary for attribution of notifications to the unit.

Optional. Type enum. choice: 'all', 'exec', 'main', 'none'.


๐Ÿ”Œ Sockets

Specifies the name of the socket units this service shall inherit socket file descriptors from when the service is started. Normally, it should not be necessary to use this setting, as all socket file descriptors whose unit shares the same name as the service (subject to the different unit name suffix of course) are passed to the spawned process. So like, it mostly just happens for you. Slay. โœจ

Note that the same socket file descriptors may be passed to multiple processes simultaneously. Also note that a different service may be activated on incoming socket traffic than the one which is ultimately configured to inherit the socket file descriptors. Or, in other words: the "Service" setting of .socket units does not have to match the inverse of the "Sockets" setting of the .service it refers to. The vibes are chaotic and that's fine. ๐Ÿฆ‹

This option may appear more than once, in which case the list of socket units is merged. Note that once set, clearing the list of sockets again (for example, by assigning the empty string to this option) is not supported. No take-backsies. ๐Ÿ™…โ™€๏ธ

Optional. Type uniline.


๐Ÿ“ฆ FileDescriptorStoreMax

Configure how many file descriptors may be stored in the service manager for the service using sd_pid_notify_with_fds(3)'s "FDSTORE=1" messages. This is useful for implementing services that can restart after an explicit request or a crash without losing state. Any open sockets and other file descriptors which should not be closed during the restart may be stored this way. Application state can either be serialized to a file in "RuntimeDirectory", or stored in a memfd_create(2) memory file descriptor. Gotta keep the state, bestie, losing it is not the move. ๐ŸŽ€

Defaults to 0, i.e. no file descriptors may be stored in the service manager. All file descriptors passed to the service manager from a specific service are passed back to the service's main process on the next service restart (see sd_listen_fds(3) for details about the precise protocol used and the order in which the file descriptors are passed). Any file descriptors passed to the service manager are automatically closed when "POLLHUP" or "POLLERR" is seen on them, or when the service is fully stopped and no job is queued or being executed for it (the latter can be tweaked with "FileDescriptorStorePreserve", see below).

If this option is used, "NotifyAccess" (see above) should be set to open access to the notification socket provided by systemd. If "NotifyAccess" is not set, it will be implicitly set to "main".

The fdstore command of systemd-analyze(1) may be used to list the current contents of a service's file descriptor store. ๐Ÿ‘€

Note that the service manager will only pass file descriptors contained in the file descriptor store to the service's own processes, never to other clients via IPC or similar. However, it does allow unprivileged clients to query the list of currently open file descriptors of a service. Sensitive data may hence be safely placed inside the referenced files, but should not be attached to the metadata (e.g. included in filenames) of the stored file descriptors. Secrets go in the file, not in the name. Fr fr. ๐Ÿซถ

If this option is set to a non-zero value the $FDSTORE environment variable will be set for processes invoked for this service. See systemd.exec(5) for details.

For further information on the file descriptor store see the File Descriptor Store <https://systemd.io/FILE_DESCRIPTOR_STORE> overview.

Optional. Type uniline.


๐Ÿ—„๏ธ FileDescriptorStorePreserve

Takes one of "no", "yes", "restart" and controls when to release the service's file descriptor store (i.e. when to close the contained file descriptors, if any).

  • If set to "no" the file descriptor store is automatically released when the service is stopped.
  • If "restart" (the default) it is kept around as long as the unit is neither inactive nor failed, or a job is queued for the service, or the service is expected to be restarted.
  • If "yes" the file descriptor store is kept around until the unit is removed from memory (i.e. is not referenced anymore and inactive). The latter is useful to keep entries in the file descriptor store pinned until the service manager exits. Pinning era. ๐Ÿ“Œ

Use systemctl clean --what=fdstore โ€ฆ to release the file descriptor store explicitly.

Optional. Type enum. choice: 'no', 'restart', 'yes'.


๐Ÿ”— USBFunctionDescriptors

Configure the location of a file containing USB FunctionFS <https://docs.kernel.org/usb/functionfs.html> descriptors, for implementation of USB gadget functions. This is used only in conjunction with a socket unit with "ListenUSBFunction" configured. The contents of this file are written to the "ep0" file after it is opened.

Optional. Type uniline.


๐Ÿงต USBFunctionStrings

Configure the location of a file containing USB FunctionFS strings. Behavior is similar to "USBFunctionDescriptors" above.

Optional. Type uniline.


๐Ÿ’€ OOMPolicy

Configure the out-of-memory (OOM) killing policy for the kernel and the userspace OOM killer systemd-oomd.service(8). ๐Ÿ’ฅ

On Linux, when memory becomes scarce to the point that the kernel has trouble allocating memory for itself, it might decide to kill a running process in order to free up memory and reduce memory pressure.

Note that "systemd-oomd.service" is a more flexible solution that aims to prevent out-of-memory situations for the userspace too, not just the kernel, by attempting to terminate services earlier, before the kernel would have to act. Proactive queen behavior, honestly. ๐Ÿ‘‘

This setting takes one of continue, stop or kill. Let's break it down bestie ๐Ÿ’…

  • if it's continue and a process in the unit gets clapped by the OOM killer, that's logged, but the unit just keeps on vibing and running.
  • if it's stop, the event gets logged and the unit's processes are terminated cleanly by the service manager.
  • if it's kill and one of the unit's processes gets OOM-killed, the kernel is instructed to kill all remaining processes of the unit too, by setting the memory.oom.group attribute to 1; also see the kernel page Control Group v2 at https://docs.kernel.org/admin-guide/cgroup-v2.html.

In the case of both stop and kill, the service ultimately ends up in the oom-kill failed state, after which Restart may apply. ๐Ÿ˜ญ

Defaults to whatever the setting DefaultOOMPolicy in systemd-system.conf(5) is set to, except for units where Delegate is turned on, where it defaults to continue.

Use the OOMScoreAdjust setting to configure whether processes of the unit shall be considered preferred or less preferred candidates for process termination by the Linux OOM killer logic. See systemd.exec(5) for details. โœจ

This setting also applies to systemd-oomd.service(8). Similarly to the kernel OOM kills performed by the kernel, this setting determines the state of the unit after systemd-oomd kills a cgroup associated with it. Optional. Type uniline.

๐Ÿฆ‹ OpenFile ๐Ÿฆ‹

Takes an argument of the form path:fd-name:options, where:

  • path is a path to a file or an AF_UNIX socket in the file system;
  • fd-name is a name that will be associated with the file descriptor; the name may contain any ASCII character, but must exclude control characters and :, and must be at most 255 characters in length; it is optional and, if not provided, defaults to the file name;
  • options is a comma-separated list of access options; possible values are read-only, append, truncate, graceful; if not specified, files will be opened in rw mode; if graceful is specified, errors during file/socket opening are ignored.
Specifying the same option several times is treated as an error. no cap, it will not let that slide ๐Ÿ’€

The file or socket is opened by the service manager and the file descriptor is passed to the service. If the path is a socket, we call connect() on it. See sd_listen_fds(3) for more details on how to retrieve these file descriptors. ๐Ÿซถ

This setting is useful to allow services to access files/sockets that they cannot access themselves (due to running in a separate mount namespace, not having privileges, ...).

This setting can be specified multiple times, in which case all the specified paths are opened and the file descriptors passed to the service. If the empty string is assigned, the entire list of open files defined prior to this is reset. Optional. Type uniline. โญ

๐ŸŒธ ReloadSignal ๐ŸŒธ

Configures the UNIX process signal to send to the service's main process when asked to reload the service's configuration. Defaults to SIGHUP.

This option has no effect unless Type=notify-reload is used, see above. ๐Ÿ‘€

Optional. Type uniline.

๐Ÿ’œ RefreshOnReload ๐Ÿ’œ

Takes a boolean argument, or a list of resources defined in systemd.exec(5). Possible values are extensions and credentials, separated by space. Prepending the list with a single tilde character (~) inverts the effect. Defaults to extensions. An empty assignment resets the list to default.

If enabled, the corresponding resources (ExtensionImages/ExtensionDirectories for extensions and LoadCredential/ImportCredential/SetCredential (along with their Encrypted counterparts) for credentials) will be refreshed on service reload. If yes, all resources listed above that are used by the service shall be refreshed. ๐Ÿ”ฅ

Specially, if this option is set explicitly, and the respective resources are in use, the service may be reloaded without any actual reload mechanism (ExecReload or Type=notify-reload) for notifying the main process, in which case the reload is considered complete immediately after refreshing. Optional. Type uniline.

๐Ÿ˜ญ FailureAction ๐Ÿ˜ญ

Deprecated Optional. Type uniline.

๐Ÿ˜ญ SuccessAction ๐Ÿ˜ญ

Deprecated Optional. Type uniline.

๐Ÿ˜ญ StartLimitBurst ๐Ÿ˜ญ

Deprecated Optional. Type uniline.

๐Ÿ˜ญ StartLimitInterval ๐Ÿ˜ญ

Deprecated Optional. Type uniline.

๐Ÿ˜ญ RebootArgument ๐Ÿ˜ญ

Deprecated Optional. Type uniline.


๐Ÿ‘‘ SEE ALSO ๐Ÿ‘‘

  • cme

  • 2010-2016 Lennart Poettering and others
  • 2016 Dominique Dumont

๐ŸŒˆ LICENSE ๐ŸŒˆ

  • LGPLv2.1+

And that's a wrap on the whole page, bestie!! ๐ŸŽ‰ You made it to the end, main character energy fr fr โ€” this was rendered by perl v5.40.1 and dated 2026-03-22, and it's the Config...ervice(3pm) manual page. Go forth and config your services, you absolute icon ๐Ÿ’–โœจ๐Ÿฆ‹

๐Ÿ“ฆ where this page comes from