Free tools Windows power users keep installed
One-click scans. No signup required.
iTechGuides is reader-supported. When you buy through links on our site, we may earn an affiliate commission. As an Amazon Associate I earn from qualifying purchases. Learn more
Antihost offers Linux VPS plans with full root access, unlimited traffic on a 1 Gbps port and a 14-day free trial.
The reliable way to run a Node.js program continuously on a Linux VPS is to keep Node in the foreground and let systemd supervise it. A systemd service starts the process at boot, restarts it after qualifying failures, records stdout and stderr in the journal, and gives you consistent commands for deployment and diagnosis. This guide builds a production-oriented unit, deploys it under a dedicated account, verifies the local health endpoint, and explains when PM2 or a reverse proxy belongs in the design.
What a Node.js daemon means under systemd
In traditional Unix terminology, a daemon is a long-running background service. Under systemd, your Node.js program should not detach itself, fork into the background, or implement its own daemon mode. Start it as a normal foreground process and let systemd own the lifecycle. systemd tracks the process, starts it during boot, stops it during shutdown, restarts it according to the unit policy, and sends output to journald.
Quick wins for a faster PC:
Repair Windows errors before they cause bigger problemsFix Now →Fix the driver behind crashes, sound loss and screen glitchesFind Drivers →Clear out junk files and repair common Windows errorsFree Scan →The Linux man-pages project describes this relationship precisely: “A unit configuration file whose name ends in “.service” encodes information about a process controlled and supervised by systemd.” Your Node script becomes that controlled process; the .service file is the declarative contract for how it runs.
Why foreground execution matters
- systemd can associate the service with the actual Node process and detect when it exits.
- Exit codes and signals remain visible to restart policies and diagnostics.
- stdout and stderr flow directly to the journal instead of disappearing into a self-managed log file.
- You avoid two competing supervisors trying to restart or stop the same process.
If an application has a command-line option that self-daemonizes, disable that option for the systemd deployment. A process that forks away from its parent can make status reporting and failure recovery unreliable.
#1 Best Overall
Prerequisites and decisions before writing the unit
Have a provisioned Linux VPS with systemd, a working Node.js installation, the application files, and a port or socket on which the application listens. Distribution-specific package commands and Node locations vary, so discover values on the actual VPS rather than copying a path from another image.
Find the Node executable
Run:
command -v node
Use the absolute path printed by that command in ExecStart. An interactive shell may add Node through a profile, version manager, or custom PATH; systemd does not automatically load those shell settings. The example path /usr/bin/node is only illustrative. If the command prints a different path, use that path exactly.
Choose the application layout
Pick a fixed directory, such as /opt/myapp, and keep the service’s code and assets there. A fixed WorkingDirectory prevents relative paths from changing with the caller’s shell or boot environment. The examples below use:
- Application directory:
/opt/myapp - Entry point:
/opt/myapp/server.js - Service name:
myapp.service - Runtime account and group:
myapp - Environment file:
/etc/myapp/myapp.env
Replace these names with your own values consistently. The account must be created before the service is started, and it should have only the filesystem permissions the application needs.
Select a restart policy deliberately
Restart=on-failure restarts the process when it exits unsuccessfully or is terminated by a qualifying signal, while a clean exit remains stopped. Restart=always also restarts after a clean exit. Choose based on your application’s semantics: a worker that should never remain stopped may justify always; a program that uses a clean exit to signal intentional shutdown may fit on-failure. Add a delay such as RestartSec=5 so an immediate crash does not create a tight restart loop. Repeated restarts are a symptom to investigate, not a reason to hide the underlying exception.
Create a dedicated service account and directory
Do not run a network-facing Node application as root unless a specific, documented requirement makes it unavoidable. A dedicated account limits the damage from an application vulnerability and makes ownership errors easier to identify. Where practical, give the account no interactive shell.
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 reinstallRank #2
sudo install -d -o myapp -g myapp /opt/myapp
Copy or deploy the application into that directory and ensure the account can read the files it needs and write only to intended runtime locations. Keep upload, cache, temporary, and log directories separate when the application requires write access. Do not grant broad write permission to the entire VPS or to system directories.
Separate secrets from the unit file
Put database credentials, API keys, and other deployment-specific values in an environment file instead of embedding them in the unit. This keeps the unit readable as configuration and lets you protect secrets with filesystem permissions.
Create /etc/myapp/myapp.env with one variable assignment per line, for example:
NODE_ENV=production
PORT=3000
DATABASE_URL=replace-with-your-value
Use the syntax your Node application expects. Keep the file owned and readable only by the service account and administrators who need it; do not commit it to source control or make it world-readable. If a secret contains spaces or shell-sensitive characters, follow the environment-file syntax supported by your systemd version and application rather than assuming shell expansion will occur.
What’s actually slowing this PC down?
Pick the symptom - the matching free tool is one click away.
Write the systemd unit
Save the following as myapp.service and install it as /etc/systemd/system/myapp.service:
[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
Change ExecStart to the real path returned by command -v node. Change the account, directory, script, and environment-file paths to match your deployment.
What each directive does
| Directive | Purpose | Operational note |
|---|---|---|
Description |
Human-readable service label. | Appears in status output. |
After=network.target |
Orders startup after the basic network target. | It orders units; it does not prove that a remote database or external network is reachable. |
Type=simple |
Tells systemd the started process is the service. | Keep Node in the foreground. |
User and Group |
Drop privileges for the application process. | The account must be able to read the application and environment file. |
WorkingDirectory |
Sets the process’s current directory. | Use an absolute, stable path. |
ExecStart |
Defines the exact command systemd launches. | Use absolute paths for both Node and the script. |
EnvironmentFile |
Loads deployment variables from a separate file. | Protect the file because it may contain credentials. |
Restart and RestartSec |
Define failure recovery and the delay before a restart. | Investigate recurring crashes instead of masking them. |
StandardOutput and StandardError |
Send application output to journald. | Read it with journalctl. |
WantedBy=multi-user.target |
Connects the service to the normal multi-user boot target. | Enabling the unit makes it start at boot. |
Install, reload, enable, and start the daemon
From the directory containing your unit file, run the deployment sequence:
sudo install -m 0644 myapp.service /etc/systemd/system/myapp.service
sudo systemctl daemon-reload
sudo systemctl enable --now myapp.service
sudo systemctl status myapp.service
Why each command is required
- Install the unit. The file under
/etc/systemd/systemis the administrator-managed service definition. - Reload the manager.
daemon-reloadmakes systemd reread unit files after creation or editing. Without it, the running manager can continue using an older definition. - Enable and start.
enablearranges boot startup;--nowstarts the service immediately. You can runsudo systemctl enable myapp.serviceandsudo systemctl start myapp.serviceseparately when you prefer to stage the change. - Inspect status. Status shows whether the unit is active, the main process ID, recent log lines, and useful failure details.
After every unit edit, repeat sudo systemctl daemon-reload. If you changed only application code, restart the service so the running Node process loads the new files:
sudo systemctl restart myapp.service
Read logs and diagnose startup failures
Follow the service’s journal entries live with:
sudo journalctl -u myapp.service -f
For a bounded recent view, use the same unit selector without -f. Combine journal output with:
sudo systemctl status myapp.service
Look for the first meaningful error, not just the final “failed” state. Common causes include a wrong Node path, a missing script, an unreadable environment file, a port already in use, a syntax or dependency error, and a working directory that does not exist.
Symptom: “Executable path is not absolute” or Node is not found
Run command -v node as an administrator on the VPS and replace the example in ExecStart. Do not rely on node alone or on a shell profile loaded by your interactive user. If Node was installed through a version manager, ensure the service can access that installation; a system-wide absolute path is generally simpler for a system service.
Symptom: the service exits immediately
Read the journal for the application’s exception and check the exit status in systemctl status. Confirm that the entry script exists, dependencies are installed in the expected directory, required variables are present, and the selected port is free. Run the same command manually as the service account from the configured working directory only as a diagnostic; do not convert the production unit into a root-run service merely because a manual test used root.
Rank #4
Symptom: permission denied
Check ownership and mode on every parent directory, the application files, and /etc/myapp/myapp.env. The myapp account needs read access to code and configuration and write access only where the application stores runtime data. A service can fail even when the file itself is readable if one parent directory blocks traversal.
Symptom: repeated restarts
Use the journal to identify the first crash and correct that cause. A restart delay reduces churn but does not repair a bad configuration, missing dependency, failed database connection, or unhandled exception. Temporarily stopping the service while fixing the fault can make logs easier to read:
sudo systemctl stop myapp.service
Start it again after the correction and verify a stable active state.
Verify the process, listener, and health endpoint
A green systemd status is necessary but not sufficient. Verify the complete path in this order:
- Confirm the service is active with
sudo systemctl status myapp.service. - Confirm the Node process is listening on the intended interface and port using the socket inspection tools available on your distribution.
- Call the local health endpoint from the VPS, for example
curl http://127.0.0.1:3000/healthwhen your application exposes that path and port. - Check the response body and HTTP status, not merely whether a TCP connection opened.
- If a reverse proxy is configured, request the public route through that proxy and compare its response with the local result.
Use the actual endpoint, interface, and port implemented by your application. Do not expose a backend listener publicly just to make testing easier.
Put Nginx or another reverse proxy at the edge
For public web traffic, keep Node on an internal port and place Nginx or another reverse proxy at the edge. The proxy can be the public entry point while Node listens on loopback or a private interface. This arrangement reduces the number of exposed ports and gives the edge component responsibility for public HTTP handling.
Best Value
Open only the firewall ports required by the proxy and administration. If the application is intended to be private, bind it to loopback or a private network address rather than all interfaces. Test both layers: the local health endpoint confirms the daemon, while the public request confirms proxy routing and firewall behavior.
Deploy updates without losing lifecycle control
Keep one supervisor responsible for starting, stopping, and restarting the application. A straightforward update flow is:
- Deploy the new application files into the service’s fixed directory.
- Check ownership, dependencies, and environment-file access as the service account.
- If the unit changed, run
sudo systemctl daemon-reload. - Restart with
sudo systemctl restart myapp.service. - Immediately inspect
sudo systemctl status myapp.serviceandsudo journalctl -u myapp.service -f. - Call the local health endpoint, then test the public route through the reverse proxy.
For larger deployments, add a release and rollback strategy around this sequence, but preserve the same principle: systemd should supervise one clearly identified foreground process.
systemd versus PM2
Both systemd and PM2 can keep a Node.js application running, but they solve different operational problems. systemd is the native Linux service manager and does not add a Node-specific runtime dependency. PM2 may be attractive when its Node-focused CLI or clustering features are explicit requirements.
| Concern | systemd | PM2 |
|---|---|---|
| Boot integration | Native integration with Linux boot targets and service state. | Requires PM2’s startup integration and its own saved process configuration. |
| Restart semantics | Declarative policies such as on-failure, always, and restart delays. |
Node-oriented restart controls through the PM2 CLI and configuration. |
| Logging | Direct integration with journald via standard output and error. | PM2 manages its own log workflow unless you integrate it with another collector. |
| Clustering | No Node-specific cluster manager; use your application, a proxy, or another orchestrator. | Provides Node-focused clustering features. |
| Dependencies | Uses the service manager already present on a typical Linux VPS. | Adds a Node-specific supervisor and operational commands. |
| Team operations | Fits teams already using systemctl, journald, and standard Linux incident procedures. | Fits teams standardized on PM2’s CLI and process model. |
Select one owner for restarts. Running PM2 under systemd can be valid when you intentionally use systemd to supervise the PM2 master and PM2 to manage a defined application set, but stacking both casually can produce duplicate restarts, confusing status output, and competing log locations. For one conventional Node service on a Linux VPS, a direct systemd unit is usually the simpler dependency chain.
Do these 3 things before closing this tab:
1Fix the driver behind crashes, sound loss and screen glitches2Clear out junk files and repair common Windows errors3Scan for outdated or missing drivers - takes under a minuteHandle Node child processes correctly
If the daemon launches subprocesses, Node’s child_process.spawn() returns a child object with a process ID and emits an error event when spawning fails. Handle that event so a missing executable, permission problem, or resource failure becomes an actionable log entry.
Do not treat a successful subprocess.kill() call as proof that the child has finished terminating. It means a signal was sent. Use the child’s exit or close events to detect completion and perform cleanup. Decide whether a child failure should cause the main daemon to exit; if it does, systemd can apply the unit’s restart policy to the main process.
Security and ongoing operations checklist
- Run the service under a dedicated non-root account with no interactive shell where practical.
- Use absolute paths in
ExecStartand a fixedWorkingDirectory. - Keep secrets in a protected
EnvironmentFile, not in the unit or source repository. - Bind backend listeners to loopback or a private interface when a reverse proxy is public.
- Open only required firewall ports.
- Patch the operating system and Node.js runtime regularly.
- Monitor journal output and restart frequency; a service that is technically “active” but repeatedly restarting is not healthy.
- Document the service name, application directory, environment-file location, health endpoint, and rollback procedure for the next operator.
Common mistakes to avoid
- Backgrounding Node. A trailing ampersand, daemon library, or fork mode defeats systemd’s process tracking.
- Using a relative Node command.
ExecStart=node server.jsdepends on an environment systemd may not have. - Forgetting daemon-reload. Editing a unit without reloading leaves the manager on the previous definition.
- Starting as root for convenience. Fix ownership and permissions instead.
- Exposing the backend port. Keep the application on an internal interface when a proxy is the public edge.
- Ignoring clean exits. Decide whether an intentional exit should remain stopped or trigger
Restart=always. - Using two restart supervisors accidentally. Choose a clear lifecycle owner and make logs and status checks point to it.
- Checking only systemd status. Confirm the listener, local health response, and public route separately.
Minimal command reference
# Discover the Node path
command -v node
# Prepare the application directory
sudo install -d -o myapp -g myapp /opt/myapp
# Install or replace the unit
sudo install -m 0644 myapp.service /etc/systemd/system/myapp.service
# Apply unit-file changes
sudo systemctl daemon-reload
# Enable at boot and start now
sudo systemctl enable --now myapp.service
# Inspect state and follow logs
sudo systemctl status myapp.service
sudo journalctl -u myapp.service -f
# Restart after an application update
sudo systemctl restart myapp.service
# Stop while diagnosing a failure
sudo systemctl stop myapp.service
With these pieces in place, the VPS has a transparent lifecycle: systemd starts the foreground Node process, applies the configured user and environment, records its output, recovers from qualifying failures, and gives operators a standard path from status to journal to endpoint verification.
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.

