DriversRecommendedOutdated drivers can make a good PC feel brokenScan driver issues before chasing fixes manually.Scan NowHalloween DealsAmazon USHalloween week deals: useful picks before month-endAmazon US: current deals, useful picks and tech finds.Check DealsClean PCRecommendedOne scan can reveal what keeps slowing WindowsLook for cleanup and repair opportunities.Run Scan×
FREEDOM251

Freedom251 › Blog › DevOps

How to Run a Node.js TCP Server with PM2 and Nginx on a VPS

By Freedom251 Team · · 11 min read

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

The dependable layout is Node.js listening on a private loopback port, PM2 supervising the process and restoring it after reboot, and Nginx accepting the public TCP port in a stream {} block. For example, let Nginx listen on 0.0.0.0:4000 and forward to Node on 127.0.0.1:4001. This keeps the application port off the public interface, survives SSH disconnects and reboots, and gives you separate places to handle process recovery, firewall exposure, connection timeouts and (if required) TLS.

What the finished deployment looks like

A raw TCP service is not an HTTP application. Clients open a TCP connection, exchange bytes according to your application protocol, and may keep the connection idle for a long time. Node’s asynchronous node:net API and net.createServer() provide the socket server; PM2 starts and supervises that Node process; Nginx’s stream proxy accepts connections on the VPS’s public address and relays them to Node.

As an Amazon Associate I earn from qualifying purchases.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
Layer Example setting Responsibility
Client-facing edge Nginx listen 0.0.0.0:4000 Accept public TCP connections, optionally terminate TLS, and proxy bytes.
Application endpoint Node 127.0.0.1:4001 Implement framing, authentication, commands, responses and connection state.
Process supervisor PM2 process named tcp-server Start, restart, log and resurrect the application.
Boot integration pm2 startup plus pm2 save Have the saved process list return after a machine restart.

The two ports are deliberately different. Binding Nginx and Node to the same address and port causes an “address already in use” failure. Node can bind to a private interface while Nginx owns the public port.

Prerequisites and design decisions

  • A VPS with root or sudo access, Node.js and npm installed, and a DNS name if clients will use a hostname.
  • A TCP protocol definition: decide how messages are delimited or framed, whether a connection is long-lived, and what an idle connection means.
  • A public TCP port to expose and a separate private Node port. The examples use 4000 publicly and 4001 internally.
  • An application directory such as /srv/my-tcp-app. Use a dedicated unprivileged service account and restrictive ownership and permissions rather than running the application as root.
  • A firewall policy that permits the Nginx port only where needed and does not expose the loopback-only Node port.

Choose the internal bind address explicitly. 127.0.0.1 is appropriate when Nginx runs on the same VPS. If Nginx is on another private host, use the VPS’s private interface address and restrict that network with firewall rules; do not casually bind a service to every interface.

#1 Best Overall
Sale
TOZO A1 Wireless Earbuds Bluetooth 5.3 Light Weight in Ear Headphones | IPX5 Waterproof,2 Mics for AI Calls,Immersive Premium Sound Bass Headset with Charging Case,32 EQ via App Black
  • [Ultra-Lightweight Ear Buds Designed for Small Ears] Each earbud weighs only 3.7g and features a compact, ergonomic in-ear design made especially for small ears. Secure, low-profile, and comfortable for workouts, all-day wear or overnight sleeping.The ultra-slim profile sits flush in your ear with no hard edges poking or rubbing, so you can even sleep on your side without discomfort.
  • [Immersive Stereo Sound with TOZO OrigX Technology] TOZO OrigX tuning delivers clear vocals, balanced mids, and natural stereo sound for music, podcasts, and videos.
  • [Long Battery Life for Daily Use] Get up to 7 hours of playtime on a single charge, with up to 32 hours total using the charging case—ideal for workdays, commuting, and extended listening sessions.
  • [Bluetooth 5.3 & Stable Connection] Bluetooth 5.3 provides fast pairing, stable wireless performance, and reduced dropouts as you move around home or office.
  • [Deep Bass with Clear Vocals] High-performance drivers produce punchy bass while keeping vocals clean and detailed for everyday listening.

Step 1: Create a minimal asynchronous TCP server

Create /srv/my-tcp-app/server.js with a small server that makes the host and port configurable. The echo behavior below is only a connectivity test; replace it with your real protocol.

import net from 'node:net';

const host = process.env.HOST || '127.0.0.1';
const port = Number(process.env.PORT || 4001);

