Learn how to create systemd service files to run Node.js, Python, Go, or any application as a background service that starts on boot and auto-restarts on crash.
Many developers deploy apps with nohup python app.py & or inside a screen session. This works until:
journalctl collects all outputA systemd service file lives in /etc/systemd/system/ and has a .service extension.
sudo nano /etc/systemd/system/myapi.service
[Unit]
Description=My FastAPI Application
After=network.target
[Service]
Type=simple
User=deploy
Group=deploy
WorkingDirectory=/home/deploy/myapi
EnvironmentFile=/home/deploy/myapi/.env
ExecStart=/home/deploy/myapi/venv/bin/uvicorn app.main:app --host 127.0.0.1 --port 8000
Restart=always
RestartSec=5
StandardOutput=journal
StandardError=journal
[Install]
WantedBy=multi-user.target
Key fields explained:
After=network.target — wait for networking before startingUser=deploy — run as non-root userEnvironmentFile — load environment variables from .envRestart=always — restart on any exit (crash, signal, etc.)RestartSec=5 — wait 5 seconds before restartingWantedBy=multi-user.target — start in normal multi-user mode[Service]
Type=simple
User=deploy
WorkingDirectory=/home/deploy/myapp
ExecStart=/usr/bin/node server.js
Restart=always
Environment=NODE_ENV=production
Environment=PORT=3000
Go Binary:
[Service]
Type=simple
User=deploy
ExecStart=/home/deploy/myapp/server
Restart=always
LimitNOFILE=65535
Docker Compose:
[Service]
Type=oneshot
RemainAfterExit=yes
User=deploy
WorkingDirectory=/home/deploy/myapp
ExecStart=/usr/bin/docker compose up -d
ExecStop=/usr/bin/docker compose down
The pattern is always the same: specify the working directory, the start command, and the user.
After creating the service file:
# Reload systemd to pick up new file
sudo systemctl daemon-reload
# Start the service
sudo systemctl start myapi
# Enable auto-start on boot
sudo systemctl enable myapi
# Check status
sudo systemctl status myapi
# View logs (last 50 lines)
sudo journalctl -u myapi -n 50
# Follow logs in real-time
sudo journalctl -u myapi -f
# Restart after code changes
sudo systemctl restart myapi
# Stop the service
sudo systemctl stop myapi
The enable command is what makes it start on boot. Without it, the service only runs when you manually start it.
Prevent a runaway process from taking down your server:
[Service]
# Memory limit (kill if exceeded)
MemoryMax=512M
MemoryHigh=400M
# CPU limit (50% of one core)
CPUQuota=50%
# Security hardening
NoNewPrivileges=yes
ProtectSystem=strict
ProtectHome=read-only
PrivateTmp=yes
ReadWritePaths=/home/deploy/myapi/data
MemoryMax — hard kill if memory exceeds 512MBMemoryHigh — throttle at 400MB (soft limit)CPUQuota — limit CPU usageProtectSystem=strict — make the entire filesystem read-only except specified pathsPrivateTmp — give the service its own /tmp directoryThese settings are especially important for production services handling untrusted input.