systemd Type= 정리|simple·exec·notify와 서비스 파일 핵심 키워드

Answer First: The systemd Type= directive in a .service file determines when the service manager considers service start-up to be complete. The current official systemd.service(5) man page defines eight values: simple, exec, forking, oneshot, dbus, notify, notify-reload, and idle. This article covers each type’s semantics, when to choose it, and the must-know service file keywords, all grounded in the freedesktop official man pages.

This article is a general overview based on systemd’s official documentation (systemd.service(5) and related pages). Option names, defaults, and exact behavior can vary depending on the systemd version packaged by your distribution. Always check man systemd.service on the target system before writing or applying unit files.

What does [Service] Type= mean in systemd?

Short answer: Type= tells the systemd service manager how to detect that a service has finished starting up.

When systemd launches a service, it needs a way to decide when that service is “ready.” Should it consider the service ready immediately after the process is forked? After the binary has been successfully executed? Or only after the daemon signals its own readiness? That decision is what Type= encodes.

The choice of Type= has three direct consequences.

  • When dependent units start: A unit declared with After=my.service will not start until systemd has judged my.service to be active. An incorrect Type= can cause downstream units to start before the service has fully initialized.
  • When systemctl start returns: Type=simple returns success immediately after fork(), so a missing binary or a non-existent User= account may not surface as an error at start time.
  • When errors surface: Type=exec waits for execve() to succeed before declaring start-up complete, so a missing binary or bad User= is caught immediately during systemctl start.

Leaving Type= at its default (simple) without deliberate intent is a common source of hard-to-diagnose failures. Choosing the right type is the first decision to make when writing a service unit.

What are the Type= values and when should each be used?

Short answer: There are eight Type= values in the current systemd.service(5) man page. Each one defines a different start-up completion signal.

simple

Start-up is considered complete immediately after the main process is forked, before execve(). This is the default when ExecStart= is set but neither Type= nor BusName= is specified (and credentials are not used). Because execve() is not awaited, a missing binary or incorrect User= will not be caught at start time. For long-running services, prefer exec.

exec

Start-up is considered complete after the main process has successfully called execve() — both fork() and exec must succeed. This type is used automatically when credentials (LoadCredential=, and so on) are configured. It surfaces binary-not-found and bad-User= errors at systemctl start time, making it the officially recommended type for long-running services.

forking

Start-up is considered complete when the parent process started by the manager exits after the traditional double-fork; the child process that remains is treated as the main process. This models the classic Unix daemonization pattern. The official man page discourages its use in new projects and recommends migrating to notify, notify-reload, or dbus. When forking is used, specifying PIDFile= is strongly recommended so the manager can track the correct process.

oneshot

Start-up is considered complete when the main process exits. This is also the default when neither Type= nor ExecStart= is set. Similar to exec, but the unit remains in the “up” state after process exit. Unlike all other types, oneshot allows multiple ExecStart= lines; they are executed sequentially. This type is not suitable for long-running daemons. Combined with RemainAfterExit=yes, it keeps the unit in an “active” state after all commands finish.

notify

Start-up is considered complete when the service sends READY=1 via sd_notify(3). Behavior is otherwise similar to exec. This is the right choice for daemons that need to complete IPC setup, open a listening socket, or connect to a database before they are ready to serve clients. The NotifyAccess= directive controls which processes may send notifications; if not configured, it defaults to main.

notify-reload

Like notify, but with an extended reload protocol. When a reload is requested, the manager sends SIGHUP (or the signal specified in ReloadSignal=), and the service is expected to send RELOADING=1 plus MONOTONIC_USEC=… followed by READY=1 when the reload is complete. This is the preferred type for daemons that reload via a signal, as it replaces the asynchronous ExecReload=kill -HUP pattern with a reliable, notification-based one.

dbus

Start-up is considered complete when the service acquires the bus name specified in BusName=. Behavior is otherwise similar to simple. This is the default type when BusName= is set. A dependency on dbus.socket is added implicitly. BusName= is mandatory with this type.

