E-NO
Node.js troubleshooting 8 Min Read

Node.js Troubleshooting with Practical Examples: A Hands-On Guide for Developers and DevOps Teams

calendar_today Published: 2026-09-27
update Last Updated: 2026-09-27
analytics SEO Efficiency: 100%
Technical guide illustration for Node.js Troubleshooting with Practical Examples: A Hands-On Guide for Developers and DevOps Teams.

Learn how to diagnose and fix common Node.js issues using practical commands, log analysis, and step-by-step recovery workflows. This guide covers version checks, configuration safety, performance diagnostics, failure modes, and an operational checklist to keep your applications healthy.

Intro

Node.js powers a vast ecosystem of web applications, APIs, and microservices. When something goes wrong, a structured approach to troubleshooting can save hours of guesswork. This guide provides practical examples and commands to help you identify, diagnose, and resolve common Node.js issues, from configuration mistakes to memory leaks. By following the steps outlined here, you can build a repeatable process that improves the reliability of your applications and reduces downtime.

This guide is written for developers and DevOps engineers who need to keep Node.js services running smoothly. Each section includes concrete commands, expected outputs, and explanations. Whether you are responding to an incident or performing routine maintenance, the techniques below will help you work faster and with greater confidence.

Version and Environment Inventory

Before changing anything, gather accurate information about your Node.js environment. This includes the Node.js version, npm version, operating system, architecture, and relevant package versions. Knowing these details helps you reproduce issues and avoid introducing new ones.

Start by checking the Node.js and npm versions:

node -v
npm -v

Expected output might look like:

v18.17.1
9.6.7

If you use a version manager like nvm, list installed versions and the current one:

nvm ls
nvm current

For the operating system and architecture, use:

uname -a

On a typical Linux system, you might see:

Linux hostname 5.15.0-91-generic #101-Ubuntu SMP Tue Nov 14 13:30:08 UTC 2023 x86_64 x86_64 x86_64 GNU/Linux

Check the project's dependencies by examining package.json and package-lock.json. Use npm ls to list installed packages and their versions:

npm ls --depth=0

Expected output might be:

[email protected] /path/to/my-app
├── [email protected]
├── [email protected]
└── [email protected]

Understanding your environment also means knowing the network topology: which services your Node.js application talks to (databases, caches, external APIs), and how they are configured via environment variables. For example, if your application connects to a PostgreSQL database and a Redis cache, note their hostnames, ports, and credentials (without exposing secrets). Collect these details before proceeding.

You should also record the process manager in use (PM2, systemd, Docker, Kubernetes) and how the application is started. This information is essential when you need to restart or inspect the service later. Keep this inventory in a shared document or runbook so the whole team can access it during an incident.

Safe Configuration Path

Configuration changes are a common source of Node.js issues. To make changes safely, follow these practices:

  • Use environment variables for sensitive or environment-specific settings. Avoid hardcoding values in code. For example, read the port from process.env.PORT:
const port = process.env.PORT || 3000;
  • Keep configuration in a single place like a .env file loaded with the dotenv package. This simplifies management and reduces mistakes. Example .env file:
PORT=3000
DB_URL=mongodb://localhost:27017/myapp
REDIS_URL=redis://localhost:6379
  • Validate configuration at startup. Use a library like joi or zod to ensure all required variables are present and correct. Here is a simple validation example using zod:
const { z } = require('zod');
const envSchema = z.object({
  PORT: z.string().default('3000'),
  DB_URL: z.string().url(),
});
const env = envSchema.safeParse(process.env);
if (!env.success) {
  console.error('Invalid environment variables:', env.error.issues);
  process.exit(1);
}
  • Make changes incrementally. Change one variable at a time and test after each change. For example, if you need to change the database connection string, do so and then run a quick connectivity test:
node -e "require('mongodb').MongoClient.connect(process.env.DB_URL, (err, client) => { if(err) console.error(err); else console.log('Connected'); client.close(); })"
  • Use configuration management tools like Ansible or Terraform for consistent deployment across environments. These tools help you track changes and roll back if needed.

Always document configuration changes in a change log or version control commit message for traceability. A good commit message might be: "Increase DB pool size to 20 to handle peak load". This makes it easier to identify what changed when an issue arises.

Verification and Diagnostics

After making changes or when investigating an issue, you need to verify the system's behavior and diagnose root causes. Here are practical techniques:

Check Process Status

Is your Node.js process running? Use ps to see processes:

ps aux | grep node

The output will show details like the process ID (PID), CPU, and memory usage. For example:

user 12345 0.5 1.2 123456 78901 ? Ssl 10:00 0:03 node server.js

If the process is not listed, it may have crashed. Look for crash logs or check the process manager status:

pm2 status
# or
systemctl status my-node-app

Examine Logs

Logs are the first place to check for errors. If your application logs to stdout/stderr, you can redirect them to a file:

node server.js > app.log 2>&1

Then view the log with tail or less:

tail -f app.log