const server = net.createServer((socket) => {
  socket.setEncoding('utf8');
  socket.on('data', (chunk) => {
    // Replace with the application protocol.
    socket.write(chunk);
  });
  socket.on('error', (err) => console.error('socket error', err));
});

server.on('error', (err) => {
  console.error('server error', err);
  process.exitCode = 1;
});

server.listen(port, host, () => {
  console.log(`TCP server listening on ${host}:${port}`);
});

The node:net module is an asynchronous interface for stream-based TCP or interprocess-communication servers and clients. A TCP stream has no inherent message boundaries: one data event can contain part of a message, several messages, or a combination. A production protocol therefore needs explicit delimiters, a length prefix, or another framing rule. It also needs decisions about authentication, maximum input size, backpressure and what to do when a peer disappears.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

The error handlers matter operationally. A socket error should not bring down unrelated connections, while a server-level error (for example, a port collision) must be visible to PM2 and the system logs. Setting process.exitCode lets the process terminate with a failure status so PM2 can apply its restart policy.

Run the server directly once, before adding a proxy, to prove that the application itself starts:

cd /srv/my-tcp-app
HOST=127.0.0.1 PORT=4001 node server.js

Leave it running in one terminal and test from another local shell with a TCP client available on your system. A successful connection to 127.0.0.1:4001 should receive the bytes you send because this example echoes them. Stop the foreground process with Ctrl+C before handing it to PM2.

Step 2: Install PM2 and run the service

Install PM2 globally, start the script from its application directory, and inspect both status and logs:

What’s actually slowing this PC down?

Pick the symptom - the matching free tool is one click away.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
Rank #2
Sale
Smart Watch for Men Women Compatible with iPhone Samsung Android Phone 1.69 inch Full Touch Screen IP68 Waterproof Bluetooh Fitness Tracker Heart Rate/Sleep Monitor (Black)
  • 【IP68 Waterproof Multifunctinal Smart Watch】Torjalph fitness activity tracker smart watch is IP68 waterproof technology ,you can wear it when swimming,handwashing,it's rain-proof which means that you perform multiple exercise modes outside without worrying about sweat or rain corroding your fitness smart watch,and this watch equipped with a 230 mAh large capacity battery magnetic charging ,and this sports activity tracker watch can be used 5-7 days If fully charged.
  • 【Larger 1.83'' Full Screen Watch & Multi-dial Selection】1.83 inch full HD color touch screen smart watch for men / women provides excellent 240*284 resolution image quality ,This Bluetooth smart watch offers a better touch experience and remarkable immersion and ,more than 200 watch faces for you to choose from App"Da Fit ", you can change the dial style according to your mood to wear keeping the world on your wrist
  • 【Accurate Activity Tracker Smart Watch 】sports watch activity tracker uses sweat and dust resistant breathable silicone strap, our Fitness Tracker Smart Watch for Android iOS Phones supports 12 sports modes,walking,running,bicycle,swimming,badminton,basketball,football,rope skipping. This bluetooth calls smart watch for men women is lightweight and stylish, bringing more convenient and relaxing sports experience while also providing more protection.
  • 【Your 24 Hour Personal Daily Health Monitor】Fitness watch for women men full touch screen IP67 waterproof bluetooth smartwatch for Android iOS Phones with heart rate blood pressure monitor multifunction Andriod smart watch with steps counter sleep monitor sports fitness watch, monitoring blood oxygen saturation allows you to understand your physical condition in real time to track fitness progress ,to know your health status in time.
  • 【More Functions Bluetooth Call Smart Watch】This fitness activity smart watch full screen for iPhone and Android phones includes a bunch of new features including:The bluetooth smart watch can hang up the phone, receive and display WhatsAPP /Text /Facebook /Twitter /Messenger reminder.Support sedentary reminder, alarm clock, stopwatch, timer,weather information,music control, remote camera control compatible with Android 5.0 and above or iOS 9.0 and above smartphones.
npm install --global pm2
cd /srv/my-tcp-app
pm2 start server.js --name tcp-server
pm2 status
pm2 logs tcp-server

PM2 keeps the process detached from your SSH session, restarts it when it exits according to its process-management settings, and provides commands for inspection and lifecycle control. Useful day-to-day commands include:

pm2 restart tcp-server
pm2 reload tcp-server
pm2 stop tcp-server
pm2 delete tcp-server
pm2 logs tcp-server
pm2 status

