Systemd is the init system on most modern Linux distributions. A service file (also called a unit file) tells systemd how to start, stop, and manage a process. Creating one is the correct way to run a custom script or application as a managed service with automatic restart, boot integration, and proper logging.
Service files use the .service extension and are plain text files in ini format. Place custom files in /etc/systemd/system/. Files there take priority over package-installed units in /usr/lib/systemd/system/ and survive package upgrades. Never edit files in /usr/lib/systemd/system/ directly — copy them to /etc/systemd/system/ to override.
Create the file:
bashsudo nano /etc/systemd/system/myapp.service
A minimal working service file:
ini[Unit]Description=My Custom ApplicationAfter=network.target[Service]Type=simpleExecStart=/usr/local/bin/myapp.shRestart=on-failureRestartSec=5[Install]WantedBy=multi-user.target
After saving, reload systemd and enable the service:
bashsudo systemctl daemon-reloadsudo systemctl enable --now myapp
Run daemon-reload after every edit to the unit file. Systemd reads the file at reload time, not at start time. Skipping this step is the most common reason why changes have no effect.
Check status and logs:
bashsudo systemctl status myappsudo journalctl -u myapp # full log historysudo journalctl -fu myapp # live log stream
[Unit] contains metadata and dependency declarations:
Description= — the name shown in systemctl status outputAfter= — start this service after the listed units are active; this is ordering only, not a dependencyRequires= — hard dependency; if the listed unit fails, this service also failsWants= — soft dependency; service still starts even if the listed unit failsAfter=network.target means “wait for networking to be ready” but does not make networking a hard requirement. For a hard requirement, combine After= with Requires=.
[Service] defines the process:
ExecStart= — the command to run; must be an absolute path. Use which binary to find the full path if unsureType= — see the table belowRestart= and RestartSec= — restart behavior and delayUser= and Group= — run as a specific user instead of rootEnvironment= — set environment variables: Environment="NODE_ENV=production"[Install] controls enablement:
WantedBy=multi-user.target — the standard value for services that start on normal multi-user bootType= tells systemd how the process behaves at startup:
| Type | When to use |
|---|---|
simple | ExecStart is the main process; right choice for most custom scripts |
forking | Process forks and the parent exits; use for traditional background daemons |
oneshot | Runs once and exits; systemd waits for completion before marking the service active |
notify | Like simple, but the process signals readiness to systemd via sd_notify() |
Restart= controls when systemd automatically restarts the service:
on-failure — restart on non-zero exit code, signal, or timeout; most common choicealways — restart after any exit, including a clean exit code 0no — never restart (default)The default RestartSec= is 100ms. Set it to a higher value like RestartSec=5 to avoid rapid restart loops.
Running as a non-root user. Running services as root is a security risk. Use User= and Group= in [Service] and create a dedicated system account first:
bashsudo useradd -r -s /usr/sbin/nologin myappuser
Add to the service file:
iniUser=myappuserGroup=myappuser
If the service is compromised, the process cannot escalate to root privileges.
Place the file in /etc/systemd/system/, run daemon-reload after every change, and check logs with journalctl -u. Use Type=simple and Restart=on-failure for most custom applications, and always run services under a dedicated non-root user. Leave a comment below if you run into any issues.