Help Running your server Docker and apps
Run something on boot with systemd
Write a service file so your app starts at boot, restarts if it crashes, and logs to the journal, then read those logs.
A program started from your SSH session stops when you log out. systemd can run it instead: at boot, in the background, restarted if it crashes, with its output saved. It's the same on Ubuntu, Debian and AlmaLinux.
Give the app its own user#
An app running as root can do anything if it's broken into. Make a user just for it, with no login. The example is myapp in /opt/myapp:
sudo useradd --system --home /opt/myapp --shell /usr/sbin/nologin myappPut the app's files in /opt/myapp and give them to that user:
sudo chown -R myapp:myapp /opt/myappWrite the service file#
A service is a short file in /etc/systemd/system/, called by its name before .service:
sudo tee /etc/systemd/system/myapp.service <<'EOF'
[Unit]
Description=My app
After=network-online.target
Wants=network-online.target
[Service]
User=myapp
Group=myapp
WorkingDirectory=/opt/myapp
EnvironmentFile=-/etc/myapp.env
ExecStart=/opt/myapp/myapp --port 3000
Restart=on-failure
RestartSec=5
NoNewPrivileges=true
[Install]
WantedBy=multi-user.target
EOF| Line | What it does |
|---|---|
After, Wants | Waits for the network before starting |
User, Group | Runs it as its own user, not root |
WorkingDirectory | The folder it starts in |
EnvironmentFile | Reads passwords and tokens from a separate file. The - means fine if missing |
ExecStart | The command. It needs the full path: find it with command -v node |
Restart=on-failure | Starts it again 5 seconds after a crash |
NoNewPrivileges | Stops it gaining more rights than it started with |
WantedBy=multi-user.target | Means start at boot |
Start it#
sudo systemctl daemon-reloadsudo systemctl enable --now myappThe first reads the new file; the second starts it now and at every boot. Then check: sudo systemctl status myapp should say active (running). Press q to leave.
| Command | What it does |
|---|---|
sudo systemctl restart myapp | Restarts it, after you change the app |
sudo systemctl stop myapp | Stops it until the next boot |
sudo systemctl disable myapp | Stops it starting at boot |
sudo systemctl daemon-reload | Rereads the .service file after you edit it. Restart after |
sudo journalctl -u myapp -f | Follows its log live. Ctrl+C to stop |
sudo journalctl -u myapp -n 100 --no-pager | Its last 100 lines |
sudo journalctl -u myapp --since "1 hour ago" | Its log for the last hour |
Keep secrets out of the service file#
Anyone on the server can read /etc/systemd/system/. Put passwords and tokens in the environment file, readable only by root:
sudo tee /etc/myapp.env <<'EOF'
DATABASE_URL=postgres://myapp:[email protected]/myapp
API_TOKEN=replace-me
EOFsudo chmod 600 /etc/myapp.envRestart the app to pick up changes to it.
If it won't start#
systemctl status shows the last lines of output, which usually say why.
| It says | Try |
|---|---|
| Permission denied | myapp can't read its files. Check ls -l /opt/myapp and chown again |
| No such file or directory | The ExecStart path is wrong, or not a full path |
| start request repeated too quickly | It crashes at once. Run it by hand to see why: sudo -u myapp /opt/myapp/myapp --port 3000 |
For a worked example with a real app, see Run a Node.js app or Discord bot.
Still stuck? Open a ticket and say which guide you followed.