idle

Behaves like simple, but execution is delayed until all active jobs have been dispatched. The maximum delay is five seconds, after which the service runs regardless. The purpose is to avoid interleaving console output from this service with messages from other services during boot. Do not use this type for general unit ordering. Use After=/Before= for ordering instead.

Type= selection summary

TypeStart-up complete when…Recommended scenario
simpleAfter fork()Legacy or socket activation + simple combination
execAfter execve() succeedsLong-running services — preferred
forkingAfter parent process exits (double-fork)Traditional Unix daemons; avoid if possible
oneshotAfter main process exitsOne-shot init scripts and tasks
notifyAfter READY=1 receivedDaemons that signal readiness via sd_notify
notify-reloadAfter READY=1 received (with reload protocol)Daemons that support signal-based reload
dbusAfter BusName= acquired on D-BusD-Bus services
idleAfter other jobs dispatched (max 5 s)Avoiding console output interleaving only

How are ExecStart=, ExecStop=, and Restart= used?

Short answer: ExecStart= specifies the command to run, ExecStop= the command to stop it, and Restart= the policy for automatic restarts on failure.

ExecStart=

Specifies the command (with arguments) to execute when the service starts. An absolute path is required. Only one ExecStart= line is allowed for all types except oneshot, which permits multiple lines executed sequentially. Use ExecStartPre= and ExecStartPost= for commands that should run before or after the main command.

ExecStop=

Specifies the command to run when the service is stopped. If not set, systemd sends a signal to the process according to KillMode= and KillSignal=. Use ExecStopPost= for cleanup actions that must run after the service stops, whether or not ExecStop= was specified.

ExecReload=

Specifies the command to run when systemctl reload is invoked. When Type=notify-reload is used, the notification-based reload protocol replaces this directive.

Restart=

Controls whether and under what conditions the service is automatically restarted when it exits. The default is no. The most commonly used values for long-running services are:

  • no: Do not restart (default).
  • on-failure: Restart if the process exits with a non-zero code or is killed by a signal. This is the most common choice for long-running services.
  • always: Restart regardless of exit reason. Not compatible with Type=oneshot.
  • on-abnormal: Restart on signals, timeouts, or watchdog failures only.

The delay before each restart attempt is controlled by RestartSec= (default: 100 ms).

What are the essential keywords — WantedBy=, After=, User=, Environment=?

Short answer: Service file keywords fall into three groups: execution environment ([Service]/systemd.exec), ordering and dependencies ([Unit]), and activation wiring ([Install]).

Execution environment

The following directives, defined in systemd.exec(5), configure the environment in which the service process runs.

  • User= / Group=: Sets the user and group identity for the service process. Defaults to root if not set. For security, create a dedicated low-privilege user for each service.
  • WorkingDirectory=: Sets the working directory of the service process. Defaults to the home directory of root.
  • Environment=: Passes environment variables as KEY=VALUE pairs. Multiple variables can be separated by spaces on a single line.
  • EnvironmentFile=: Reads environment variables from a file. Useful for keeping secrets or distribution-specific paths outside the unit file.

Ordering and dependencies

The following directives are written in the [Unit] section and defined in systemd.unit(5). Ordering and dependency are distinct concepts.

  • After= / Before=: Controls start order only. Does not create a dependency. After=network.target means “start this unit after network.target has been activated,” but does not cause this unit to fail if network.target fails.
  • Requires=: Strong dependency. If any listed unit fails to activate or is stopped, this unit is stopped too.
  • Wants=: Weak dependency. Listed units are activated alongside this one if possible, but their failure does not prevent this unit from running. Generally preferred over Requires=.

Activation wiring — [Install] section

  • WantedBy=: Specifies which target’s .wants/ directory receives a symlink when systemctl enable is run. Most user-facing services use WantedBy=multi-user.target. This symlink is what causes the service to start automatically at boot.

