Why Systemd Instead of nohup or screen?
Many developers deploy apps with nohup python app.py & or inside a screen session. This works until:
- The server reboots and your app doesn't start
- The app crashes at 3am and nobody restarts it
- You need to check logs and they're scattered or lost
- You're running multiple services and lose track
- Auto-start on boot — your app starts when the server does
- Auto-restart on crash — configurable restart policies
- Centralized logging —
journalctlcollects all output - Resource management — set memory and CPU limits
- Dependency management — start after database, network, etc.
Creating Your First Service File
A 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 Files for Different Stacks
[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.
Managing Your Service
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.
Advanced: Resource Limits and Hardening
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 directory
These settings are especially important for production services handling untrusted input.