Use restart when a full stop/start is acceptable. reload is useful for a service that can tolerate workers being replaced, but a raw TCP application with long-lived sessions must define what happens to existing connections during a reload. PM2’s cluster mode can distribute TCP connections, yet it does not automatically make application state shared or ordering-safe.

Use an ecosystem file for repeatable settings

Command-line options are convenient for a first launch. An ecosystem file records the working directory, process name, execution mode and environment in one reviewable configuration. PM2 expects the filename to end in .config.js:

module.exports = {
  apps: [{
    name: 'tcp-server',
    script: './server.js',
    cwd: '/srv/my-tcp-app',
    instances: 1,
    exec_mode: 'fork',
    env_production: {
      NODE_ENV: 'production',
      HOST: '127.0.0.1',
      PORT: 4001
    }
  }]
};

Start it with the named environment:

cd /srv/my-tcp-app
pm2 start ecosystem.config.js --env production

The ecosystem format also supports the script path, current working directory, interpreter, arguments and environment values. Keep secrets out of a world-readable configuration file; provide them through a protected mechanism appropriate to your operating system and deployment process.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Step 3: Make PM2 return after a reboot

PM2’s in-memory process list is not enough after a VPS restart. Generate the boot integration, execute the exact command PM2 prints, then save the current list:

pm2 startup
# Run the exact sudo command printed by PM2
pm2 save

pm2 startup detects the service-manager integration and prints a command tailored to the current user and Node installation. The generated startup hook restores the list saved by pm2 save. If you add, remove or rename processes later, run pm2 save again or the old list will be resurrected.

Rank #3
New Bee Guitar Cable 10ft Electric Instrument Bass AMP Cord | 6.35mm Jack, Durable Design, Stage and Home Studio Use, Low Noise Shielded Electric Cord for Bass, Keyboard, Amp, Mixer
  • [Low Noise High Fidelity] New bee guitar cord with 22AWG oxygen-free-copper center conductors provides enhanced signal transfer, the PVC insulation material and the high-density stranded copper double shield provide noise rejection for quiet operation
  • [Ideal for Choice] New bee 1/4 straight and right angle metal connectors, ensures a secure connection in any instrument, pedal, or amp jack. L shape great fro on stage guitar stands
  • [Wide Compatible] Suitable for mixer, power amplifier, microphone, effector, decoder, equalizer, electoronic organ, electric guitar, electric bass and other electronic equipments. Widely used in the installation of smart home, karaoke speaker, professional stage audio, etc
  • [Easy Disassemble] Making incidental repairs quick and easy! Very nice, flexible cable
  • [Dont Hesite to Order] 24 hours customer services and professional technology team are standing by

Node installed through NVM deserves special attention. Its executable path can differ between interactive shells and the boot environment. If you change Node versions or the NVM installation path, rerun pm2 startup, execute the newly printed command, and save the list again. After a reboot, verify the result rather than assuming it worked:

pm2 status
pm2 logs tcp-server
sudo systemctl status pm2-<user>

Replace <user> with the account that owns the PM2 process. The systemd unit name can vary with the account and distribution, so use the exact name shown by PM2 during setup.

Free tools Windows power users keep installed

One-click scans. No signup required.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Step 4: Configure Nginx for raw TCP with the stream module

HTTP reverse-proxy syntax cannot proxy an arbitrary TCP protocol. A location block inside http {} is for HTTP requests; raw TCP belongs in Nginx’s top-level stream {} context. The installed Nginx build must include stream support (enabled at build time with --with-stream), and the stream proxy module supplies proxy_pass.

Check the build options before editing configuration:

nginx -V 2>&1

Look for stream support in the configure arguments or in the modules supplied by your distribution. If the package lacks it, install a package/build that provides the stream core and proxy modules before proceeding. Do not place a stream block inside http; both are top-level contexts.

Minimal single-backend configuration

Add this block in the main Nginx configuration, commonly /etc/nginx/nginx.conf, at top level:

