TutorialsAugust 4, 20264 min read

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.

NBy Nxeon

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 ExecStart is 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.

The Nxeon game-server control panel — live console, player slots, and TPS
The Nxeon game-server control panel — live console, player slots, and TPS

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.

#systemd#linux#ubuntu#vps#deployment#seobatch

Deploy your first server in under a minute

Creating an account is free and takes no card details. You pay when you deploy — choose a billing term and pay from your wallet or by card at checkout.