Monitor Node.js applications with Prometheus and Grafana for comprehensive performance metrics

Intermediate 45 min Apr 29, 2026 124 views
Ubuntu 24.04 Debian 12 AlmaLinux 9 Rocky Linux 9

Set up comprehensive Node.js application monitoring with Prometheus metrics collection and Grafana dashboards. Configure alerting rules for performance issues, memory usage, and error tracking in production environments.

Prerequisites

  • Root or sudo access
  • Node.js application to monitor
  • At least 2GB RAM
  • Network connectivity for package installation

What this solves

Node.js applications in production need comprehensive monitoring to track performance, memory usage, response times, and error rates. This tutorial sets up Prometheus 2.45 to collect metrics from your Node.js applications using the prom-client library, creates Grafana dashboards for visualization, and implements alerting rules for proactive performance monitoring.

Step-by-step installation

Update system packages

Start by updating your package manager to ensure you get the latest versions of dependencies.

sudo apt update && sudo apt upgrade -y
sudo apt install -y curl wget gnupg2 software-properties-common
sudo dnf update -y
sudo dnf install -y curl wget gnupg2 tar

Install Prometheus 2.45

Download and install Prometheus server to collect and store metrics from your Node.js applications.

cd /tmp
wget https://github.com/prometheus/prometheus/releases/download/v2.45.0/prometheus-2.45.0.linux-amd64.tar.gz
tar -xzf prometheus-2.45.0.linux-amd64.tar.gz
sudo mv prometheus-2.45.0.linux-amd64/prometheus /usr/local/bin/
sudo mv prometheus-2.45.0.linux-amd64/promtool /usr/local/bin/
sudo mkdir -p /etc/prometheus /var/lib/prometheus
sudo chown -R prometheus:prometheus /etc/prometheus /var/lib/prometheus

Create Prometheus user and service

Create a dedicated user for Prometheus and set up the systemd service for automatic startup.

sudo useradd --no-create-home --shell /bin/false prometheus
sudo chown -R prometheus:prometheus /usr/local/bin/prometheus /usr/local/bin/promtool
sudo chown -R prometheus:prometheus /etc/prometheus /var/lib/prometheus

Configure Prometheus

Create the main Prometheus configuration file to scrape Node.js application metrics.

global:
  scrape_interval: 15s
  evaluation_interval: 15s

rule_files:
  - "nodejs_alerts.yml"

scrape_configs:
  - job_name: 'prometheus'
    static_configs:
      - targets: ['localhost:9090']
  
  - job_name: 'nodejs-app'
    static_configs:
      - targets: ['localhost:3000']
    metrics_path: '/metrics'
    scrape_interval: 5s

Create Prometheus systemd service

Set up systemd service file to manage Prometheus startup and enable automatic restart on failure.

[Unit]
Description=Prometheus Server
Documentation=https://prometheus.io/docs/
After=network-online.target

[Service]
User=prometheus
Group=prometheus
Type=simple
Restart=on-failure
ExecStart=/usr/local/bin/prometheus \
  --config.file=/etc/prometheus/prometheus.yml \
  --storage.tsdb.path=/var/lib/prometheus/ \
  --web.console.templates=/etc/prometheus/consoles \
  --web.console.libraries=/etc/prometheus/console_libraries \
  --web.listen-address=0.0.0.0:9090 \
  --web.enable-lifecycle

[Install]
WantedBy=multi-user.target

Install Node.js and npm

Install Node.js runtime and npm package manager if not already installed.

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

Create sample Node.js application with metrics

Create a sample Node.js application that exposes Prometheus metrics using the prom-client library.

mkdir -p /opt/nodejs-app
cd /opt/nodejs-app
npm init -y
npm install express prom-client

Implement Node.js metrics collection

Create the main application file with comprehensive Prometheus metrics including HTTP requests, memory usage, and custom business metrics.

const express = require('express');
const promClient = require('prom-client');

const app = express();
const port = 3000;

// Create a Registry to register the metrics
const register = new promClient.Registry();