Look for stack traces, error codes, and warning messages. For example, an unhandled promise rejection might log:

(node:12345) UnhandledPromiseRejectionWarning: Error: connect ECONNREFUSED 127.0.0.1:27017

This indicates the Node.js app could not connect to MongoDB at the default port. Common log patterns include:

  • EADDRINUSE: Port already in use.
  • ECONNREFUSED: Connection refused by target service.
  • ETIMEDOUT: Connection timed out.
  • SyntaxError: Code parsing error.

Use structured logging libraries like pino or winston to output JSON logs that are easier to parse and search. For example, with pino:

const pino = require('pino');
const logger = pino();
logger.info({ user: 'alice' }, 'User logged in');

Use Debugging Tools

Node.js has a built-in debugger. Start your application in debug mode:

node inspect server.js

Then you can set breakpoints and step through code. For more advanced debugging, use the Chrome DevTools protocol by running:

node --inspect server.js

Then open chrome://inspect in Chrome to attach. This gives you a full debugging interface with breakpoints, watch expressions, and call stack inspection.

Performance Diagnostics

To diagnose performance issues like slow responses or high CPU usage, use the built-in --prof flag to generate a CPU profile:

node --prof server.js

After it runs for a while, stop it and process the profile with:

node --prof-process isolate-*.log > processed.txt

Inspect the processed file to find hotspots. Look for functions with high "ticks" or "total" percentages. For example:

 [Summary]:
   ticks  total  nonlib   name
    102    5.2%   10.4%  JavaScript
     86    4.4%    8.8%  C++
     42    2.1%    4.3%  GC

For memory issues, take heap snapshots using --inspect and the Chrome DevTools Memory tab, or use process.memoryUsage() in your code to log memory statistics periodically:

setInterval(() => {
  const mem = process.memoryUsage();
  console.log(`RSS: ${mem.rss}, Heap Used: ${mem.heapUsed}, Heap Total: ${mem.heapTotal}`);
}, 10000);

Check event loop lag to detect blocking code. You can use a library like blocked or measure it yourself:

const { monitorEventLoopDelay } = require('perf_hooks').performance;
const h = monitorEventLoopDelay();
h.enable();
setInterval(() => {
  console.log(`Event loop delay: ${h.mean} ms`);
}, 1000);

High delay (e.g., >50ms) indicates a blocked event loop.

Failure Modes and Recovery

Node.js applications can fail in various ways. Understanding common failure modes helps you prepare recovery strategies.

Uncaught Exceptions and Unhandled Rejections

If an exception is not caught, the process will exit. Add global handlers to log and optionally restart:

process.on('uncaughtException', (err) => {
  console.error('Uncaught exception:', err);
  process.exit(1); // many recommend exiting
});

process.on('unhandledRejection', (reason, promise) => {
  console.error('Unhandled rejection at:', promise, 'reason:', reason);
});

Better yet, use a process manager like PM2 to automatically restart on crash:

pm2 start server.js --name my-app
pm2 save
pm2 startup

PM2 will also capture logs and provide a dashboard. Alternatively, use systemd with Restart=always in the service file.

Memory Leaks

A memory leak can cause the process to run out of memory and crash. Use --max-old-space-size to increase heap size temporarily, but the real fix is to identify and fix the leak. Take heap snapshots over time and compare them to find objects that are not being freed.

Example workflow:

  1. Start app with node --inspect server.js
  2. Open Chrome DevTools and attach.
  3. Go to Memory tab, take a heap snapshot.
  4. Generate load, wait, take another snapshot.
  5. Compare snapshots and look for objects with increasing counts.

Common leak sources include global variables, closures retaining references, and event listeners not removed. Fix the root cause.

Blocked Event Loop

Blocking operations like synchronous file I/O or CPU-intensive loops can freeze the event loop. Avoid sync functions in production code. Use worker_threads for CPU-bound tasks.

Example of bad code:

const fs = require('fs');
const data = fs.readFileSync('/path/to/large/file'); // blocks event loop

Better:

const fs = require('fs');
fs.readFile('/path/to/large/file', (err, data) => {
  // handle async
});

Configuration Errors

If the application fails to start due to missing environment variables or invalid config, it will often exit immediately with an error. Use the validation technique from earlier to catch these early.

Network Issues

Timeouts and connection refused errors are common. Use curl to test endpoints, and check firewall rules and service availability.

curl -I http://localhost:3000

Expected output includes HTTP status like HTTP/1.1 200 OK. For database connectivity, use a quick Node.js one-liner as shown earlier.

Recovery Steps

  • Restart the application if it is down. Use PM2 or systemd:
pm2 restart my-app
# or
sudo systemctl restart my-node-app
  • Check logs for the root cause before restarting to avoid losing information. Save logs to a file if needed.
  • Rollback configuration changes if you suspect a recent change caused the issue. If using Git, revert to a previous commit:
git revert <commit-hash>
  • Restore from backup if data corruption occurred. Ensure you have regular backups of databases and important files.
  • Update dependencies if a known bug is the cause. But test updates in a staging environment first:
npm update <package-name>

After recovery, conduct a post-mortem to document the incident and improve monitoring.

Common Pitfalls and How to Avoid Them

Experienced Node.js developers often fall into the same traps. Here are common mistakes and how to avoid or recover from them.

Ignoring Environment Differences

Problem: Code works locally but fails in production because of different Node.js versions or environment variables.

Why it happens: Developers assume the production environment matches their local setup.

How to avoid: Use Docker to standardize environments, or at least pin Node.js versions in package.json with "engines" field:

{
  "engines": {
    "node": ">=18.0.0"
  }
}

Use nvm or a version manager in development to match production.

Not Handling Async Errors Properly

Problem: Unhandled promise rejections crash the process or leave it in an inconsistent state.

Why it happens: Forgetting to await a promise or not adding .catch() handlers.

How to avoid: Use async/await consistently and wrap in try/catch. Enable --unhandled-rejections=strict to fail fast in development:

node --unhandled-rejections=strict server.js

Overusing Synchronous Methods

Problem: Synchronous file or network operations block the event loop, causing slowdowns and timeouts.

Why it happens: Convenience or lack of awareness of async alternatives.

How to avoid: Use async versions of methods (fs.promises.readFile instead of fs.readFileSync). Lint with rules like no-sync in ESLint.

Neglecting Security Updates

Problem: Known vulnerabilities in dependencies are exploited.

Why it happens: Teams forget to run npm audit regularly.

How to avoid: Run npm audit in CI pipeline and after every dependency change. Use npm audit fix to apply safe updates.

Misconfiguring Process Managers

Problem: Application does not restart on crash or fails to start on boot.

Why it happens: Missing --update-env flag or incorrect PM2 save/startup setup.

How to avoid: Always use pm2 save after changes and ensure pm2 startup is configured. Test by rebooting the server or simulating a crash.

Operations Checklist

Use this checklist for routine operations and when troubleshooting. Each item includes the command or action, expected result, and the owner responsible for that check.

TaskCommand / ActionExpected ResultOwnerFrequency
Check Node.js versionnode -vOutput version (e.g., v18.17.1)DevOps EngineerWeekly
Check npm versionnpm -vOutput versionDevOps EngineerWeekly
List installed packagesnpm ls --depth=0List of top-level dependenciesBackend DeveloperWeekly
Check process statusps aux | grep nodeNode.js process listed with PIDDevOps EngineerDaily
View application logstail -f app.logReal-time log outputBackend DeveloperDuring incidents
Test HTTP endpointcurl -I http://localhost:3000HTTP response headers with 200 OKQA EngineerAfter deployments
Check database connectivitynode -e "require('mongodb').MongoClient.connect('mongodb://localhost:27017/test', (err, client) => { if(err) console.error(err); else console.log('Connected'); client.close(); })""Connected" message or errorBackend DeveloperDaily
Check memory usageps -o rss= -p <PID>Resident set size in KBDevOps EngineerHourly via monitoring
Verify event loop healthUse monitorEventLoopDelay or clinic doctorLow mean delay (<10ms)Backend DeveloperDuring performance testing
Run linternpm run lintNo errors or warningsBackend DeveloperEvery commit
Run testsnpm testAll tests passQA EngineerEvery merge
Check for outdated packagesnpm outdatedList of packages with newer versionsDevOps EngineerMonthly
Backup configurationcp .env .env.backupBackup file createdDevOps EngineerBefore config changes
Review security vulnerabilitiesnpm auditReport with vulnerabilities (should be zero)Security EngineerWeekly
Check disk spacedf -hSufficient free space (>20%)DevOps EngineerWeekly
Monitor CPU loadtop or htopLoad average below number of coresDevOps EngineerDuring high traffic
Test failover/restartSimulate crash and verify recoveryProcess restarts within 30 secondsDevOps EngineerQuarterly
Review incident post-mortemsDocument and assign action itemsAll action items completedEngineering ManagerAfter every incident

Regularly performing these checks can catch issues before they affect users. Assign each check to a single owner (not a team) to ensure accountability. Review the checklist quarterly to adjust frequencies and tasks based on system changes.

Conclusion

Node.js troubleshooting requires a methodical approach: know your environment, make configuration changes safely, verify with observable diagnostics, and prepare for failure modes with recovery plans. By using the practical examples in this guide, you can reduce downtime and improve the reliability of your Node.js applications. Start by implementing the operations checklist in your daily workflow, and build a culture of proactive monitoring and logging. The next step is to integrate these practices into your development and deployment processes.

Remember that troubleshooting is not just about fixing issues but also about preventing them. Regular maintenance, clear ownership, and continuous learning are key to keeping Node.js applications healthy. Use this guide as a reference, adapt it to your stack, and share it with your team to build a common troubleshooting playbook.

Related Research

Article Quality Score

Reader usefulness 100%
  • check_circle Reader-ready guide
  • check_circle Practical examples included
  • check_circle Clean SEO article URL