A proper unit file gives your app automatic restarts, logging, boot-time start and security sandboxing for free.
Custom units go in /etc/systemd/system/name.service. A service unit has three sections:
[Unit]:Description=, and ordering/dependencies such asAfter=network-online.targetwithWants=network-online.target, orRequires=postgresql.service.[Service]: how to run it.ExecStart=needs an absolute path and runs the program directly, without a shell, so pipes and$(...)do not work unless you call/bin/sh -c.Type=simple(default: the process stays in the foreground),notify(the program signals readiness) oroneshot(scripts that exit). AddUser=,WorkingDirectory=,Environment=orEnvironmentFile=, andRestart=on-failurewithRestartSec=5.[Install]:WantedBy=multi-user.targetmakesenablestart it at boot.
After creating or editing a unit run systemctl daemon-reload. To change a unit shipped by a package, do not edit it in /usr/lib/systemd/system: run systemctl edit name, which creates a drop-in override in /etc/systemd/system/name.service.d/.
systemd can sandbox a service with a few lines: NoNewPrivileges=yes, ProtectSystem=strict (read-only filesystem except ReadWritePaths=), ProtectHome=yes, PrivateTmp=yes, or DynamicUser=yes for a throwaway user. systemd-analyze security name scores how exposed a service is. The app should log to stdout/stderr and let the journal handle storage.
Example
# /etc/systemd/system/myapp.service
[Unit]
Description=My web app
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
User=myapp
WorkingDirectory=/opt/myapp
EnvironmentFile=/etc/myapp/env
ExecStart=/opt/myapp/bin/server --port 8080
Restart=on-failure
RestartSec=5
NoNewPrivileges=yes
ProtectSystem=strict
ReadWritePaths=/var/lib/myapp
PrivateTmp=yes
[Install]
WantedBy=multi-user.target
# Then: sudo systemctl daemon-reload && sudo systemctl enable --now myapp
# Override a packaged unit instead of editing it: sudo systemctl edit nginxRemember
- [Unit] ordering, [Service] how to run, [Install] when to start
- ExecStart runs without a shell; daemon-reload after edits
- Use drop-ins via systemctl edit and add sandboxing options
Watch out for
- Writing
ExecStart=cd /opt/app && ./server: there is no shell, so && and cd do not work.