Setup Node.js error tracking with Sentry for production monitoring and debugging

Intermediate 25 min Jun 09, 2026 15 views
Ubuntu 24.04 Debian 12 AlmaLinux 9 Rocky Linux 9

Configure Sentry error tracking for Node.js applications with PM2 process management, automated error reporting, and production-ready alerting to catch and debug issues before they affect users.

Prerequisites

  • Root or sudo access
  • Basic knowledge of Node.js and npm
  • Sentry account (free tier available)

What this solves

Node.js applications in production need comprehensive error tracking to catch runtime exceptions, performance issues, and user-affecting bugs before they cascade. Sentry provides real-time error monitoring with detailed stack traces, user context, and alerting that integrates seamlessly with Node.js applications running under PM2 process management.

Step-by-step installation

Update system packages

Start by updating your package manager to ensure you get the latest versions of Node.js and PM2.

sudo apt update && sudo apt upgrade -y
sudo apt install -y curl build-essential
sudo dnf update -y
sudo dnf groupinstall -y "Development Tools"
sudo dnf install -y curl

Install Node.js and npm

Install the latest LTS version of Node.js using the NodeSource repository for better version control and security updates.

curl -fsSL https://deb.nodesource.com/setup_lts.x | sudo -E bash -
sudo apt install -y nodejs
curl -fsSL https://rpm.nodesource.com/setup_lts.x | sudo bash -
sudo dnf install -y nodejs npm

Install PM2 globally

PM2 provides process management for Node.js applications with clustering, monitoring, and automatic restarts.

sudo npm install -g pm2
pm2 --version

Create Sentry account and project

Sign up for a Sentry account at sentry.io and create a new Node.js project. Copy the DSN (Data Source Name) from your project settings.

Note: Sentry offers a free tier with 5,000 errors per month, which is sufficient for small to medium applications. The DSN looks like: https://abc123@o123456.ingest.sentry.io/123456

Create sample Node.js application

Create a basic Express.js application to demonstrate Sentry integration and error tracking capabilities.

mkdir -p /opt/nodeapp
cd /opt/nodeapp
npm init -y

Install required dependencies

Install Sentry SDK, Express framework, and additional packages for comprehensive error tracking.

npm install @sentry/node @sentry/profiling-node express helmet cors dotenv
npm install --save-dev nodemon

Configure environment variables

Create environment configuration file to store Sentry DSN and application settings securely.

NODE_ENV=production
PORT=3000
SENTRY_DSN=https://your-dsn@o123456.ingest.sentry.io/123456
SENTRY_TRACES_SAMPLE_RATE=1.0
SENTRY_PROFILES_SAMPLE_RATE=1.0

Create main application file

Build Express application with Sentry integration, error handling middleware, and sample routes for testing error tracking.

const Sentry = require('@sentry/node');
const { nodeProfilingIntegration } = require('@sentry/profiling-node');
const express = require('express');
const helmet = require('helmet');
const cors = require('cors');
require('dotenv').config();

// Initialize Sentry BEFORE requiring any other modules
Sentry.init({
  dsn: process.env.SENTRY_DSN,
  environment: process.env.NODE_ENV,
  integrations: [
    // enable HTTP calls tracing
    new Sentry.Integrations.Http({ tracing: true }),
    // enable Express.js middleware tracing
    new Sentry.Integrations.Express({ app: express() }),
    // enable profiling
    nodeProfilingIntegration(),
  ],
  // Performance monitoring
  tracesSampleRate: parseFloat(process.env.SENTRY_TRACES_SAMPLE_RATE),
  // Profiling sample rate
  profilesSampleRate: parseFloat(process.env.SENTRY_PROFILES_SAMPLE_RATE),
});

const app = express();
const PORT = process.env.PORT || 3000;

// Sentry request handler must be first middleware
app.use(Sentry.Handlers.requestHandler());
app.use(Sentry.Handlers.tracingHandler());

// Security middleware
app.use(helmet());
app.use(cors());
app.use(express.json({ limit: '10mb' }));
app.use(express.urlencoded({ extended: true }));

// Health check endpoint
app.get('/health', (req, res) => {
  res.status(200).json({ 
    status: 'healthy', 
    timestamp: new Date().toISOString(),
    uptime: process.uptime()
  });
});

// Sample route that works
app.get('/', (req, res) => {
  res.json({ message: 'Node.js app with Sentry error tracking' });
});

