$linuxjunkies
>

How to Create a systemd Service

Learn to write systemd service unit files from scratch: unit types, dependency directives, restart policies, enabling at boot, and reading logs with journalctl.

IntermediateUbuntuDebianFedoraArch9 min readUpdated June 7, 2026

Before you start

  • sudo or root access on the target system
  • Basic familiarity with a terminal text editor such as nano or vim
  • A script or binary you want to run as a service
  • systemd version 232 or later (check with systemctl --version)

systemd is the init system and service manager on virtually every major Linux distribution today. Writing your own service unit file lets you run scripts, daemons, or any executable as a managed service—complete with automatic restarts, dependency ordering, logging via journald, and clean start/stop semantics. This guide walks through creating a unit file from scratch, understanding the most important options, and integrating your service into the boot process correctly.

Understanding Unit File Types

systemd manages units, and each unit type has its own file extension. For running processes, you almost always want a .service unit. Other relevant types include:

  • .service – a daemon or one-shot process (the focus of this guide)
  • .timer – triggers a service on a schedule (systemd's cron replacement)
  • .socket – socket-activated services; systemd listens on the socket and starts the service on demand
  • .target – a grouping mechanism, analogous to runlevels

Unit files live in three locations. Know which to use:

PathPurpose
/lib/systemd/system/Shipped by packages; don't edit directly
/etc/systemd/system/System-wide custom or override units — use this
~/.config/systemd/user/Per-user units (run with systemctl --user)

Anatomy of a Service Unit File

Every .service file has three sections: [Unit], [Service], and [Install]. Here is a complete, annotated example for a fictional Python web application:

sudo nano /etc/systemd/system/myapp.service

Paste in the following, then adjust paths and values for your application:

[Unit]
Description=My Python Web Application
Documentation=https://example.com/docs
After=network.target postgresql.service
Requires=postgresql.service

[Service]
Type=simple
User=myapp
Group=myapp
WorkingDirectory=/opt/myapp
EnvironmentFile=/etc/myapp/env
ExecStart=/opt/myapp/venv/bin/python app.py
ExecReload=/bin/kill -HUP $MAINPID
Restart=on-failure
RestartSec=5s
StandardOutput=journal
StandardError=journal
SyslogIdentifier=myapp
PrivateTmp=true
NoNewPrivileges=true

[Install]
WantedBy=multi-user.target

Key Directives Explained

[Unit] Section

  • After – guarantees ordering; this unit starts after the listed units. It does not imply a dependencyjust sequencing.
  • Requires – hard dependency. If postgresql.service fails, this service also fails. Use Wants instead for a soft dependency where failure of the dependency is tolerated.
  • BindsTo – stricter than Requires; if the dependency stops, this unit is also stopped immediately.

[Service] Section — Type

The Type= directive tells systemd how to track your process. Choose the right one:

  • simple (default) – the ExecStart process is the main process. Use this for most long-running daemons that don't fork.
  • forking – the process forks and the parent exits. Traditional daemons work this way. Pair with PIDFile=.
  • exec – like simple, but systemd waits until the binary is actually exec'd before considering the service started. More accurate than simple for scripts.
  • oneshot – the process runs to completion and exits. Useful for setup scripts. Often paired with RemainAfterExit=yes.
  • notify – the daemon sends a sd_notify() message when it is fully ready. The most reliable option if your application supports it.
  • dbus – the service is considered ready when it acquires a D-Bus name.

[Service] Section — Restart Policy

  • Restart=no – never restart (default)
  • Restart=on-failure – restart only on non-zero exit code or signal
  • Restart=always – restart unconditionally, even on clean exit
  • Restart=on-abnormal – restart on signals, watchdog timeout, or core dump
  • RestartSec= – delay between restart attempts

[Install] Section

WantedBy=multi-user.target is correct for any service that should run in normal multi-user (non-graphical or graphical) mode. Use graphical.target only if the service genuinely requires a running display server.

Creating a Minimal One-Shot Service

Not every service is a long-running daemon. Here is a complete example for a startup script:

sudo nano /etc/systemd/system/initial-setup.service
[Unit]
Description=Run initial server setup script
After=network-online.target
Wants=network-online.target
ConditionPathExists=!/var/lib/initial-setup.done

[Service]
Type=oneshot
ExecStart=/usr/local/bin/initial-setup.sh
ExecStartPost=/usr/bin/touch /var/lib/initial-setup.done
RemainAfterExit=yes

[Install]
WantedBy=multi-user.target

ConditionPathExists=! prefixed with ! means: only run if the path does not exist. This is a clean way to make a one-shot service idempotent.

Enabling and Starting the Service

After writing the unit file, reload the systemd daemon so it picks up the new file:

sudo systemctl daemon-reload

Enable the service to start at boot, then start it immediately:

sudo systemctl enable --now myapp.service

--now combines enable and start in one command. To disable and stop in one step, use disable --now.

Verifying the Service

Check the current runtime status:

systemctl status myapp.service

You'll see the active state (active/running, failed, activating), the main PID, memory use, and the last few log lines. A healthy service shows Active: active (running).

View the full logs for the service since the last boot:

journalctl -u myapp.service -b

Follow logs in real time (like tail -f):

journalctl -u myapp.service -f

Check whether the service is enabled at boot:

systemctl is-enabled myapp.service

Overriding Package-Shipped Unit Files

Never edit files under /lib/systemd/system/ directly—package updates will overwrite your changes. Instead, create a drop-in override:

sudo systemctl edit nginx.service

This opens a temporary file at /etc/systemd/system/nginx.service.d/override.conf. Only include the sections and directives you want to change. For example, to add an environment variable:

[Service]
Environment="EXTRA_OPTS=-v"

Save and close; systemd reloads the configuration automatically when using systemctl edit. To see the merged final unit, run:

systemctl cat nginx.service

Troubleshooting

Service fails to start

The status output and journal are your first stops. Look for the exact error message:

journalctl -u myapp.service -b --no-pager | tail -40

Common causes: wrong path in ExecStart, the specified User doesn't exist, missing EnvironmentFile, or the binary is not executable.

Unit file syntax errors

Validate your unit file before reloading:

systemd-analyze verify /etc/systemd/system/myapp.service

This catches syntax problems, unknown directives, and missing dependencies without actually starting anything.

Service starts but immediately exits

If Type=simple and the process exits right away, check whether your binary daemonizes itself (forks and exits). If it does, switch to Type=forking and supply a PIDFile=. If it doesn't daemonize, the issue is in the application itself—check its own logs and exit code.

Changes not taking effect

After modifying a unit file manually (not via systemctl edit), always run daemon-reload before restarting the service. Forgetting this is the single most common mistake when working with systemd unit files.

sudo systemctl daemon-reload && sudo systemctl restart myapp.service
tested on:Ubuntu 24.04Debian 12Fedora 41Arch rolling

Frequently asked questions

What is the difference between Wants= and Requires= in a unit file?
Requires= is a hard dependency—if the listed unit fails to start, your service also fails. Wants= is a soft dependency—systemd will try to start the listed unit but your service continues even if it fails.
Do I need to run daemon-reload every time I edit a unit file?
Yes, any manual edit to a unit file under /etc/systemd/system/ requires sudo systemctl daemon-reload before systemd will see the changes. The exception is systemctl edit, which triggers a reload automatically.
How do I run a service as a specific user instead of root?
Set User= and Group= in the [Service] section to the desired account. Make sure that account exists on the system and has read access to all files the service needs.
What is the correct Type= for a daemon that forks into the background?
Use Type=forking and supply a PIDFile= pointing to where the daemon writes its PID. Without PIDFile=, systemd cannot reliably track the main process after the parent exits.
Can I create a systemd service without root privileges?
Yes. Place the unit file in ~/.config/systemd/user/ and use systemctl --user enable --now myapp.service. User services start when you log in and stop when your last session ends, unless you also enable lingering with loginctl enable-linger.

Related guides