← Back to documentation
Intermediate

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.

nodejsnginxsystemddeploymenthttps

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:3000 with sudo 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.