Run a Node.js service under systemd with a dedicated unprivileged account, protected filesystem access, restart policy, environment separation, journal logs, and zero-guesswork health checks.
What you will build
This tutorial produces a working, verifiable result rather than a command list. Read each command before running it, keep configuration in version control without secrets, and record the versions used for repeatable deployment.
1. Prepare the project
sudo useradd --system --home /opt/node-app --shell /usr/sbin/nologin nodeapp
sudo install -d -o nodeapp -g nodeapp -m 0750 /opt/node-app
sudo install -o nodeapp -g nodeapp -m 0640 package.json package-lock.json server.js /opt/node-app/
cd /opt/node-app && sudo -u nodeapp npm ci --omit=dev
sudo install -m 0600 -o root -g root /dev/null /etc/node-app.env
Run preparation commands as an unprivileged application user unless a command explicitly requires sudo. A clean working tree and lockfile make rollback much easier.
2. Implement the solution
[Unit]
Description=LAPVN Node.js API
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
User=nodeapp
Group=nodeapp
WorkingDirectory=/opt/node-app
EnvironmentFile=/etc/node-app.env
ExecStart=/usr/local/bin/node server.js
Restart=on-failure
RestartSec=5
TimeoutStopSec=15
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=strict
ProtectHome=true
[Install]
WantedBy=multi-user.target
Save the example in the filename indicated by its comment or surrounding instructions. Treat it as a minimal baseline: production applications should separate transport, business logic, persistence, and configuration into testable modules.
3. Verify end to end
sudo install -m 0644 node-app.service /etc/systemd/system/node-app.service
sudo systemd-analyze verify /etc/systemd/system/node-app.service
sudo systemctl daemon-reload
sudo systemctl enable --now node-app
systemctl status node-app --no-pager
journalctl -u node-app -n 100 --no-pager
curl -fsS http://127.0.0.1:3000/readyz
Verification should cover both process state and a real request or data operation. A process that is merely running is not necessarily ready to serve traffic.
4. Troubleshooting and production notes
Use the absolute node path reported by command -v node. A restart loop usually means an invalid environment, permissions, missing dependencies, or a port conflict; read the journal before changing policy. Grant WritePaths only for directories the app truly needs.
Production checklist
- The supported Node.js LTS version and dependency lockfile are recorded.
- Configuration is validated at startup and secrets are stored outside source control.
- Input limits, authentication, authorization, timeouts, and error boundaries are explicit.
- Logs identify a request without exposing credentials or personal data.
- Health checks, graceful termination, resource limits, backup, and rollback have been tested.
Reference: official topic documentation. For production version selection, use a supported LTS line from the Node.js release schedule.