How to run program as systemd service for VPS
Make your program run forever even after you log out. This guide show you how to write unit files, handle auto-restart, and check logs using journalctl for your VPS.
Wetin be systemd service, and why you need am
Systemd service na small text file wey dey tell your server how to run program: make e start when server boot, restart am if e crash, and send wetin e dey output go system log. Na dat one be the full work. Program wey you start by hand inside SSH session go die the moment you log out or the server reboot. Program wey you wrap inside systemd service go continue to run, because na the server itself dey own am, no be your shell.
systemd na the init system for Ubuntu, Debian, Fedora, and most modern Linux servers. E be the first process wey dey start and the one wey dey supervise everything else. Dat one no always be true, and how systemd replace the init scripts wey come before am dey worth to read once you know wetin unit file dey do. When you write service file, you dey hand your program give dat supervisor. Dis guide go show the smallest unit wey dey work, the three sections wey every unit get, how to turn am on and read im logs, how to run am on schedule with timer, and how to lock am down so e go run with small privilege as possible.
Di smallest service wey dey work
Service file dey live inside /etc/systemd/system/, e dey end with .service, and e just need small lines. Create one for program wey dey /usr/local/bin/myapp:
sudo nano /etc/systemd/system/myapp.service[Unit]
Description=My application
[Service]
ExecStart=/usr/local/bin/myapp
[Install]
WantedBy=multi-user.targetDat one na complete, working unit. ExecStart na di command wey you go run. WantedBy=multi-user.target mean say make di service start once di server reach normal multi-user operation, na dat one dey make am start wen you boot di system. Every oda tin na just refinement.
Di three sections, and wetin each one dey do
Every unit file get sections wey dey inside square brackets. One service dey use three.
[Unit] dey explain di service and how e take relate with oda things. Di two lines wey you go use pass:
[Unit]
Description=My application
After=network-online.target
Wants=network-online.targetDescription na di human label wey you go see for systemctl status. After=network-online.target dey tell systemd make e no start your program until network don ready, dis one dey important for anytin wey dey bind port or connect go outside.
[Service] na how di program dey run. Na here most of your settings dey stay:
[Service]
ExecStart=/usr/local/bin/myapp --config /etc/myapp/config.toml
WorkingDirectory=/opt/myapp
User=myapp
Restart=on-failure
RestartSec=5
Environment=LOG_LEVEL=infoUser=myapp dey run di program as user wey no get special privilege instead of root, and dis one na di most important line for safety. No Type= line dey here, so systemd go just use simple and assume say di ExecStart process go stay for foreground; if di program dey fork go background, you need di correct Type= for how e take start, if not, di unit go show say e active even though di real daemon don disappear. Restart=on-failure and RestartSec=5 get dia own section for down, because na dem be di reason why most pipo dey write service.
[Install] na wetin dey happen when you enable di service:
[Install]
WantedBy=multi-user.targetWantedBy=multi-user.target na wetin dey link di service make e start wit system boot when you run systemctl enable. If [Install] section no dey, you fit start di service wit your hand, but e no go start by itself after you reboot di system.
Turn am on and watch am
After you write or edit any unit file, reload systemd make e read the change, then enable and start the service for one time:
sudo systemctl daemon-reload
sudo systemctl enable --now myapp.servicedaemon-reload na the step wey people dey forget: systemd dey cache unit files, so if you edit am, e no go work until you reload. enable --now go enable the service for boot and start am sharp-sharp. Check am:
sudo systemctl status myapp.service* myapp.service - My application
Loaded: loaded (/etc/systemd/system/myapp.service; enabled)
Active: active (running) since Wed 2026-07-15 22:40:11 UTC; 3s ago
Main PID: 4123 (myapp)Active: active (running) and enabled na wetin you want see. To read wetin the program dey output, ask the journal for just this unit:
sudo journalctl -u myapp.service -fThe -f dey follow new lines as dem dey show, just like tail -f. Anything wey your program write go standard output or standard error go land here, you no need do any extra logging setup.
Restart wen e fail, di reason why you dey here
Di main benefit of service na say systemd go restart your program wen e die. Na two lines dey do am:
[Service]
Restart=on-failure
RestartSec=5Restart=on-failure go restart di program if e exit wit non-zero code or if e die sake of crash signal like SIGKILL or SIGSEGV. If di program exit well, or if you stop am wit SIGTERM, SIGINT, SIGHUP, or SIGPIPE, e no go restart. RestartSec=5 go wait five seconds between each try, so if di program crash quick-quick, e no go just dey spin for one tight loop. Confirm am by killing di process and watch as systemd bring am back. Use SIGKILL: because di default SIGTERM na clean stop, so on-failure no go restart di service:
sudo systemctl kill -s SIGKILL myapp.service
sudo systemctl status myapp.serviceWithin five seconds, di status go show new Main PID and active (running) again. Na dat one be di whole feature, and na why service better pass make you just leave program dey run for tmux or screen.
Run am as user wey no get special power, and make am strong
If program get flaw and e dey run as root, attacker fit do anything for your server. Make you run am as im own user, and give systemd some directives wey go lock am inside. First, create system account wey no fit login and no get home directory:
sudo useradd --system --no-create-home --shell /usr/sbin/nologin myappThen set User=myapp and add these hardening lines go [Service]:
[Service]
User=myapp
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=strict
ProtectHome=trueEvery line dey remove wetin the program no need. NoNewPrivileges=true go stop the process make e no fit gain new privileges, even if e try use setuid binary. PrivateTmp=true go give am private /tmp wey no other process fit see. ProtectSystem=strict go make the whole filesystem read-only, except for some paths wey you go name with ReadWritePaths=. ProtectHome=true go hide /home from am completely. This na the same "least-privilege" logic as when you put service behind firewall: give am only wetin e need. If you don read the guide about how to close IPv6 firewall gap for VPS, this one na the part wey you go do for inside the host. For service wey dey face internet, join this hardening with Fail2ban for front of SSH and default-deny firewall.
Instead of make you dey type all this one by hand and come forget directive, generate complete, hardened unit and copy am out:
Timers: cron wey modern
A systemd timer dey run one service according to schedule, and na modern replacement for cron job. Timer na two files: one .service wey dey do the work, and one .timer wey dey talk when e go run. Suppose say you want backup every day for 3am. The service go do the task once and exit:
# /etc/systemd/system/backup.service
[Unit]
Description=Nightly backup
[Service]
Type=oneshot
ExecStart=/usr/local/bin/backup.shType=oneshot dey tell systemd say the program go run, finish, and done, instead of remaining resident. The timer dey schedule am:
# /etc/systemd/system/backup.timer
[Unit]
Description=Run the nightly backup
[Timer]
OnCalendar=*-*-* 03:00:00
Persistent=true
[Install]
WantedBy=timers.targetOnCalendar=*-*-* 03:00:00 mean 3am every day. Test any calendar expression with systemd-analyze calendar "*-*-* 03:00:00". E go confirm say e parse correctly and print the next time e go run. By default, e dey show only the next run; add --iterations=5 to see the next five. Persistent=true go run missed job as soon as server come back if e dey off for 3am, something cron no fit do. Notice say you enable timer through timers.target, not multi-user.target. Enable the timer, not the service:
sudo systemctl daemon-reload
sudo systemctl enable --now backup.timer
systemctl list-timerslist-timers dey show every timer with its next and last run, so you fit quickly see when your job go run next. The generator above dey create the paired .service and .timer for you when you enable timer mode. Compared with cron line, timer gives you real logs for the journal, the same hardening directives wey any service get, and missed-run catch-up wey Persistent=true provides. Cron still dey okay for simple job; timer na better tool once the job become important.
FAQ
Wetin be the difference between systemd service and cron job?
One service dey keep long-running program alive: e dey start wen system boot, e dey restart if e fail, and e dey log output go the journal. One cron job dey run short command base on schedule then e go stop. If you want schedule but you still want journal logs, hardening, and make e run missed tasks, use systemd timer. E dey pair .timer schedule with oneshot service and e dey replace cron for most server tasks.
Where I suppose put my systemd service file?
Put your own units for /etc/systemd/system/, with name wey end with .service. That directory na for units wey administrator add, and e get priority pass units wey packages ship for /lib/systemd/system/. After you create or edit file for there, run sudo systemctl daemon-reload so systemd go see the change.
How I go make service restart if e crash?
Add Restart=on-failure and RestartSec=5 for the [Service] section, then run sudo systemctl daemon-reload and restart the service. systemd go relaunch the program if e exit with non-zero code or if e die from crash signal, and e go wait five seconds between tries. Test am with sudo systemctl kill -s SIGKILL myapp.service. SIGTERM, the default signal, na clean stop and e no dey trigger on-failure, and you go see systemctl status show new PID within few seconds.
How I go run systemd service as non-root user?
Create system account with sudo useradd --system --no-create-home --shell /usr/sbin/nologin myapp, then add User=myapp for the [Service] section. Add NoNewPrivileges=true, PrivateTmp=true, and ProtectSystem=strict so the process go run with only the access wey e need. To run as user wey no get special privilege na the most important change wey you fit make for service safety.
Why my service no gree start?
Run systemctl status myapp.service for summary and journalctl -u myapp.service for the full output. The common reasons na wrong path for ExecStart, missing WorkingDirectory, permission error because User= no fit read file, or you forget sudo systemctl daemon-reload after you edit. The journal go show the error message from the program, wey usually dey tell you the problem directly.