systemd is the initialization and service management system in modern Linux distributions (Ubuntu 16.04+, Debian 8+, CentOS 7+). A unit file describes how to start and stop a service.
Minimal Unit File
# /etc/systemd/system/myapp.service
[Unit]
Description=My Application
After=network.target
[Service]
ExecStart=/usr/bin/node /opt/myapp/app.js
WorkingDirectory=/opt/myapp
User=www-data
Restart=on-failure
[Install]
WantedBy=multi-user.target
Key [Unit] Directives
| Directive | Value |
|---|---|
| Description | Service description |
| After=network.target | Start after network initialization |
| After=mysql.service | Start after MySQL |
| Requires=docker.service | Hard dependency |
| Wants=redis.service | Soft dependency |
Key [Service] Directives
[Service]
Type=simple # Type: simple, forking, oneshot, notify
ExecStart=/path/cmd # Start command
ExecStop=/path/stop # Stop command (optional)
ExecReload=/bin/kill -HUP $MAINPID # Reload config
WorkingDirectory=/app # Working directory
User=appuser # Run as this user
Group=appgroup # Group
Environment=NODE_ENV=production # Environment variables
EnvironmentFile=/etc/myapp.env # File with variables
# Restart policy
Restart=always # Always restart
Restart=on-failure # Only on failure
RestartSec=5s # Pause before restart
# Resource limits
LimitNOFILE=65536 # Open file limit
MemoryLimit=512M # Memory limit
Activation and Management
# Reload unit files
sudo systemctl daemon-reload
# Enable autostart
sudo systemctl enable myapp
# Start
sudo systemctl start myapp
# Check status
sudo systemctl status myapp
# View logs
sudo journalctl -u myapp -f
# Today's logs
sudo journalctl -u myapp --since today
Example: Python Application with virtualenv
>[Unit]
Description=Python Flask App
After=network.target postgresql.service
[Service]
Type=simple
User=flask
WorkingDirectory=/opt/flask_app
Environment=FLASK_ENV=production
ExecStart=/opt/flask_app/venv/bin/gunicorn --workers 4 --bind 0.0.0.0:8000 app:application
Restart=on-failure
RestartSec=10
[Install]
WantedBy=multi-user.target
💡 Storing secrets: Don't put passwords in the unit file — it's readable by everyone. Use
EnvironmentFile=/etc/myapp.env with 600 permissions. Even better — systemd credentials or HashiCorp Vault.Common systemd Unit File Errors
| Message | Cause |
|---|---|
| Failed to start myapp.service: Unit not found | The file is not in /etc/systemd/system/, or systemctl daemon-reload was never run after creating it |
| Main process exited, code=exited, status=203/EXEC | The ExecStart path is wrong, or the file lacks execute permission — run chmod +x |
| Failed with result 'exit-code' right after start | The application crashes without an environment variable that lived in .bashrc — systemd does not read a user's shell config files |
| Start request repeated too quickly | Restart=always combined with a constantly crashing service exceeds the StartLimitBurst limit within StartLimitIntervalSec; systemd temporarily blocks the autostart |
| Failed to determine user credentials: No such process | The user named in User= does not exist on the system — a typo in the name, or the user was never created beforehand |
When a systemd Unit Is Not the Right Tool
- You need to run a process without root access to the server — creating a file under /etc/systemd/system requires privileges that most shared hosting plans do not grant; only cron is available there for scheduled work
- The application needs to scale across several instances with load balancing — one unit file runs one process; several copies need template units (@.service) or a separate orchestrator such as Docker Compose
- The service runs inside a minimal Docker container — systemd usually is not running there as PID 1, so you need a different supervisor (tini, s6, a built-in healthcheck)
- You need one cross-platform service definition for both Linux and Windows — systemd is Linux-specific; Windows needs a separate mechanism such as NSSM
Verifying the Result
Confirming the service starts once is not enough — it matters just as much that it survives a crash and does not hit memory limits. The system logs are covered in the article on journalctl and rsyslog, and a common cause of processes suddenly stopping is covered in the article on the Linux OOM killer.
# Confirm the service actually restarts after a crash
sudo systemctl kill -s SIGKILL myapp
sleep 3
sudo systemctl status myapp | grep Active
# Check whether the process is hitting a memory limit
journalctl -u myapp | grep -i "oom\|killed"