Managing VPS Services with systemd: start, enable, logs
systemd is the init system and service manager used on all modern Linux distributions—from Ubuntu and Debian to AlmaLinux and Rocky Linux. The systemctl command lets you start, stop, restart, and monitor various services on your VPS in an organized manner. It also allows you to create custom services to run your own scripts or applications with automatic startup after reboot.
Basic systemctl Commands
# Start a service
systemctl start nginx
# Stop a service
systemctl stop nginx
# Restart (stop and start again)
systemctl restart nginx
# Reload (reload config without killing the process — if the service supports it)
systemctl reload nginx
# Enable auto-start after reboot
systemctl enable nginx
# Disable auto-start
systemctl disable nginx
# Check status (active/inactive/failed)
systemctl status nginx
View All Services
# All services on the system
systemctl list-units --type=service
# Only active services
systemctl list-units --type=service --state=active
# Only failed services
systemctl list-units --type=service --state=failed
# Services that are enabled (will start after reboot)
systemctl list-unit-files --type=service --state=enabled
Viewing Logs with journalctl
systemd stores logs for all services in the journal, which you can view using journalctl:
# View all logs for a service
journalctl -u nginx
# View logs in real-time (like tail -f)
journalctl -u nginx -f
# View only the last 100 lines
journalctl -u nginx -n 100
# View logs since the last reboot
journalctl -u nginx -b
# View logs within a specific time range
journalctl -u nginx --since "2026-06-01 10:00:00" --until "2026-06-01 12:00:00"
# View only error level and above
journalctl -u nginx -p err
# View logs for all services (boot log)
journalctl -b
Creating a Custom Service Unit
If you want your application or script to run as a service and start automatically, create a unit file:
Example: Running a Node.js App as a systemd Service
# Create the unit file
nano /etc/systemd/system/myapp.service
[Unit]
Description=My Node.js Application
After=network.target
[Service]
Type=simple
User=www-data
WorkingDirectory=/var/www/myapp
ExecStart=/usr/bin/node /var/www/myapp/server.js
Restart=always
RestartSec=5
StandardOutput=journal
StandardError=journal
Environment=NODE_ENV=production
Environment=PORT=3000
[Install]
WantedBy=multi-user.target
# Reload the unit file
systemctl daemon-reload
# Enable and start
systemctl enable myapp
systemctl start myapp
# Verify
systemctl status myapp
Example: Running a Python Script as a Background Service
[Unit]
Description=My Python Worker
After=network.target mysql.service
Requires=mysql.service
[Service]
Type=simple
User=worker
WorkingDirectory=/opt/worker
ExecStart=/opt/worker/venv/bin/python /opt/worker/main.py
Restart=on-failure
RestartSec=10
StartLimitInterval=60
StartLimitBurst=3
[Install]
WantedBy=multi-user.target
always = restart in all cases (including normal stop), on-failure = restart only on crash, on-abnormal = restart when exit code is non-zero or killed by signal
Setting Resource Limits in a Service
systemd supports resource limiting for individual services directly—no need for separate ulimit or cgroups configuration:
[Service]
; Limit CPU to 50% of 1 core
CPUQuota=50%
; Limit RAM
MemoryLimit=512M
; Limit disk I/O
IOWeight=50
; Limit number of processes
LimitNPROC=100
; Limit file descriptors
LimitNOFILE=65536
Override Existing Service Values (systemctl edit)
Instead of directly editing the original unit file (which will be overwritten on updates), use systemctl edit to create overrides:
# Open the override editor
systemctl edit nginx
# The system creates /etc/systemd/system/nginx.service.d/override.conf
# Add values you want to override, for example:
[Service]
LimitNOFILE=65536
Environment=NGINX_WORKER=4
Systemd Timers as an Alternative to Cron
systemd timers are a cron alternative with better logging, retry capability, and journal integration:
# Create /etc/systemd/system/backup.service
[Unit]
Description=Daily Database Backup
[Service]
Type=oneshot
ExecStart=/usr/local/bin/backup-db.sh
# Create /etc/systemd/system/backup.timer
[Unit]
Description=Run backup daily at 2am
[Timer]
OnCalendar=daily
OnCalendar=*-*-* 02:00:00
Persistent=true
[Install]
WantedBy=timers.target
# Enable
systemctl enable backup.timer
systemctl start backup.timer
# View all timers
systemctl list-timers
Troubleshooting Failed Services
# Check why the service failed
systemctl status myapp
journalctl -u myapp -n 50
# If service is blocked due to start limit exceeded
systemctl reset-failed myapp
systemctl start myapp
# View service dependencies
systemctl list-dependencies nginx
# Check unit file syntax
systemd-analyze verify /etc/systemd/system/myapp.service
Understanding systemd helps you manage your VPS systematically, whether it's viewing logs, configuring auto-restart, or creating new services for your applications. Combined with journalctl and systemd timers, you can replace traditional cron and log management entirely on a single machine.
Verify Services are Working Correctly
# Check service status
systemctl status myapp
# View recent logs
journalctl -u myapp -n 30
# Test unit file syntax
systemd-analyze verify /etc/systemd/system/myapp.service
# List all failed services
systemctl list-units --type=service --state=failed
# Verify service will start after reboot
systemctl is-enabled myapp
systemctl status myapp to see the last 10 log lines + active/inactive status in one command—perfect for quick checks before using journalctl for more details
Common Issues & Troubleshooting
Error: "Failed to start service" — service won't start
Check the logs to find the real error—the status message may be too brief:
# View detailed logs
journalctl -u myapp -n 50 --no-pager
# Check exit code
systemctl status myapp | grep "Main PID\|Exit code"
# Verify all paths in ExecStart
which node # verify binary path
ls /var/www/myapp/server.js # verify script exists
Error: "Too many start requests for unit" — start limit exceeded
The service is crashing and restarting too quickly, hitting systemd's start limit:
# Reset the failed counter
systemctl reset-failed myapp
# Fix the root cause first before restarting
journalctl -u myapp -n 100 | grep -i "error\|fatal\|exception"
# If needed, increase StartLimitInterval in the unit file
[Service]
StartLimitInterval=120
StartLimitBurst=5
Error: Unit file changed but systemctl still uses old config
You must run daemon-reload every time you edit a unit file:
# Required every time!
systemctl daemon-reload
# Then restart the service
systemctl restart myapp
Error: Service runs but doesn't start after reboot
Enable and start are different commands—start opens it now, enable makes it start after reboot:
# Check if enabled
systemctl is-enabled myapp
# Should answer: enabled
# If disabled, enable it
systemctl enable myapp
# Enable and start together
systemctl enable --now myapp
Error: Service restarts in a loop and uses high CPU
Your application is in a crash loop. Use RestartSec to add delay, or use on-failure instead of always:
[Service]
Restart=on-failure # restart only on crash (not on normal stop)
RestartSec=10 # wait 10 seconds before restarting
StartLimitBurst=3 # max 3 restarts
StartLimitInterval=60 # within 60 seconds
Frequently Asked Questions
How is systemd timer better than cron?
systemd timers log every run through journalctl, allow you to set dependencies on other services (wait for network to be ready first), can retry if the server was down when the timer should have run (Persistent=true), and show status in systemctl list-timers. Cron is simpler and more familiar—choose based on your use case.
Is it safe to use Environment= in the unit file? Will secrets leak?
Environment= is safe for general values, but for secrets like API keys or passwords, use EnvironmentFile= to point to a file with 600 permissions, such as EnvironmentFile=/etc/myapp/secrets.env, so secrets don't appear in systemctl show myapp.
How do I view logs for a service from 3 days ago?
journalctl supports flexible --since and --until formats: journalctl -u myapp --since "3 days ago" or journalctl -u myapp --since "2026-06-06 00:00:00". Logs persist until the journal storage fills up—check size with journalctl --disk-usage.