Do these 3 things before closing this tab:
1Fix the driver behind crashes, sound loss and screen glitches2Repair Windows errors before they cause bigger problems3Scan for outdated or missing drivers - takes under a minuteThe reliable way to keep a Node.js application running on a Linux VPS is to leave Node in the foreground and let systemd own its lifecycle. A service unit can start the app at boot, restart it after a failure, provide a fixed working directory and environment, send output to the journal, and run it as a dedicated non-root user. Do not background the process or make the Node program daemonize itself.
Antihost offers Linux VPS plans with full root access, unlimited traffic on a 1 Gbps port and a 14-day free trial.
This guide builds a production-oriented service, enables it at boot, shows how to inspect failures, and explains when a Node-specific supervisor such as PM2 is a better fit.
As an Amazon Associate I earn from qualifying purchases.
Quick wins for a faster PC:
Scan for outdated or missing drivers - takes under a minuteDriver Scan →Repair Windows errors before they cause bigger problemsFix Now →Fix the driver behind crashes, sound loss and screen glitchesFind Drivers →What a Node.js daemon on a VPS actually is
On Linux, “daemon” generally means a long-running process managed independently of an interactive shell. With systemd, your Node.js program does not need special daemon code. It remains a normal foreground process, while systemd supervises it.
The Linux man-pages description is precise: “A unit configuration file whose name ends in “.service” encodes information about a process controlled and supervised by systemd.” That supervision covers boot ordering, starting and stopping, restart policy, process status, and journal integration.
#1 Best Overall
A typical deployment has three layers:
- Node.js: listens on an internal address and port and serves the application.
- systemd: starts Node, restarts it when appropriate, and records standard output and errors.
- Nginx or another reverse proxy: accepts public HTTP or HTTPS traffic and forwards requests to Node on a private or loopback port.
Keeping the application behind a reverse proxy means the public web server, TLS configuration, and firewall exposure can be managed separately from the Node process.
Prerequisites and decisions to make first
You need a Linux VPS with administrative access, a working Node.js installation, your application files, and a port selected for local listening. Distribution-specific package commands and Node executable locations vary, so do not copy a path from another server without checking it.
Confirm Node and the application
From the account that installed Node, find the executable:
command -v node
node --version
npm --version
Use the absolute path printed by command -v node in ExecStart. An interactive shell’s PATH, including paths added by a version manager, is not automatically inherited by systemd.
Run the application manually once from its intended directory. Confirm which command starts it, which port it uses, whether it expects an environment file, and whether it writes files at runtime. A service that only works from your login shell usually has an unstated dependency on the shell’s working directory, environment, or permissions.
Choose a service identity
Use a dedicated account such as myapp, not root. The account should own only the files it must modify and should have no interactive shell where practical. A service account limits the damage from an application vulnerability or an accidental file operation.
Crashes, No Sound, or Screen Glitches?
Random freezes, missing sound and display glitches usually trace back to one bad driver. Find and replace yours safely.Free scan · under a minuteWindows Errors? Fix Them Before They Spread
Repair common Windows errors and clear accumulated junk for a smoother, more stable PC - no reinstall needed.Free scan · no reinstallChoose restart semantics
Restart=on-failure is suitable when a clean exit should remain stopped but crashes should be recovered automatically. Restart=always also restarts after a clean exit. Select the policy that matches your application’s intended behavior; automatic restarts do not fix a program that repeatedly crashes, and a tight restart loop can hide the original error.
Rank #2
Prepare the application directory and environment
Place the release in a fixed directory so systemd does not depend on a developer’s home directory or a temporary checkout. For example:
sudo install -d -o myapp -g myapp /opt/myapp
Copy the application and its production dependencies into /opt/myapp, then ensure the service account can read the code and execute the files it needs. If the application must write uploads, caches, or generated data, give write access only to those specific directories.
Keep secrets out of the unit file. Put values such as database credentials and signing keys in an environment file, for example /etc/myapp/myapp.env. Restrict that file so only root and the service’s group can read it:
Recommended Free Tools
sudo install -o root -g myapp -m 0640 /dev/null /etc/myapp/myapp.env
Add one variable per line, using the syntax your application expects:
NODE_ENV=production
PORT=3000
DATABASE_URL=replace-with-your-secret
Do not place shell commands, exports, or unquoted assumptions in this file. Test that the service account can read it, but do not make it world-readable.
Write the systemd service unit
Create /etc/systemd/system/myapp.service. The following unit is a practical baseline:
[Unit]
Description=My Node.js daemon
After=network.target
[Service]
Type=simple
User=myapp
Group=myapp
WorkingDirectory=/opt/myapp
ExecStart=/usr/bin/node /opt/myapp/server.js
EnvironmentFile=/etc/myapp/myapp.env
Restart=on-failure
RestartSec=5
StandardOutput=journal
StandardError=journal
[Install]
WantedBy=multi-user.target
Replace /usr/bin/node with the exact path returned by command -v node. It is an example, not a universal location. Replace the script path and service account with your own values.
What each directive does
| Directive | Purpose | Operational note |
|---|---|---|
Description |
Human-readable name shown by status tools. | Use a name that identifies the application and environment. |
After=network.target |
Starts the unit after basic network initialization. | This orders startup; it does not guarantee that a remote database is ready. |
Type=simple |
Tells systemd that the process started by ExecStart is the main process. |
Keep Node in the foreground. |
User and Group |
Drop privileges to the dedicated account. | Check ownership of code, logs, sockets, and writable directories. |
WorkingDirectory |
Sets the process’s current directory. | Relative paths in the application resolve from this location. |
ExecStart |
Defines the exact executable and arguments. | Use absolute paths; systemd does not use your interactive shell setup. |
EnvironmentFile |
Loads runtime variables separately from the unit. | Protect the file because it may contain secrets. |
Restart and RestartSec |
Control recovery after process exit. | Investigate repeated crashes instead of masking them with restarts. |
StandardOutput and StandardError |
Send normal output and errors to journald. | Read them with journalctl. |
WantedBy=multi-user.target |
Allows the unit to be enabled for normal multi-user boot. | Enabling affects future boots; it does not by itself start a currently stopped service. |
Do not add a shell wrapper unless you need one for a documented reason. A wrapper can obscure exit codes, signals, and the actual process systemd is supervising.
Install, enable, and start the daemon
After saving the unit, tell systemd to reread unit files and start the service:
- Install the unit.
sudo install -m 0644 myapp.service /etc/systemd/system/myapp.service - Reload unit definitions.
sudo systemctl daemon-reloadRun this after every edit to a unit file. It does not restart a process that is already running.
- Enable and start in one command.
sudo systemctl enable --now myapp.serviceenableschedules startup at boot, while--nowstarts it immediately. - Inspect the current state.
sudo systemctl status myapp.service - Follow live logs.
sudo journalctl -u myapp.service -f
If you change only application code, a restart is normally sufficient:
sudo systemctl restart myapp.service
If you change the unit, run daemon-reload before restarting. To stop the service without removing boot enablement, use sudo systemctl stop myapp.service. To prevent it from starting at future boots, use sudo systemctl disable myapp.service.
Verify that the service is really usable
A green “active (running)” state proves that systemd has a live process; it does not prove that the application is accepting useful requests. Verify each layer in order.
- Check the status output for the main PID, recent exit code, and startup messages.
- Read recent logs rather than only following new ones:
sudo journalctl -u myapp.service -n 100 --no-pager - Confirm the process is listening on the intended interface and port with the socket inspection tool available on your distribution, such as:
sudo ss -ltnp - Call the local health endpoint from the VPS, for example:
curl --fail http://127.0.0.1:3000/health - If a reverse proxy is configured, test the public hostname separately. A successful local request does not rule out proxy routing, TLS, DNS, or firewall errors.
Bind an internal backend to loopback or a private interface when the proxy is the public entry point. Exposing the application port directly can bypass proxy access controls and TLS.
Diagnose common startup and runtime failures
“Executable path is not absolute” or Node cannot be found
Run command -v node and copy the resulting absolute path into ExecStart. A path that works in your shell may come from a version manager or profile script unavailable to systemd. After editing, run daemon-reload and restart.
Rank #4
The service exits immediately
Inspect the journal for syntax errors, missing modules, invalid environment variables, and permission failures:
sudo journalctl -u myapp.service -b --no-pager
Run the same Node command manually as the service account from WorkingDirectory. This often exposes files that are readable by your login account but not by myapp.
The service restarts continuously
Look at the first failure in the journal, not only the latest restart. Common causes include an occupied port, a failed database connection, an invalid configuration value, or an application that exits intentionally after a one-time task. Fix the cause, then restart. Do not stack another process manager on top of systemd merely to hide a restart loop.
Environment variables are missing
Confirm the EnvironmentFile path, spelling, permissions, and variable syntax. Remember that systemd does not source .bashrc, .profile, or shell-specific Node version-manager setup.
Local health works but public traffic fails
Check the reverse proxy’s upstream address and port, its own logs, firewall rules, DNS, and TLS configuration. Ensure Node is listening on an address reachable from the proxy. If Node is bound only to loopback, the proxy must run on the same VPS.
Use journald as the first diagnostic record
With the unit above, anything written to standard output or standard error is available through the system journal. Useful commands include:
sudo journalctl -u myapp.service -f
sudo journalctl -u myapp.service --since "1 hour ago"
sudo journalctl -u myapp.service -b
sudo journalctl -u myapp.service -p err
Include timestamps, request identifiers, and meaningful error context in application logs, but never print passwords, tokens, or full connection strings. Monitor restart frequency as well as log volume: a service that remains “active” only because it is repeatedly restarted is not healthy.
Best Value
Put Nginx or another proxy at the edge
For a public web application, keep Node on an internal port such as 3000 and let the edge proxy listen on ports 80 and 443. The proxy can terminate HTTPS, select the correct virtual host, serve static assets, and forward dynamic requests to Node.
Free tools Windows power users keep installed
One-click scans. No signup required.
The exact proxy configuration depends on your distribution and application, but the traffic path should be explicit: client to proxy, proxy to the Node health endpoint or application port. Open only the ports required by that design in the VPS firewall. If no public client needs direct access to port 3000, do not expose it.
systemd versus PM2
Both can keep a Node application running, but they solve different operational problems. systemd is the native Linux service manager and adds no Node-specific runtime dependency. PM2 can be attractive when Node-specific clustering and a Node-oriented command-line workflow are requirements.
| Concern | systemd | PM2 |
|---|---|---|
| Boot integration | Native Linux targets and dependencies. | Requires PM2 startup integration with systemd or another boot mechanism. |
| Restart semantics | Unit-level policies such as on-failure, delays, and dependency ordering. |
Node-focused restart controls and CLI-managed process definitions. |
| Logs | Integrated with journald and standard Linux log tooling. | PM2’s own log and process-management workflow. |
| Clustering | Can run multiple separately configured instances, but provides no Node-specific cluster command. | Convenient Node-oriented clustering features. |
| Dependencies | Available as part of the operating system. | Adds a Node package and its operational lifecycle. |
| Team fit | Best when administrators already standardize on systemd. | Useful when developers need PM2’s CLI and clustering model. |
Choose one supervisor as the owner of start, stop, and restart operations. Running PM2 under a systemd unit that also restarts the same application can create overlapping policies, confusing status output, and restart storms. If you choose PM2, let it own the Node process and use systemd only for the clearly defined PM2 service; if you choose systemd directly, do not add PM2 just for redundancy.
Handle child processes correctly in Node.js
Some daemons launch workers, encoders, command-line utilities, or other subprocesses. Node’s child_process.spawn() returns a child object with a process ID and emits an error when spawning fails.
What’s actually slowing this PC down?
Pick the symptom - the matching free tool is one click away.
import { spawn } from 'node:child_process';
const child = spawn('/usr/bin/my-worker', ['--job', '42'], {
stdio: 'inherit'
});
child.on('error', (error) => {
console.error('Could not start worker:', error);
});
child.on('close', (code, signal) => {
console.log('Worker finished', { code, signal });
});
A successful child.kill() means that a signal was sent; it does not mean the child has finished terminating. Use the child’s exit or close event to perform completion handling, release resources, or decide whether a job should be retried. Make sure the parent handles termination signals so children do not remain behind during a service stop.
Security and maintenance checklist
- Run the service as a dedicated non-root account with no interactive shell where practical.
- Keep the unit file, application directory, and environment file owned and readable only as required.
- Store secrets in the protected environment file or another secret-management mechanism, never in a publicly readable unit or source repository.
- Bind backend listeners to loopback or a private interface when a reverse proxy is the public entry point.
- Open only required firewall ports.
- Patch the operating system and Node.js runtime on a planned schedule.
- Review journal output and restart frequency after every deployment.
- Keep releases recoverable so you can restore the previous application version if a new one fails.
A repeatable deployment sequence
- Copy the new release into a versioned or otherwise recoverable directory.
- Install production dependencies and verify ownership for the service account.
- Update symlinks or configuration only after the new files are complete.
- Run
sudo systemctl restart myapp.service. - Check
systemctl status, inspect the journal, and call the local health endpoint. - Test the public route through the reverse proxy.
- If the health check fails, stop the rollout and restore the last known-good release before investigating further.
Final operating model
A robust Node.js daemon on a VPS is intentionally uneventful: one foreground process, one clearly owned systemd unit, one dedicated account, an explicit executable path, protected configuration, and logs you can inspect without guessing. Once the local health check and public proxy path both work, systemd provides the boot persistence and failure recovery that an interactive shell cannot.
Quick Recap
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.




