How to Create a systemd Service for Your App
Turn any script or server into a managed service: a systemd unit file that starts on boot, restarts on crash, and logs to journald.

A systemd service turns any long-running program — a Node server, a Python app, a Go binary, a script — into a managed background service that starts on boot, restarts if it crashes, and logs cleanly. This guide shows you how to write a robust unit file and manage it, on Ubuntu 24.04 or any modern Linux.
Why systemd?
Running node app.js or python main.py in a terminal dies when you disconnect. systemd is the init system already running on your server; letting it supervise your app gives you automatic startup, crash recovery, resource limits, and unified logging — with no extra tools to install. It is the leanest alternative to something like PM2.
Anatomy of a unit file
Unit files live in /etc/systemd/system/ and have three sections. Create /etc/systemd/system/myapp.service:
[Unit]
Description=My application
After=network.target
[Service]
Type=simple
User=youruser
WorkingDirectory=/home/youruser/myapp
ExecStart=/usr/bin/node /home/youruser/myapp/server.js
Restart=on-failure
RestartSec=5
Environment=NODE_ENV=production
Environment=PORT=3000
[Install]
WantedBy=multi-user.target
What each key does:
- After=network.target: wait for networking before starting.
- Type=simple: the process started by
ExecStartis the service itself (the common case). - User: run as an unprivileged user, never root, for safety.
- WorkingDirectory: the directory the process runs in.
- ExecStart: the exact command — always use absolute paths.
- Restart=on-failure + RestartSec: restart 5 seconds after a crash.
- Environment: inline environment variables.
- WantedBy=multi-user.target: start at boot in normal multi-user mode.
Load and start it
Whenever you add or edit a unit file, reload systemd, then enable (start on boot) and start it now:
sudo systemctl daemon-reload
sudo systemctl enable --now myapp
sudo systemctl status myapp
enable --now both enables and starts in one step. The status output shows whether it is active (running) and the most recent log lines.

Read the logs
systemd captures your app's stdout and stderr in the journal:
journalctl -u myapp -f # live tail
journalctl -u myapp -e # jump to the end
journalctl -u myapp --since "1 hour ago"
No log files to configure — it just works, with rotation handled by journald.
Load secrets from a file
Hard-coding secrets in the unit is poor practice. Use an environment file instead:
EnvironmentFile=/home/youruser/myapp/.env
Put KEY=value lines in that .env (no export, no quotes needed) and lock it down with chmod 600.
Add basic hardening
systemd can sandbox your service with a few extra lines in [Service]:
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=full
These stop the service gaining privileges, give it a private /tmp, and make most of the filesystem read-only to it — cheap defence in depth that complements your VPS hardening.
Common management commands
- Restart:
sudo systemctl restart myapp - Stop:
sudo systemctl stop myapp - Disable at boot:
sudo systemctl disable myapp - See if enabled:
systemctl is-enabled myapp
This same unit pattern powers our guides for Flask + Gunicorn, Django, and FastAPI.
Run tasks on a schedule with timers
systemd can also replace cron for scheduled work, with the bonus of journald logging and catch-up on missed runs. A timer needs two units: a service that does the work, and a timer that triggers it. Create backup.service:
[Unit]
Description=Nightly backup
[Service]
Type=oneshot
ExecStart=/home/youruser/scripts/backup.sh
And backup.timer:
[Unit]
Description=Run backup daily
[Timer]
OnCalendar=*-*-* 03:00:00
Persistent=true
[Install]
WantedBy=timers.target
Persistent=true means a run missed while the server was off fires at next boot — something cron cannot do. Enable the timer, not the service:
sudo systemctl enable --now backup.timer
systemctl list-timers
list-timers shows every scheduled unit and when each next fires. For simple recurring commands a cron job is still perfectly fine; reach for timers when you want logging and catch-up.
FAQ
systemd or PM2 for a Node app?
PM2 is Node-native with clustering and a rich CLI. systemd is universal, dependency-free, and already running — ideal if you want one supervisor for every service on the box regardless of language. Both restart on crash and boot.
Why does my service fail with status 203/EXEC?
That means systemd could not execute ExecStart. The path is wrong or not executable — use the absolute path (find it with which node) and double-check the file exists and the User can run it.
How do I restart a service automatically only on crashes?
Use Restart=on-failure, which restarts on non-zero exit or a signal but not on a clean stop. Restart=always restarts even after a normal exit — useful for apps meant to run forever.
Where do user vs system services live?
System services go in /etc/systemd/system/ and run as root/systemd at boot. User services live under ~/.config/systemd/user/ and run within a user session — less common on servers, where system services are the norm.
Nxeon VPS plans give you full root to manage services with systemd exactly as you like on fast NVMe — with free migration help if you are moving apps across.