// Add default system metrics
promClient.collectDefaultMetrics({
  register,
  timeout: 10000,
  gcDurationBuckets: [0.001, 0.01, 0.1, 1, 2, 5],
});

// Custom metrics
const httpRequestCounter = new promClient.Counter({
  name: 'nodejs_http_requests_total',
  help: 'Total number of HTTP requests',
  labelNames: ['method', 'route', 'status_code'],
  registers: [register],
});

const httpRequestDuration = new promClient.Histogram({
  name: 'nodejs_http_request_duration_seconds',
  help: 'Duration of HTTP requests in seconds',
  labelNames: ['method', 'route', 'status_code'],
  buckets: [0.1, 0.3, 0.5, 0.7, 1, 3, 5, 7, 10],
  registers: [register],
});

const activeConnections = new promClient.Gauge({
  name: 'nodejs_active_connections',
  help: 'Number of active connections',
  registers: [register],
});

const businessMetric = new promClient.Counter({
  name: 'nodejs_business_operations_total',
  help: 'Total business operations performed',
  labelNames: ['operation_type'],
  registers: [register],
});

// Middleware to collect metrics
app.use((req, res, next) => {
  const start = Date.now();
  
  activeConnections.inc();
  
  res.on('finish', () => {
    const duration = (Date.now() - start) / 1000;
    
    httpRequestCounter.inc({
      method: req.method,
      route: req.route ? req.route.path : req.path,
      status_code: res.statusCode,
    });
    
    httpRequestDuration.observe(
      {
        method: req.method,
        route: req.route ? req.route.path : req.path,
        status_code: res.statusCode,
      },
      duration
    );
    
    activeConnections.dec();
  });
  
  next();
});

// Sample routes
app.get('/', (req, res) => {
  businessMetric.inc({ operation_type: 'homepage_view' });
  res.json({ message: 'Hello World!', timestamp: new Date().toISOString() });
});

app.get('/api/users', (req, res) => {
  businessMetric.inc({ operation_type: 'user_list' });
  
  // Simulate some processing time
  setTimeout(() => {
    res.json({
      users: [
        { id: 1, name: 'John Doe' },
        { id: 2, name: 'Jane Smith' },
      ],
    });
  }, Math.random() * 1000);
});

app.get('/api/error', (req, res) => {
  businessMetric.inc({ operation_type: 'error_simulation' });
  res.status(500).json({ error: 'Simulated error' });
});

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

// Metrics endpoint
app.get('/metrics', async (req, res) => {
  res.set('Content-Type', register.contentType);
  const metrics = await register.metrics();
  res.end(metrics);
});

