Kingsfordweg 151, 1043GR Amsterdam, The Netherlands ● 24/7 support — support@novogara.com
Knowledge base

Deploying Node.js

Node in production is straightforward once you stop running it the way you run it on your laptop.

Run it as a service

Not npm start in a terminal, not screen. A systemd unit gives you restart on failure, start at boot, journald logging and sandboxing — see running your application as a service.

pm2 does some of the same things, and then you have a process manager being managed by a process manager. If you already use it, run pm2 itself under systemd. For new deployments, systemd alone is simpler.

In the unit: Environment=NODE_ENV=production, an unprivileged User=, and ExecStart=/usr/bin/node /srv/app/server.js with an absolute path to the binary.

Put a proxy in front

Let nginx terminate TLS, serve static files and buffer slow clients; let Node do your logic. Listen on 127.0.0.1:3000 and never on a public address. See nginx pitfalls for the proxy headers, and add WebSocket support if you need it:

proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";

Use all the cores

Node is single-threaded. On a machine with sixteen cores, one process uses one of them. Two options:

  • Several systemd instances on different ports, balanced by nginx upstream. Explicit, and each can be restarted independently.
  • The cluster module, which forks workers that share a socket. Fewer moving parts, and a crash in the primary takes everything.

Either way, do not run more workers than cores; you add context switching, not throughput. See choosing a CPU.

Memory

The V8 heap has a default ceiling that is unrelated to how much memory your machine has. On a large box, raise it deliberately:

ExecStart=/usr/bin/node --max-old-space-size=4096 /srv/app/server.js

And bound it from the other side with MemoryMax=6G in the unit, so one leaking process cannot take the machine down with it.

Restarts without dropping requests

Handle SIGTERM: stop accepting new connections, finish the ones in flight, then exit. Give systemd time to let that happen with TimeoutStopSec=30. Without it, every deploy drops whatever was in progress.

Practical habits

  • npm ci --omit=dev in deployment, so the lockfile decides and dev dependencies stay off the server
  • Log to stdout and let journald handle it — see log management
  • Never commit secrets; read them from the environment, see managing secrets
  • Pin the Node major version and upgrade deliberately; LTS releases only
  • npm audit in your pipeline, and act on the results

Still stuck? Mail support@novogara.com — an engineer answers, at any hour. Back to the knowledge base