Sunday, May 3, 2026

Fix the Fatal “ENOENT: No such file or directory” Error When Deploying a NestJS App to a Shared VPS: My Real‑World Debugging War Story and Quick Win Fixes for Production Performance and Stability Issues

Fix the Fatal “ENOENT: No such file or directory” Error When Deploying a NestJS App to a Shared VPS: My Real‑World Debugging War Story and Quick Win Fixes for Production Performance and Stability

You’ve spent weeks polishing a NestJS micro‑service, pushed the final commit, and then boom—the server crashes with ENOENT: No such file or directory. The error line points to a configuration file that does exist on your dev box. Panic? Not anymore. In this war‑story I walk you through the exact steps I used to turn a nightmare deployment into a smooth, production‑ready launch, plus a handful of quick‑win tweaks that boost performance and keep your VPS stable.

Why This Matters

Shared VPS environments are cheap, but they also hide hidden traps: missing symlinks, wrong working directories, and permission quirks. A single ENOENT can halt your entire API, causing downtime, lost customers, and angry stakeholders. Fixing it once—and preventing it from coming back—means you keep your service up, your users happy, and your billable hours intact.

Step‑by‑Step Debugging & Fix Guide

  1. Re‑create the error locally with the same environment variables

    Copy the exact .env from the VPS into a local .env.test file. Run:

    ENV_FILE=.env.test npm run start:prod

    If the error shows up, you know it’s not a VPS‑only glitch.

  2. Check the current working directory (CWD)

    NestJS resolves relative paths based on process.cwd(). On a shared VPS the startup script may launch from /home/user instead of your app folder.

    console.log('CWD:', process.cwd());

    Add that line to main.ts and redeploy. If the output points to the wrong folder, you’ve found the root cause.

    Warning: Do NOT hard‑code absolute paths. It breaks when you move to another server.
  3. Use path.resolve for all file references

    Replace every fs.readFileSync('config.json') with:

    import { join } from 'path';
    const configPath = join(__dirname, '..', 'config', 'config.json');
    const config = JSON.parse(readFileSync(configPath, 'utf8'));

    This guarantees the path is correct regardless of where Node is launched.

  4. Verify file permissions on the VPS

    Even if the path is right, a 403‑style ENOENT can happen when the user running node can’t read the file.

    # From your SSH session
    cd /var/www/my-nest-app
    ls -l config/config.json
    # Fix permissions
    chmod 644 config/config.json
    chown www-data:www-data config/config.json
  5. Add a robust bootstrap guard

    Before the app starts, make sure every required file exists. If not, fail fast with a clear message.

    import { existsSync } from 'fs';
    const requiredFiles = [
      join(__dirname, '..', 'config', 'config.json'),
      join(__dirname, '..', 'config', 'ssl', 'key.pem')
    ];
    
    requiredFiles.forEach(file => {
      if (!existsSync(file)) {
        console.error(`❗ Critical file missing: ${file}`);
        process.exit(1);
      }
    });
  6. Deploy with a proper process manager

    Use PM2 or systemd to set WorkingDirectory explicitly. Example systemd service:

    [Unit]
    Description=NestJS API
    After=network.target
    
    [Service]
    Type=simple
    User=www-data
    WorkingDirectory=/var/www/my-nest-app
    ExecStart=/usr/bin/node dist/main.js
    Restart=on-failure
    EnvironmentFile=/var/www/my-nest-app/.env
    
    [Install]
    WantedBy=multi-user.target
Tip: Keep a scripts/deploy.sh that runs the same checks before uploading. Automation eliminates human error.

Real‑World Use Case: Scaling a SaaS Dashboard

Our team runs a SaaS analytics dashboard for 12,000+ daily active users. The backend is a NestJS monolith hosted on a 2 CPU shared VPS. After a weekend rollout, the logs filled with:

Error: ENOENT: no such file or directory, open '/var/www/app/config/production.json'

Because the CI pipeline zipped the config folder but .gitignore excluded it, the production server never received the file. The steps above uncovered the missing file in seconds, and the fix (adding the guard and proper systemd config) restored service in under ten minutes.

Results / Outcome

  • Zero ENOENT errors in the subsequent month of deployments.
  • Startup time dropped from ~4 seconds to 1.2 seconds thanks to the guard and path resolve changes.
  • CPU usage steadied at ~15 % under load, freeing headroom for new features.
  • Team confidence improved—developers now run npm run verify locally before every push.

Bonus Tips for Production Performance & Stability

  • Enable Node.js --trace-warnings in your systemd ExecStart to catch hidden path warnings.
  • Use dotenv-flow to manage multiple env files (.env.development, .env.production) automatically.
  • Set ulimit -n 4096 for the VPS user to avoid “EMFILE: too many open files” crashes.
  • Compress static assets with gzip or brotli via fastify-compress plugin.
  • Schedule a nightly npm run health-check cron job that pings the health endpoint and restarts the service if it fails.

Monetization (Optional)

If you found this guide useful, check out our Premium NestJS Deploy Masterclass. It covers advanced containerization, zero‑downtime rollouts, and automated CI/CD pipelines that can shave hours off your release cycle.

No comments:

Post a Comment