Important: daemon-reload

After creating or modifying any unit file, always run systemctl daemon-reload. systemd does not automatically re-read unit files at runtime. Without this step, the previous in-memory configuration continues to be used.

# Always run after editing unit files
systemctl daemon-reload
systemctl enable --now my-service.service

Why are PIDFile= (for forking) and RemainAfterExit= (for oneshot) important?

Short answer: PIDFile= lets systemd track the correct main process when using Type=forking. RemainAfterExit= keeps a Type=oneshot unit in an “active” state after its commands finish.

PIDFile= and Type=forking

Traditional Unix daemons use a double-fork technique to detach themselves from the terminal and run in the background. After the double-fork, the parent process that systemd originally launched exits, and the actual daemon runs as a grandchild. Systemd then has no automatic way to know which PID is the real main process.

PIDFile= tells the manager where the daemon writes its PID file after daemonizing. Systemd reads the PID from that file and uses it to track, monitor, and signal the correct process. The path must be absolute and is typically under /run/.

The official man page recommends avoiding Type=forking + PIDFile= in new projects and migrating to notify or notify-reload instead. PID file approaches are susceptible to race conditions and are less reliable than notification-socket-based approaches.

[Service]
Type=forking
PIDFile=/run/my-legacy-daemon.pid
ExecStart=/usr/sbin/my-legacy-daemon --daemonize

RemainAfterExit= and Type=oneshot

A Type=oneshot service exits by design after its commands complete. Without RemainAfterExit=, the unit transitions from “activating” to “dead (inactive)” even if the commands succeeded. This means systemctl is-active reports the unit as inactive and units that declare After=this.service may not behave as expected.

Setting RemainAfterExit=yes keeps the unit in an “active (exited)” state after all ExecStart= commands finish successfully. This is particularly useful in two scenarios.

  • As a dependency anchor: Use the oneshot unit as a marker that a setup task has completed. Downstream units with After=setup.service wait for this unit to reach the “active” state before starting.
  • For systemctl is-active checks: Scripts or other units can query whether the initialization task has been run and succeeded.
[Unit]
Description=One-time system initialization

[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart=/usr/bin/setup-step1.sh
ExecStart=/usr/bin/setup-step2.sh

[Install]
WantedBy=multi-user.target

Once both scripts complete successfully, the unit enters “active (exited)” state, and any service that lists it with After= can safely proceed.

FAQ

Short answer: Answers to common questions about systemd service units, based on the official man pages.

QuestionAnswer
When is daemon-reload needed?Immediately after creating or modifying any unit file on disk. systemd does not watch unit files for changes at runtime. Running daemon-reload causes systemd to reload its unit-file configuration into memory. It does not restart any already-running service processes.
Why does systemctl start succeed with Type=simple even when the binary is missing?Because Type=simple considers start-up complete immediately after fork(). The failure of execve() happens inside the child process after the manager has already recorded success. To catch this at start time, use Type=exec, which waits for execve() to return successfully before declaring start-up complete.
What is the relationship between socket activation and Type=?Socket activation is a technique where a .socket unit listens for connections and only launches the service when a connection arrives. The service’s Type= still describes how the service itself signals start-up completion. Common combinations are socket activation with Type=simple or Type=notify. Socket activation with Type=forking is discouraged.
Is Type=idle a valid way to control unit start order?No. idle is designed only to prevent console output from this service from being interleaved with output from other services during boot. The delay is at most five seconds, after which the service runs unconditionally. For start-order control, use After=/Before= together with Requires=/Wants=.

References

Short answer: Facts in this article are drawn from systemd official documentation checked on 2026-09-14.


Disclaimer: This article is a general overview based on systemd’s official documentation (systemd.service(5) and related pages). Option names, default values, and exact type behavior can vary depending on the systemd version packaged by your Linux distribution. Always check man systemd.service on the target system before writing or applying unit files.