Deploy Node.js with systemd and NGINX
Deploy a Node.js application on Ubuntu 26.04, run it as a dedicated systemd service, and add NGINX with HTTPS.
This guide deploys a Node.js application to /srv/example-api, runs it under a dedicated Linux account, and exposes it through NGINX. Replace api.example.com and the example application with your own values.
Prerequisites
- An Ubuntu 26.04 VPS prepared with the initial setup guide
- A domain with A and, when used, AAAA records pointing to the VPS
- A non-root user with sudo access
- An application with a health endpoint and a documented Node.js version
Install the runtime and NGINX
Ubuntu packages provide a straightforward baseline. Confirm the packaged Node.js version is supported by your application before continuing.
sudo apt update
sudo apt install nodejs npm nginx certbot python3-certbot-nginx
node --version
npm --version
Use the installation method recommended by Node.js if the application needs another maintained release line.
Create the service account and directories
sudo adduser --system --group --home /srv/example-api example-api
sudo install -d -o example-api -g example-api -m 0750 /srv/example-api/releases
sudo install -d -o root -g example-api -m 0750 /etc/example-api
Upload the application into a versioned directory such as /srv/example-api/releases/2026-09-21-1. Install production dependencies as example-api, then point current to that release:
sudo -u example-api npm --prefix /srv/example-api/releases/2026-09-21-1 ci --omit=dev
sudo ln -sfn /srv/example-api/releases/2026-09-21-1 /srv/example-api/current
Create the environment file with root ownership and read access for the service group:
sudo install -o root -g example-api -m 0640 /dev/null /etc/example-api/app.env
sudoedit /etc/example-api/app.env
Store only runtime configuration there. Do not commit this file.
NODE_ENV=production
HOST=127.0.0.1
PORT=3000
Create the systemd service
Create /etc/systemd/system/example-api.service:
[Unit]
Description=Example Node.js API
After=network.target
[Service]
Type=simple
User=example-api
Group=example-api
WorkingDirectory=/srv/example-api/current
EnvironmentFile=/etc/example-api/app.env
ExecStart=/usr/bin/node server.js
Restart=on-failure
RestartSec=5
PrivateTmp=true
NoNewPrivileges=true
[Install]
WantedBy=multi-user.target
Validate, start, and test the service locally:
sudo systemd-analyze verify /etc/systemd/system/example-api.service
sudo systemctl daemon-reload
sudo systemctl enable --now example-api
sudo systemctl status example-api --no-pager
curl --fail http://127.0.0.1:3000/health
If the request fails, inspect sudo journalctl -u example-api -n 100 --no-pager before configuring NGINX.
Configure NGINX
Create /etc/nginx/sites-available/api.example.com:
server {
listen 80;
listen [::]:80;
server_name api.example.com;
location / {
proxy_pass http://127.0.0.1:3000;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
sudo ln -s /etc/nginx/sites-available/api.example.com /etc/nginx/sites-enabled/api.example.com
sudo nginx -t
sudo systemctl reload nginx
sudo ufw allow 'Nginx Full'
curl --fail http://api.example.com/health
Request the certificate only after DNS and the HTTP test work:
sudo certbot --nginx -d api.example.com
sudo certbot renew --dry-run
curl --fail https://api.example.com/health
Deploy an update and roll back
Upload each release to a new directory, run npm ci --omit=dev, switch the current symlink, and restart the service. Check the health endpoint and logs immediately.
If verification fails, point current back to the previous release, restart example-api, and repeat the health check. Database migrations need their own tested rollback or forward-fix plan.
Operational checks
- Confirm Node.js listens only on
127.0.0.1:3000withsudo ss -lntp. - Monitor systemd restarts, NGINX errors, response time, memory, and free disk space.
- Patch Ubuntu, Node.js, dependencies, and NGINX on a planned schedule.
- Back up application data and configuration, then test restore separately.
For plan sizing and operating tradeoffs, see hosting web applications on a VPS.
Sources: systemd service documentation, NGINX proxy module, and Certbot instructions.