
A common pain point on VPS is an application stopping after a server reboot or crash without recovering automatically. systemd — the init system and service manager built into every modern Linux distribution — solves this by running your app as a background service that starts on boot and restarts automatically after a failure.
What Is a systemd Service Unit?
A service unit is a plain text file with a .service extension stored in /etc/systemd/system/. It describes which process to run, under which user, with what environment variables, and how to handle restarts. systemd reads this file and manages the process lifecycle for you.
Why not just use PM2? PM2 is popular for Node.js, but systemd is a native Linux solution that requires no extra installation. It works for any language, integrates with journald for centralized logging, and is the standard way to manage services on Ubuntu.
Service Unit File Structure
Every .service file has three sections:
[Unit]
Description=Human-readable service name
After=network.target # Wait for network before starting
[Service]
Type=simple # Process type
User=ubuntu # Run as this user (not root)
WorkingDirectory=/home/ubuntu/myapp
ExecStart=/usr/bin/node server.js
Restart=always # Restart whenever the process exits
RestartSec=5 # Wait 5s before restarting
[Install]
WantedBy=multi-user.target # Enable at boot
Example 1: Node.js Application
Create the Service File
sudo nano /etc/systemd/system/myapp.service
[Unit]
Description=My Node.js Application
After=network.target
[Service]
Type=simple
User=ubuntu
WorkingDirectory=/home/ubuntu/myapp
ExecStart=/usr/bin/node /home/ubuntu/myapp/server.js
Restart=on-failure
RestartSec=10
StandardOutput=journal
StandardError=journal
SyslogIdentifier=myapp
Environment=NODE_ENV=production
Environment=PORT=3000
[Install]
WantedBy=multi-user.target
Enable and Start the Service
# Reload systemd to pick up the new file
sudo systemctl daemon-reload
# Enable auto-start on boot
sudo systemctl enable myapp
# Start the service now
sudo systemctl start myapp
# Check status
sudo systemctl status myapp
Managing Environment Variables and Secrets in systemd
Hardcoding API keys, database passwords, or other secrets directly in a unit file is unsafe because anyone with access to systemctl cat myapp can read them. systemd provides a secure way to inject secrets into services without exposing them in the unit file.
Option 1: EnvironmentFile (Recommended)
# Create a separate environment file
sudo nano /etc/myapp.env
# Contents of /etc/myapp.env (no "export" keyword — KEY=VALUE only)
DB_PASSWORD=SuperSecretPassword123
API_KEY=sk-xxxxxxxxxxxxxxxxxxxx
PORT=3000
# Restrict permissions so only root can read it
sudo chmod 600 /etc/myapp.env
sudo chown root:root /etc/myapp.env
Reference the file in your unit file with EnvironmentFile:
[Service]
User=ubuntu
EnvironmentFile=/etc/myapp.env
ExecStart=/usr/bin/node /home/ubuntu/myapp/server.js
# DB_PASSWORD, API_KEY, and PORT are injected automatically
Option 2: systemd Credentials (Ubuntu 22.04+)
# Encrypt a secret with systemd-creds (optionally sealed to TPM2)
echo -n "SuperSecretPassword" | sudo systemd-creds encrypt --name=db-password -
# Reference in unit file
[Service]
LoadCredential=db-password:/etc/credentials/db-password
# App reads the secret at $CREDENTIALS_DIRECTORY/db-password
Security Rule: Never write passwords directly as Environment=DB_PASS=secret in a unit file. Running systemctl show myapp exposes all environment variables. Always use EnvironmentFile with restricted permissions instead.
Example 2: Python App (FastAPI / Flask)
Set Up a Virtual Environment
cd /home/ubuntu/myapi
python3 -m venv venv
source venv/bin/activate
pip install fastapi uvicorn
Create the Service File for Python/FastAPI
sudo nano /etc/systemd/system/myapi.service
[Unit]
Description=FastAPI Application
After=network.target
[Service]
Type=simple
User=ubuntu
WorkingDirectory=/home/ubuntu/myapi
ExecStart=/home/ubuntu/myapi/venv/bin/uvicorn main:app --host 0.0.0.0 --port 8000
Restart=always
RestartSec=5
StandardOutput=journal
StandardError=journal
SyslogIdentifier=myapi
Environment=ENV=production
[Install]
WantedBy=multi-user.target
sudo systemctl daemon-reload
sudo systemctl enable myapi
sudo systemctl start myapi
sudo systemctl status myapi
Common Service Management Commands
# Check status
sudo systemctl status myapp
# Stop / Start / Restart
sudo systemctl stop myapp
sudo systemctl start myapp
sudo systemctl restart myapp
# Disable from auto-start
sudo systemctl disable myapp
# View last 100 log lines
sudo journalctl -u myapp -n 100
# Follow logs in real time
sudo journalctl -u myapp -f
Production Best Practices
Choosing the Right Restart Policy
Restart=always— Restart whenever the process stops for any reasonRestart=on-failure— Restart only when the process exits with a non-zero codeRestart=on-abnormal— Restart on signal termination or timeout
Resource Limits
[Service]
# Cap memory at 512 MB
MemoryMax=512M
# Limit CPU to 50%
CPUQuota=50%
Security Tip: Always specify a dedicated non-root User= in [Service]. Create a system user with no login shell: sudo useradd -r -s /bin/false appuser and run the service as that user to minimize privilege.
Restart Policy Comparison Table
Choosing the right restart policy is critical for production stability. Restarting too aggressively may mask application bugs; never restarting means a crashed service stays down indefinitely.
| Restart Policy | Restarts When | Does Not Restart When | Best For |
|---|---|---|---|
no |
Never | Any stop | One-time tasks |
on-failure |
Non-zero exit, signal kill | systemctl stop | Most web apps |
always |
Any stop (including clean exit) | Nothing | Critical services |
on-abnormal |
Signal, timeout, watchdog | Normal non-zero exit | Background workers |
Prevent Restart Loops with StartLimitInterval
[Unit]
Description=My Application
StartLimitIntervalSec=60 # Within a 60-second window
StartLimitBurst=3 # Stop trying after 3 restarts
[Service]
Restart=on-failure
RestartSec=10
# If the service restarts more than 3 times in 60 seconds, systemd gives up.
# Run: sudo systemctl reset-failed myapp — before trying to start it again.
Test Auto-Restart
After setting up your service, always simulate a crash in a staging environment to confirm that systemd restarts the process correctly. Check the restart count and the log output to make sure everything behaves as expected before moving to production.
# Kill the process — systemd should restart it within RestartSec seconds
sudo kill -9 $(sudo systemctl show myapp -p MainPID | cut -d= -f2)
# Verify it restarted
sudo systemctl status myapp
# Check how many times the service has restarted
sudo systemctl show myapp -p NRestarts
Ready to Run Your App on a VPS?
Linux VPS starting at 500 THB/month with Full Root Access — deploy Node.js, Python, FastAPI, or any stack you need.
View VPS Plans