// Test route for throwing errors
app.get('/debug-sentry', (req, res) => {
  throw new Error('Test error for Sentry - this should be caught!');
});

// Async error test
app.get('/async-error', async (req, res) => {
  try {
    await new Promise((resolve, reject) => {
      setTimeout(() => reject(new Error('Async operation failed')), 100);
    });
  } catch (error) {
    Sentry.captureException(error);
    res.status(500).json({ error: 'Async error occurred' });
  }
});

// Custom error with context
app.get('/context-error', (req, res) => {
  Sentry.withScope((scope) => {
    scope.setTag('section', 'api');
    scope.setLevel('error');
    scope.setContext('request_details', {
      path: req.path,
      method: req.method,
      query: req.query
    });
    Sentry.captureException(new Error('Error with custom context'));
  });
  res.status(500).json({ error: 'Context error sent to Sentry' });
});

// 404 handler
app.use('*', (req, res) => {
  res.status(404).json({ error: 'Route not found' });
});

// Sentry error handler must be before any other error middleware
app.use(Sentry.Handlers.errorHandler());

// Global error handler
app.use((err, req, res, next) => {
  console.error('Unhandled error:', err.message);
  res.status(500).json({ 
    error: 'Internal server error',
    requestId: res.sentry
  });
});

// Graceful shutdown handling
process.on('SIGTERM', () => {
  console.log('SIGTERM received, shutting down gracefully');
  process.exit(0);
});

process.on('SIGINT', () => {
  console.log('SIGINT received, shutting down gracefully');
  process.exit(0);
});

// Unhandled promise rejection
process.on('unhandledRejection', (reason, promise) => {
  console.error('Unhandled Rejection at:', promise, 'reason:', reason);
  Sentry.captureException(new Error(Unhandled Rejection: ${reason}));
});

app.listen(PORT, () => {
  console.log(Server running on port ${PORT});
  console.log(Environment: ${process.env.NODE_ENV});
  console.log(Sentry DSN configured: ${process.env.SENTRY_DSN ? 'Yes' : 'No'});
});

Create PM2 ecosystem configuration

Configure PM2 with clustering, monitoring, and automatic restart policies for production deployment.

module.exports = {
  apps: [{
    name: 'nodeapp-sentry',
    script: './app.js',
    instances: 'max', // Use all CPU cores
    exec_mode: 'cluster',
    env: {
      NODE_ENV: 'development',
      PORT: 3000
    },
    env_production: {
      NODE_ENV: 'production',
      PORT: 3000
    },
    // PM2 monitoring and restart policies
    max_memory_restart: '1G',
    min_uptime: '10s',
    max_restarts: 10,
    restart_delay: 4000,
    
    // Logging configuration
    log_file: '/var/log/nodeapp/combined.log',
    out_file: '/var/log/nodeapp/out.log',
    error_file: '/var/log/nodeapp/error.log',
    log_date_format: 'YYYY-MM-DD HH:mm:ss Z',
    
    // Advanced PM2 features
    watch: false, // Set to true in development
    ignore_watch: ['node_modules', 'logs'],
    watch_options: {
      followSymlinks: false
    },
    
    // Health monitoring
    health_check_grace_period: 3000,
    health_check_fatal_exceptions: true
  }]
};

Create log directories and set permissions

Set up logging directories with correct ownership for PM2 and the application user.

sudo mkdir -p /var/log/nodeapp
sudo chown $USER:$USER /var/log/nodeapp
sudo chmod 755 /var/log/nodeapp

Set application ownership and permissions

Configure proper file ownership and permissions for the Node.js application directory.

sudo chown -R $USER:$USER /opt/nodeapp
sudo chmod -R 755 /opt/nodeapp
sudo chmod 600 /opt/nodeapp/.env

Start application with PM2

Launch the application using PM2 ecosystem configuration and enable startup script for automatic restart on server reboot.

cd /opt/nodeapp
pm2 start ecosystem.config.js --env production
pm2 save
pm2 startup
Note: The startup command will show a command to run with sudo. Execute that command to enable PM2 autostart on system boot.

Configure firewall access

Open the application port in the firewall to allow external access while maintaining security.

sudo ufw allow 3000/tcp
sudo ufw reload
sudo firewall-cmd --permanent --add-port=3000/tcp
sudo firewall-cmd --reload

Configure Sentry alerts and notifications

Set up email and Slack notifications in your Sentry project dashboard. Navigate to Settings > Alerts and create rules for error frequency, new issues, and performance degradation.