Rank #4
BSIMB 15.6" Digital Family Calendar & Chore Chart, No Subscription Fees
  • Digital Family Calendar: BSIMB digital calendar wall touch screen puts an end to your chaotic life. Designed for busy families, the digital calendar keeps you organised and in step with your family. Assign different colours and tasks to each family member to make family scheduling easier and more intuitive.
  • Easy Setup and Auto-Sync: Plug in, connect to WLAN and auto-syn your electric calendar in minutes. Digital wall calendar compatible with Google calendar, iCloud calendar, Outlook calendar, Cozi calendar and Yahoo calendar. Add events directly on the device or through the ecalendar app. Electronic calendar stays current whether you're at work or traveling—no manual updates needed.
  • All-In-One Home Assistant: Smart calendar builds habits and encourage independence with personalised chores. Display daily meal plan to reduce the daily ‘What's for dinner?’ stress. To-do lists keep track of everything you want. Turning chores into valuable achievements with stars and rewards, making life easier and more enjoyable.
  • Manage Anytime Anywhere: BSIMB electronic calendar for family stay connected with the powerful ecalendar app. Access and manage your digital wall calendar anytime, anywhere with free features like stars & rewards, photo screensaver, chore charts, meals plans, to-do lists etc. Electronic planner paid features like Magic Import tailor advanced tools for users with special needs.
  • Stay in Touch with Loved Ones: Share photos or videos from your phone to electronic wall calendar with app, instantly update your life moment, and keep in touch with your family, especially those who are far away; you can also surprise your smart calendar digital wall by sending him a wish card; it's also great for keeping tech-illiterate elders in touch with the youngsters.
stream {
    server {
        listen 0.0.0.0:4000;
        proxy_connect_timeout 5s;
        proxy_timeout 1h;
        proxy_pass 127.0.0.1:4001;
    }
}

listen is the public address and port Nginx accepts. proxy_pass is the private endpoint to which Nginx opens a connection. The proxy does not use an HTTP scheme; write an address and port only.

The one-hour proxy_timeout is an example, not a universal protocol setting. It controls how long Nginx may wait between successive read or write operations on the proxied connection. Choose it from the real idle-session requirement: a chatty protocol may need a shorter value, while a device connection that legitimately sends no data for hours needs a longer value or an application-level heartbeat. TCP keepalive and protocol heartbeats are separate decisions and should be designed deliberately.

Validate, reload and confirm both listeners

sudo nginx -t
sudo systemctl reload nginx
sudo ss -ltnp | grep -E ':4000|:4001'

Run the syntax test before every reload. The socket listing should show Nginx listening on the public port and Node listening on the private port. If Node is bound to loopback, remote clients should not be able to connect directly to 4001; they should use the Nginx port 4000.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Do not configure Nginx to listen on 0.0.0.0:4000 while Node also listens on that same address and port. Change Node’s PORT to 4001 (as in the ecosystem file), restart PM2, and keep Nginx on 4000.

Firewall, privileges and TLS

  • Allow the public Nginx port through the VPS firewall only for the networks that need it. Keep 4001 closed externally; loopback binding provides an additional boundary.
  • Run the Node process as a dedicated unprivileged account. Give that account ownership only of the application files and writable directories it actually needs.
  • Review Nginx and PM2 logs without granting broad read access to credentials or private protocol data.
  • If the protocol requires encryption, decide where TLS terminates. Nginx has a separate stream SSL module that must also be present in the build. Certificates, protocol versions, client-certificate policy and whether Nginx should pass through end-to-end TLS are application requirements; a plain stream proxy does not add encryption by itself.

When TLS terminates at Nginx, the upstream connection to Node is normally a separate hop. Protect that hop with network isolation or upstream encryption if it crosses an untrusted network. When Nginx passes encrypted bytes through, Node or the ultimate protocol endpoint performs the TLS handshake instead.

Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Scaling beyond one Node process

There are two distinct scaling layers. Nginx can select among several backend sockets, or PM2 can create several Node workers and distribute incoming TCP connections. They solve different placement problems and can also be combined, but each adds connection-state considerations.

Option A: Nginx stream upstreams

stream {
    upstream tcp_backends {
        least_conn;
        server 127.0.0.1:4001;
        server 127.0.0.1:4002;
    }

    server {
        listen 0.0.0.0:4000;
        proxy_pass tcp_backends;
        proxy_timeout 1h;
    }
}

The stream upstream group names two backend endpoints and the server block references that group with proxy_pass. The least_conn choice favors the backend with fewer active connections. Other upstream features can include backup behavior, but selection does not make a stateful application share its memory. If a client must return to the same worker, design stickiness explicitly or move session state to an external store.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Option B: PM2 cluster mode

pm2 start server.js --name tcp-server -i max