app.listen(port, () => {
  console.log(Node.js app listening at http://localhost:${port});
  console.log(Metrics available at http://localhost:${port}/metrics);
});

Create Node.js application service

Set up systemd service for the Node.js application to ensure it starts automatically and restarts on failure.

[Unit]
Description=Node.js Monitoring Demo App
After=network.target

[Service]
Type=simple
User=nodejs
Group=nodejs
WorkingDirectory=/opt/nodejs-app
Environment=NODE_ENV=production
ExecStart=/usr/bin/node app.js
Restart=on-failure
RestartSec=10
KillMode=process

[Install]
WantedBy=multi-user.target

Create application user and set permissions

Create a dedicated user for the Node.js application and set proper file ownership and permissions.

sudo useradd --system --shell /bin/false nodejs
sudo chown -R nodejs:nodejs /opt/nodejs-app
sudo chmod 755 /opt/nodejs-app
sudo chmod 644 /opt/nodejs-app/*

Install Grafana

Install Grafana for creating dashboards and visualizing the Node.js metrics collected by Prometheus.

wget -q -O - https://packages.grafana.com/gpg.key | sudo apt-key add -
echo "deb https://packages.grafana.com/oss/deb stable main" | sudo tee /etc/apt/sources.list.d/grafana.list
sudo apt update
sudo apt install -y grafana
sudo tee /etc/yum.repos.d/grafana.repo << 'EOF'
[grafana]
name=grafana
baseurl=https://packages.grafana.com/oss/rpm
repo_gpgcheck=1
enabled=1
gpgcheck=1
gpgkey=https://packages.grafana.com/gpg.key
sslverify=1
sslcacert=/etc/pki/tls/certs/ca-bundle.crt
EOF
sudo dnf install -y grafana

Configure Grafana data source

Configure Grafana to use Prometheus as a data source for Node.js metrics visualization.

apiVersion: 1

datasources:
  - name: Prometheus
    type: prometheus
    access: proxy
    url: http://localhost:9090
    isDefault: true
    jsonData:
      timeInterval: 15s
      queryTimeout: 60s
      httpMethod: POST
    editable: true

Create Node.js dashboard configuration

Create a comprehensive Grafana dashboard configuration for monitoring Node.js application performance.

{
  "dashboard": {
    "id": null,
    "title": "Node.js Application Monitoring",
    "tags": ["nodejs", "prometheus"],
    "timezone": "browser",
    "panels": [
      {
        "id": 1,
        "title": "HTTP Request Rate",
        "type": "stat",
        "targets": [
          {
            "expr": "rate(nodejs_http_requests_total[5m])",
            "refId": "A"
          }
        ],
        "gridPos": {"h": 8, "w": 12, "x": 0, "y": 0}
      },
      {
        "id": 2,
        "title": "Response Time",
        "type": "graph",
        "targets": [
          {
            "expr": "histogram_quantile(0.95, rate(nodejs_http_request_duration_seconds_bucket[5m]))",
            "refId": "A"
          }
        ],
        "gridPos": {"h": 8, "w": 12, "x": 12, "y": 0}
      },
      {
        "id": 3,
        "title": "Memory Usage",
        "type": "graph",
        "targets": [
          {
            "expr": "process_resident_memory_bytes",
            "refId": "A"
          }
        ],
        "gridPos": {"h": 8, "w": 12, "x": 0, "y": 8}
      },
      {
        "id": 4,
        "title": "Error Rate",
        "type": "stat",
        "targets": [
          {
            "expr": "rate(nodejs_http_requests_total{status_code=~\"5..\"}[5m])",
            "refId": "A"
          }
        ],
        "gridPos": {"h": 8, "w": 12, "x": 12, "y": 8}
      }
    ],
    "time": {
      "from": "now-1h",
      "to": "now"
    },
    "refresh": "5s"
  }
}

Create alerting rules

Define Prometheus alerting rules for Node.js application performance monitoring and error detection.

groups:
  - name: nodejs-alerts
    rules:
      - alert: NodejsHighErrorRate
        expr: rate(nodejs_http_requests_total{status_code=~"5.."}[5m]) > 0.1
        for: 2m
        labels:
          severity: critical
        annotations:
          summary: "High error rate detected in Node.js application"
          description: "Error rate is {{ $value }} errors per second"
      
      - alert: NodejsHighResponseTime
        expr: histogram_quantile(0.95, rate(nodejs_http_request_duration_seconds_bucket[5m])) > 1.0
        for: 5m
        labels:
          severity: warning
        annotations:
          summary: "High response time in Node.js application"
          description: "95th percentile response time is {{ $value }}s"
      
      - alert: NodejsHighMemoryUsage
        expr: process_resident_memory_bytes > 500000000
        for: 10m
        labels:
          severity: warning
        annotations:
          summary: "High memory usage in Node.js application"
          description: "Memory usage is {{ $value | humanize }}B"
      
      - alert: NodejsApplicationDown
        expr: up{job="nodejs-app"} == 0
        for: 1m
        labels:
          severity: critical
        annotations:
          summary: "Node.js application is down"
          description: "Node.js application has been down for more than 1 minute"

Start and enable services

Start all services and enable them to start automatically on system boot.

sudo systemctl daemon-reload
sudo systemctl enable --now nodejs-app
sudo systemctl enable --now prometheus
sudo systemctl enable --now grafana-server

Configure firewall

Open necessary firewall ports for Prometheus, Grafana, and the Node.js application.

sudo ufw allow 3000/tcp comment 'Node.js app'
sudo ufw allow 9090/tcp comment 'Prometheus'
sudo ufw allow 3000/tcp comment 'Grafana'
sudo ufw --force enable
sudo firewall-cmd --permanent --add-port=3000/tcp --add-comment="Node.js app"
sudo firewall-cmd --permanent --add-port=9090/tcp --add-comment="Prometheus"
sudo firewall-cmd --permanent --add-port=3000/tcp --add-comment="Grafana"
sudo firewall-cmd --reload

Verify your setup

Test that all components are working correctly and metrics are being collected.

sudo systemctl status nodejs-app prometheus grafana-server
curl http://localhost:3000/health
curl http://localhost:3000/metrics
curl http://localhost:9090/api/v1/targets
echo "Grafana available at: http://localhost:3000 (admin/admin)"

Generate test metrics

Create some sample traffic to populate your dashboards with meaningful data.

for i in {1..100}; do
  curl -s http://localhost:3000/ > /dev/null
  curl -s http://localhost:3000/api/users > /dev/null
  curl -s http://localhost:3000/api/error > /dev/null
  sleep 0.1
done

Advanced monitoring configuration

Add custom business metrics

Extend your Node.js application with custom business metrics for specific application events.

const promClient = require('prom-client');

class ApplicationMetrics {
  constructor(register) {
    this.register = register;
    
    this.userRegistrations = new promClient.Counter({
      name: 'nodejs_user_registrations_total',
      help: 'Total number of user registrations',
      labelNames: ['source'],
      registers: [register],
    });
    
    this.orderValues = new promClient.Histogram({
      name: 'nodejs_order_value_dollars',
      help: 'Order values in dollars',
      labelNames: ['product_category'],
      buckets: [10, 50, 100, 500, 1000, 5000],
      registers: [register],
    });
    
    this.cacheHits = new promClient.Counter({
      name: 'nodejs_cache_hits_total',
      help: 'Total cache hits',
      labelNames: ['cache_type'],
      registers: [register],
    });
  }
  
  recordUserRegistration(source) {
    this.userRegistrations.inc({ source });
  }
  
  recordOrder(value, category) {
    this.orderValues.observe({ product_category: category }, value);
  }
  
  recordCacheHit(cacheType) {
    this.cacheHits.inc({ cache_type: cacheType });
  }
}

module.exports = ApplicationMetrics;

Set up log-based metrics

Configure log parsing to extract metrics from application logs using structured logging.

const winston = require('winston');

const logger = winston.createLogger({
  level: 'info',
  format: winston.format.combine(
    winston.format.timestamp(),
    winston.format.errors({ stack: true }),
    winston.format.json()
  ),
  transports: [
    new winston.transports.File({ 
      filename: '/var/log/nodejs-app/error.log', 
      level: 'error' 
    }),
    new winston.transports.File({ 
      filename: '/var/log/nodejs-app/combined.log' 
    }),
    new winston.transports.Console({
      format: winston.format.simple()
    })
  ],
});

module.exports = logger;

Common issues

SymptomCauseFix
Metrics endpoint returns 404prom-client not configured correctlyCheck app.get('/metrics') route and ensure prom-client is installed
Prometheus can't scrape targetsNetwork connectivity or wrong portVerify with curl http://localhost:3000/metrics
Grafana shows no dataData source not configuredCheck Prometheus connection in Grafana datasources
Alerts not firingAlerting rules syntax errorValidate rules with promtool check rules /etc/prometheus/nodejs_alerts.yml
High memory usage from metricsToo many metric labelsReduce cardinality by limiting dynamic label values
Permission denied errorsIncorrect file ownershipRun sudo chown -R prometheus:prometheus /etc/prometheus

Next steps

Running this in production?

Want this handled for you? Setting this up once is straightforward. Keeping it patched, monitored, backed up and performant 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.