Tip: Configure alert rules for immediate notification on new errors, and digest emails for error trends. Set up different thresholds for staging and production environments.

Verify your setup

Test the Node.js application with Sentry integration and verify error tracking is working correctly.

# Check PM2 status
pm2 status
pm2 logs nodeapp-sentry --lines 20

Test application endpoints

curl http://localhost:3000/health curl http://localhost:3000/

Test Sentry error tracking

curl http://localhost:3000/debug-sentry curl http://localhost:3000/async-error curl http://localhost:3000/context-error

Check PM2 monitoring

pm2 monit

Visit your Sentry dashboard to confirm errors are being captured with full stack traces, user context, and performance data. You should see the test errors appear within seconds.

Production configuration and best practices

Configure log rotation

Set up log rotation to prevent disk space issues in production environments.

/var/log/nodeapp/*.log {
    daily
    missingok
    rotate 14
    compress
    delaycompress
    notifempty
    create 644 $USER $USER
    postrotate
        pm2 reloadLogs
    endscript
}

Configure Sentry performance monitoring

Optimize Sentry configuration for production workloads with appropriate sampling rates.

# Production Sentry configuration
SENTRY_TRACES_SAMPLE_RATE=0.1
SENTRY_PROFILES_SAMPLE_RATE=0.1
SENTRY_RELEASE=v1.0.0
SENTRY_ENVIRONMENT=production

Set up reverse proxy with NGINX

Configure NGINX reverse proxy for better performance, SSL termination, and load balancing across PM2 cluster instances.

upstream nodeapp {
    least_conn;
    server 127.0.0.1:3000 max_fails=3 fail_timeout=30s;
    keepalive 32;
}

server {
    listen 80;
    server_name example.com;
    
    # Security headers
    add_header X-Frame-Options "SAMEORIGIN" always;
    add_header X-Content-Type-Options "nosniff" always;
    add_header X-XSS-Protection "1; mode=block" always;
    
    # Gzip compression
    gzip on;
    gzip_vary on;
    gzip_min_length 1024;
    gzip_types text/plain text/css application/json application/javascript;
    
    location / {
        proxy_pass http://nodeapp;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection 'upgrade';
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_cache_bypass $http_upgrade;
        
        # Timeouts
        proxy_connect_timeout 30s;
        proxy_send_timeout 30s;
        proxy_read_timeout 30s;
    }
    
    # Health check endpoint
    location /health {
        proxy_pass http://nodeapp;
        access_log off;
    }
}

Advanced Sentry features

Feature Configuration Use Case
Custom Tags Sentry.setTag('feature', 'payment') Filter errors by application feature
User Context Sentry.setUser({id: '123', email: 'user@example.com'}) Track errors affecting specific users
Breadcrumbs Sentry.addBreadcrumb({message: 'API call started'}) Track user actions leading to errors
Performance const transaction = Sentry.startTransaction({name: 'API'}) Monitor slow database queries and API calls
Release Tracking SENTRY_RELEASE=v1.2.3 Track errors by deployment version

Monitoring and alerting integration

For comprehensive production monitoring, integrate Sentry with your existing observability stack. You can connect Sentry error tracking with Prometheus metrics collection for complete application visibility.

Configure PM2 monitoring and log management with the PM2 advanced monitoring setup to complement Sentry's error tracking with system-level metrics.

Common issues

Symptom Cause Fix
Errors not appearing in Sentry Invalid DSN or network issues Verify DSN in environment variables and check curl -I https://sentry.io
PM2 app crashing repeatedly Memory limit exceeded or unhandled errors Check pm2 logs and increase max_memory_restart in ecosystem config
High Sentry quota usage Too many captured errors or high sampling rate Reduce SENTRY_TRACES_SAMPLE_RATE to 0.1 and filter noisy errors
Missing error context Sentry not configured before Express middleware Ensure Sentry.init() is called before requiring Express
PM2 startup script not working Incorrect user permissions Run pm2 unstartup then pm2 startup and follow instructions

Next steps

Running this in production?

Want this handled for you? Setting up error tracking once is straightforward. Keeping it tuned, managing alert fatigue, and correlating errors with performance metrics across environments is the harder part. See how we run infrastructure like this for European teams.

Automated install script

Run this to automate the entire setup

Need help?

Don't want to manage this yourself?

We handle managed devops services for businesses that depend on uptime. From initial setup to ongoing operations.