Turn Any Script Into a Proper systemd Service — A Practical Guide
You wrote a script. It works. Now you want it to run forever, start on boot, and restart if it crashes. Here’s how, using systemd.
Create the Service File
sudo nano /etc/systemd/system/myapp.service
[Unit]
Description=My Application
After=network.target
Wants=network-online.target
[Service]
Type=simple
User=appuser
Group=appuser
WorkingDirectory=/opt/myapp
ExecStart=/opt/myapp/start.sh
Restart=on-failure
RestartSec=5
StartLimitBurst=3
StartLimitIntervalSec=60
# Security hardening
NoNewPrivileges=true
ProtectSystem=strict
ProtectHome=true
ReadWritePaths=/opt/myapp/data
PrivateTmp=true
# Environment
Environment=NODE_ENV=production
EnvironmentFile=/opt/myapp/.env
# Logging
StandardOutput=journal
StandardError=journal
SyslogIdentifier=myapp
[Install]
WantedBy=multi-user.target
Enable and Start
sudo systemctl daemon-reload
sudo systemctl enable myapp
sudo systemctl start myapp
Daily Commands
# Check status
systemctl status myapp
# View logs
journalctl -u myapp -f # follow
journalctl -u myapp --since today # today's logs
journalctl -u myapp -n 50 # last 50 lines
# Restart after code changes
sudo systemctl restart myapp
# Stop
sudo systemctl stop myapp
Useful Patterns
Timer (instead of cron):
# /etc/systemd/system/backup.timer
[Unit]
Description=Daily backup
[Timer]
OnCalendar=<em>-</em>-* 02:00:00
Persistent=true
[Install]
WantedBy=timers.target
Socket activation (start on first request):
# /etc/systemd/system/myapp.socket
[Unit]
Description=My App Socket
[Socket]
ListenStream=8080
[Install]
WantedBy=sockets.target
Debugging
# Why did it fail?
systemctl status myapp
journalctl -u myapp -e
# Check config syntax
systemd-analyze verify myapp.service
# See all failed services
systemctl --failed
# See service dependencies
systemctl list-dependencies myapp
The security hardening options (NoNewPrivileges, ProtectSystem, ProtectHome) are free security improvements that most tutorials skip. Use them.
Write a comment