PM2 cluster mode starts multiple Node processes and can distribute TCP connections among them. Confirm that the application is safe for multiple workers before using it:

  • In-memory connection maps exist separately in each worker.
  • Message ordering is only guaranteed within the connection and worker handling it, not across workers.
  • Shared files, databases, queues and rate limits need concurrency-safe coordination.
  • Worker replacement can interrupt long-lived sessions unless the protocol and client support reconnecting.

Use a single fork first when simplicity and predictable connection ownership matter. Add Nginx upstreams when you need explicit backend endpoints or want Nginx to balance separate services. Add PM2 cluster mode when the application itself benefits from several Node workers and has a sound state-sharing strategy.

Operations after deployment

Keep four observations separate when diagnosing an incident:

  • PM2 status and logs: confirm that the process is online, not repeatedly restarting, and listening on the expected port.
  • Nginx error log: identify failed upstream connects, timeouts and configuration-level stream errors.
  • Systemd status: verify that the PM2 boot service ran under the intended account after reboot.
  • Socket state: use ss -ltnp to see which process owns 4000 and 4001 and whether the expected listeners exist.

These checks tell you where a failure occurs without confusing an application protocol problem with a proxy or boot problem. Record configuration changes in the same deployment process as the application, and run sudo nginx -t before reloading Nginx.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Common failures and precise fixes

Symptom Likely cause Fix
Nginx reports “address already in use”. Nginx and Node, or two Nginx servers, own the same address and port. Keep Nginx on 4000 and Node on 4001; inspect ownership with ss -ltnp, then restart the incorrectly bound process.
Local connection to 4001 is refused. Node is stopped, crashed, bound to another host/port, or PM2 loaded stale environment values. Run pm2 status, pm2 logs tcp-server, check the ecosystem file, and confirm the listener with ss -ltnp.
Nginx accepts the public connection but cannot reach Node. proxy_pass points at the wrong private port, Node is not listening, or a firewall policy blocks a non-loopback upstream. Test the private endpoint locally, correct proxy_pass, and reload only after nginx -t succeeds.
Configuration test says “stream” is unknown. The installed Nginx build lacks the stream module. Install or build a package with stream support; verify with nginx -V.
Connections drop after a period of silence. proxy_timeout is shorter than the protocol’s legitimate idle interval, or an intermediate firewall/NAT expires the flow. Set a timeout that matches the protocol and add an application heartbeat when appropriate. Do not blindly choose an unlimited timeout.
The service works until reboot, then disappears. The PM2 process list was never saved, the startup command was not executed, or an NVM path changed. Run pm2 startup, execute its printed sudo command, run pm2 save, reboot during a maintenance window, and check PM2/systemd status.
Clustered workers behave inconsistently. State is held in worker memory, ordering assumptions span workers, or clients require stickiness. Use one worker, add an external state store, design sticky routing, or make reconnect and ordering behavior explicit before scaling out.

Deployment checklist

  1. Define the TCP framing, authentication, idle behavior and reconnect policy.
  2. Set Node to an explicit private host and port, such as 127.0.0.1:4001.
  3. Run the server directly and verify the application protocol locally.
  4. Start it with PM2, inspect pm2 status and pm2 logs tcp-server, and record the ecosystem configuration.
  5. Generate PM2 boot integration with pm2 startup, execute the printed command and run pm2 save.
  6. Confirm Nginx has stream support and place a top-level stream {} block, not an HTTP location.
  7. Expose only the Nginx port, proxy it to the private Node port, set protocol-appropriate timeouts, and decide whether TLS belongs at Nginx or in the application.
  8. Run sudo nginx -t, reload Nginx, inspect listeners, and test through the public port.
  9. Only then consider Nginx upstreams or PM2 cluster mode, after reviewing connection state and worker behavior.

This arrangement keeps the public edge, process lifecycle and TCP application concerns separate. That separation is what allows an SSH logout, a Node crash or a VPS reboot to be handled without exposing the application port or pretending that an HTTP proxy can understand a raw stream.

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.

DevOpsNGINXNode.jsPM2TCP serverVPS

Leave a Reply

Your email address will not be published. Required fields are marked *

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Recommended PC Tool
Recommended PC Tool
Windows Errors? Fix Them Before They SpreadFree repair scan
Crashes, No Sound, or Screen Glitches?Free driver scan

Two free Windows tools

One Free Minute Could Fix That PC

Before you go - each of these free tools takes about a minute and tackles what quietly slows a Windows PC down.

Special offer. View Outbyte info, uninstall instructions, EULA, and Privacy Policy.