A systemd service unit is a configuration file that tells the systemd init system how to start, stop, restart and manage a process on Ubuntu. If you have a script, application, or daemon that you want to run automatically at boot, restart on failure, or manage using standard systemctl commands, creating a custom systemd service is the correct approach.
This guide walks through the process of writing a service unit file, enabling it, and managing it with the standard systemd tooling.
Understanding Service Unit Files
Systemd reads service definitions from unit files stored in specific directories. The most important locations are:
/etc/systemd/system/— Custom service units created by the system administrator. This is where you place your custom services./lib/systemd/system/— Service units provided by installed packages. Do not edit files here directly, as package updates will overwrite your changes.
A service unit file has the extension .service and uses a simple INI-style format with three main sections: [Unit], [Service], and [Install].
Writing a Basic Service Unit File
Create a new service file using a text editor with root privileges:
sudo nano /etc/systemd/system/myapp.service
Here is a complete example for a service that runs a Python application:
[Unit]
Description=My Custom Application
After=network.target
[Service]
Type=simple
User=www-data
Group=www-data
WorkingDirectory=/opt/myapp
ExecStart=/usr/bin/python3 /opt/myapp/main.py
Restart=on-failure
RestartSec=5
StandardOutput=journal
StandardError=journal
[Install]
WantedBy=multi-user.target
Explaining Each Section
The [Unit] Section
This section describes the service and its dependencies:
- Description: A human-readable description of the service. This appears in
systemctl statusoutput and log entries. - After: Specifies that this service should start after the listed units have started.
network.targetensures the network is available before your service starts. Other common values includepostgresql.service,mysql.service, ordocker.serviceif your application depends on those services. - Requires: (optional) Lists units that must be running for this service to start. If a required unit fails, your service will not start.
- Wants: (optional) Like Requires, but weaker — your service will still start even if the wanted unit fails.
The [Service] Section
This section defines how the service runs:
- Type: Defines the startup behaviour.
simple(default) — systemd considers the service started as soon as theExecStartprocess is forked. Use this for long-running processes that do not fork.forking— the process forks a child and the parent exits. systemd considers the service started when the parent process exits. Use this for traditional daemons that fork.oneshot— the process runs once and exits. systemd waits for it to complete before marking the service as active. Use this for scripts that perform a task and exit.notify— like simple, but the process signals systemd when it is ready using thesd_notify()function.
- User / Group: The Unix user and group the process runs as. Never run application services as root unless absolutely necessary.
- WorkingDirectory: The directory the process uses as its current working directory.
- ExecStart: The full path to the command that starts the service. Always use absolute paths.
- ExecStop: (optional) The command to stop the service gracefully. If not specified, systemd sends SIGTERM followed by SIGKILL after a timeout.
- Restart: Defines when the service should automatically restart.
on-failure— restart only if the process exits with a non-zero exit code or is killed by a signal.always— always restart regardless of exit code.no— do not restart automatically.
- RestartSec: The number of seconds to wait before restarting the service after a failure.
- StandardOutput / StandardError: Where to direct stdout and stderr.
journalsends output to the systemd journal, viewable withjournalctl. - Environment: (optional) Set environment variables for the process, e.g.,
Environment=NODE_ENV=production PORT=3000. - EnvironmentFile: (optional) Load environment variables from a file, e.g.,
EnvironmentFile=/etc/myapp/env.
The [Install] Section
This section defines how the service integrates with the boot process:
- WantedBy: Specifies the target (analogous to a runlevel) that should include this service.
multi-user.targetmeans the service starts during a normal multi-user boot (the standard for servers and desktops). Usegraphical.targetif the service should only start when a graphical desktop environment is available.
Enabling and Starting the Service
After creating the service file, tell systemd to reload its configuration:
sudo systemctl daemon-reload
Enable the service to start automatically at boot:
sudo systemctl enable myapp.service
Start the service immediately:
sudo systemctl start myapp.service
Check the service status:
sudo systemctl status myapp.service
This displays whether the service is active, its PID, memory usage, and the most recent log entries.
Managing the Service
Once enabled, you can manage the service with standard systemctl commands:
| Command | Action |
|---|---|
sudo systemctl start myapp |
Start the service |
sudo systemctl stop myapp |
Stop the service |
sudo systemctl restart myapp |
Stop and start the service |
sudo systemctl reload myapp |
Reload configuration without stopping (if supported) |
sudo systemctl status myapp |
Show current status and recent logs |
sudo systemctl enable myapp |
Enable auto-start at boot |
sudo systemctl disable myapp |
Disable auto-start at boot |
journalctl -u myapp -f |
Follow live log output |
journalctl -u myapp --since today |
View today’s logs |
Example: Node.js Application Service
[Unit]
Description=Node.js Web Application
After=network.target
[Service]
Type=simple
User=nodeapp
WorkingDirectory=/var/www/mysite
ExecStart=/usr/bin/node /var/www/mysite/server.js
Restart=always
RestartSec=3
Environment=NODE_ENV=production PORT=3000
StandardOutput=journal
StandardError=journal
[Install]
WantedBy=multi-user.target
Example: Bash Script Service (One-Shot)
[Unit]
Description=Database Backup Script
After=postgresql.service
[Service]
Type=oneshot
User=postgres
ExecStart=/opt/scripts/backup-database.sh
StandardOutput=journal
StandardError=journal
[Install]
WantedBy=multi-user.target
This service runs the backup script once during boot. To run it on a schedule, pair it with a systemd timer unit rather than cron.
Troubleshooting Common Issues
Service Fails to Start
- Check the logs:
journalctl -u myapp.service -n 50 - Verify the
ExecStartpath is absolute and the binary is executable. - Ensure the User and Group specified exist on the system.
- Confirm the WorkingDirectory exists and is readable by the service user.
Service Keeps Restarting
If your application exits immediately after starting, systemd will restart it repeatedly (if Restart=always or Restart=on-failure is set). This typically indicates a configuration or dependency issue within the application itself. Check the journal logs for error messages.
Changes to the Service File Are Not Taking Effect
After editing a service file, you must reload the systemd daemon configuration:
sudo systemctl daemon-reload
sudo systemctl restart myapp.service
Without daemon-reload, systemd continues using the old cached version of the unit file.
Creating custom systemd services is the standard method for managing long-running processes on Ubuntu. Once configured, your application integrates cleanly with the system’s boot sequence, logging infrastructure, and process management tools — no third-party process managers required.