BLOG POSTS
    MangoHost Blog / How to Set Up a Node.js Application for Production on Ubuntu 24
How to Set Up a Node.js Application for Production on Ubuntu 24

How to Set Up a Node.js Application for Production on Ubuntu 24

Setting up a Node.js application for production on Ubuntu 24 is a critical skill that every developer and system administrator should master. Unlike development environments where quick restarts and manual processes are acceptable, production deployments require robust process management, security hardening, reverse proxy configuration, and automated startup procedures. This guide will walk you through the complete setup process, from initial server preparation to advanced monitoring configurations, while covering common pitfalls and troubleshooting scenarios that you’ll inevitably encounter in real-world deployments.

Understanding Node.js Production Requirements

Running Node.js in production differs significantly from development environments. Your application needs to handle crashes gracefully, restart automatically, serve multiple concurrent requests efficiently, and maintain security standards. The key components include:

  • Process Management: Tools like PM2 or systemd to keep your application running
  • Reverse Proxy: Nginx or Apache to handle static files and SSL termination
  • Environment Configuration: Proper separation of development and production settings
  • Security Measures: Firewall configuration, user permissions, and dependency management
  • Monitoring and Logging: Application performance monitoring and centralized logging

Initial Server Setup and Node.js Installation

Start with a fresh Ubuntu 24 server. Whether you’re using a VPS or dedicated server, the initial setup process remains consistent.

First, update your system and install essential packages:

sudo apt update && sudo apt upgrade -y
sudo apt install curl wget gnupg2 software-properties-common apt-transport-https ca-certificates lsb-release -y

Install Node.js using NodeSource repository for the latest LTS version:

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

# Verify installation
node --version
npm --version

Create a dedicated user for your Node.js application. Never run production applications as root:

sudo adduser nodeapp
sudo usermod -aG sudo nodeapp
su - nodeapp

Application Deployment and Configuration

Let’s deploy a sample Express.js application to demonstrate the complete process. Create your application directory:

mkdir -p /home/nodeapp/myapp
cd /home/nodeapp/myapp

Here’s a basic Express application structure for production:

// app.js
const express = require('express');
const helmet = require('helmet');
const compression = require('compression');
const rateLimit = require('express-rate-limit');

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

// Security middleware
app.use(helmet());
app.use(compression());

// Rate limiting
const limiter = rateLimit({
  windowMs: 15 * 60 * 1000, // 15 minutes
  max: 100 // limit each IP to 100 requests per windowMs
});
app.use(limiter);

// Basic routes
app.get('/', (req, res) => {
  res.json({
    message: 'Production Node.js App Running',
    environment: process.env.NODE_ENV,
    timestamp: new Date().toISOString()
  });
});

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

// Error handling
app.use((err, req, res, next) => {
  console.error('Error:', err.stack);
  res.status(500).json({ error: 'Something went wrong!' });
});

app.listen(PORT, '127.0.0.1', () => {
  console.log(`Server running on port ${PORT}`);
});

module.exports = app;

Create the package.json file with production dependencies:

{
  "name": "production-node-app",
  "version": "1.0.0",
  "description": "Production-ready Node.js application",
  "main": "app.js",
  "scripts": {
    "start": "NODE_ENV=production node app.js",
    "dev": "NODE_ENV=development nodemon app.js",
    "test": "jest"
  },
  "dependencies": {
    "express": "^4.18.2",
    "helmet": "^7.0.0",
    "compression": "^1.7.4",
    "express-rate-limit": "^6.7.0"
  },
  "devDependencies": {
    "nodemon": "^3.0.1",
    "jest": "^29.5.0"
  },
  "engines": {
    "node": ">=18.0.0"
  }
}

Install dependencies and test the application:

npm install --only=production
NODE_ENV=production node app.js

Process Management with PM2

PM2 is the most popular process manager for Node.js applications in production. Install it globally:

sudo npm install -g pm2

Create a PM2 ecosystem file for advanced configuration:

// ecosystem.config.js
module.exports = {
  apps: [{
    name: 'myapp',
    script: './app.js',
    instances: 'max',
    exec_mode: 'cluster',
    env: {
      NODE_ENV: 'production',
      PORT: 3000
    },
    log_date_format: 'YYYY-MM-DD HH:mm Z',
    error_file: './logs/err.log',
    out_file: './logs/out.log',
    log_file: './logs/combined.log',
    time: true,
    max_memory_restart: '1G',
    node_args: '--max-old-space-size=1024'
  }]
};

Create the logs directory and start your application:

mkdir logs
pm2 start ecosystem.config.js
pm2 save
pm2 startup

The startup command will provide a command to run as root – execute it to enable automatic startup on system reboot.

Nginx Reverse Proxy Configuration

Install and configure Nginx as a reverse proxy:

sudo apt install nginx -y
sudo systemctl start nginx
sudo systemctl enable nginx

Create a virtual host configuration:

sudo nano /etc/nginx/sites-available/myapp

Add the following configuration:

server {
    listen 80;
    server_name your-domain.com www.your-domain.com;

    # Security headers
    add_header X-Frame-Options "SAMEORIGIN" always;
    add_header X-XSS-Protection "1; mode=block" always;
    add_header X-Content-Type-Options "nosniff" always;

    # Gzip compression
    gzip on;
    gzip_vary on;
    gzip_min_length 1024;
    gzip_types text/plain text/css text/xml text/javascript application/javascript application/xml+rss application/json;

    location / {
        proxy_pass http://127.0.0.1:3000;
        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;
        proxy_connect_timeout 60s;
        proxy_send_timeout 60s;
        proxy_read_timeout 60s;
    }

    # Static file serving (if needed)
    location /static/ {
        alias /home/nodeapp/myapp/public/;
        expires 30d;
        add_header Cache-Control "public, immutable";
    }
}

Enable the site and test the configuration:

sudo ln -s /etc/nginx/sites-available/myapp /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx

SSL Certificate Setup with Let’s Encrypt

Install Certbot for free SSL certificates:

sudo apt install certbot python3-certbot-nginx -y
sudo certbot --nginx -d your-domain.com -d www.your-domain.com

Set up automatic renewal:

sudo crontab -e
# Add this line:
0 12 * * * /usr/bin/certbot renew --quiet

Firewall Configuration

Configure UFW (Uncomplicated Firewall) for basic security:

sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow ssh
sudo ufw allow 'Nginx Full'
sudo ufw --force enable
sudo ufw status

Performance Monitoring and Optimization

Install PM2 monitoring tools:

pm2 install pm2-logrotate
pm2 set pm2-logrotate:max_size 10M
pm2 set pm2-logrotate:retain 30

Here’s a comparison of different process management solutions:

Feature PM2 Forever Systemd Docker
Cluster Mode ✓ (with orchestration)
Built-in Monitoring
Log Management
Zero-downtime Reload
Resource Usage Medium Low Low Medium-High

Environment Variables and Security

Create a secure environment configuration:

sudo nano /home/nodeapp/myapp/.env
NODE_ENV=production
PORT=3000
DB_HOST=localhost
DB_USER=myapp_user
DB_PASS=secure_password_here
JWT_SECRET=your_jwt_secret_here
SESSION_SECRET=your_session_secret_here

Secure the environment file:

sudo chown nodeapp:nodeapp /home/nodeapp/myapp/.env
sudo chmod 600 /home/nodeapp/myapp/.env

Install and configure dotenv for environment variable management:

npm install dotenv --save

Update your app.js to use environment variables:

require('dotenv').config();
// ... rest of your application code

Database Configuration and Security

If you’re using MongoDB, install and secure it:

sudo apt install -y mongodb
sudo systemctl start mongodb
sudo systemctl enable mongodb

# Create application-specific database user
mongo
use myapp
db.createUser({
  user: "myapp_user",
  pwd: "secure_password_here",
  roles: ["readWrite"]
})
exit

For PostgreSQL setup:

sudo apt install postgresql postgresql-contrib -y
sudo -u postgres createuser --interactive myapp_user
sudo -u postgres createdb myapp_db
sudo -u postgres psql -c "ALTER USER myapp_user PASSWORD 'secure_password_here';"

Common Issues and Troubleshooting

Here are the most frequent problems you’ll encounter and their solutions:

Issue 1: Application crashes after deployment

# Check PM2 logs
pm2 logs myapp

# Check system logs
sudo journalctl -u nginx -f
sudo tail -f /var/log/nginx/error.log

Issue 2: Port 3000 already in use

# Find process using the port
sudo lsof -i :3000
sudo netstat -tulpn | grep :3000

# Kill the process
sudo kill -9 PID

Issue 3: Nginx 502 Bad Gateway

  • Verify Node.js application is running: pm2 status
  • Check if the application is listening on 127.0.0.1:3000
  • Verify Nginx configuration: sudo nginx -t
  • Check firewall rules: sudo ufw status

Issue 4: High memory usage

# Monitor memory usage
pm2 monit

# Restart application if memory limit exceeded
pm2 restart myapp

# Adjust memory limit in ecosystem.config.js
max_memory_restart: '512M'

Performance Optimization Strategies

Enable Node.js performance optimizations:

# Update ecosystem.config.js with performance settings
module.exports = {
  apps: [{
    name: 'myapp',
    script: './app.js',
    instances: 'max',
    exec_mode: 'cluster',
    node_args: [
      '--max-old-space-size=2048',
      '--optimize-for-size'
    ],
    env: {
      NODE_ENV: 'production',
      UV_THREADPOOL_SIZE: 128
    }
  }]
};

Configure Nginx for better performance:

# Add to /etc/nginx/nginx.conf in http block
worker_processes auto;
worker_connections 1024;
keepalive_timeout 65;
client_max_body_size 50M;

# Enable caching
proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=my_cache:10m max_size=10g 
                 inactive=60m use_temp_path=off;

Backup and Deployment Automation

Create a deployment script for easy updates:

#!/bin/bash
# deploy.sh

APP_DIR="/home/nodeapp/myapp"
BACKUP_DIR="/home/nodeapp/backups/$(date +%Y%m%d_%H%M%S)"

echo "Creating backup..."
mkdir -p $BACKUP_DIR
cp -r $APP_DIR $BACKUP_DIR

echo "Pulling latest code..."
cd $APP_DIR
git pull origin main

echo "Installing dependencies..."
npm ci --only=production

echo "Running tests..."
npm test

echo "Reloading application..."
pm2 reload myapp

echo "Deployment complete!"

Make the script executable:

chmod +x deploy.sh

Monitoring and Logging Best Practices

Set up comprehensive logging:

// logger.js
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()
  ),
  defaultMeta: { service: 'myapp' },
  transports: [
    new winston.transports.File({ filename: 'logs/error.log', level: 'error' }),
    new winston.transports.File({ filename: 'logs/combined.log' })
  ]
});

if (process.env.NODE_ENV !== 'production') {
  logger.add(new winston.transports.Console({
    format: winston.format.simple()
  }));
}

module.exports = logger;

Install monitoring tools:

npm install winston --save
pm2 install pm2-server-monit

This production setup provides a robust foundation for running Node.js applications on Ubuntu 24. The configuration handles automatic restarts, load balancing through clustering, security hardening, and comprehensive monitoring. For additional resources, check the official Node.js production guide and PM2 documentation. Remember to regularly update your dependencies, monitor performance metrics, and implement proper backup strategies for production reliability.



This article incorporates information and material from various online sources. We acknowledge and appreciate the work of all original authors, publishers, and websites. While every effort has been made to appropriately credit the source material, any unintentional oversight or omission does not constitute a copyright infringement. All trademarks, logos, and images mentioned are the property of their respective owners. If you believe that any content used in this article infringes upon your copyright, please contact us immediately for review and prompt action.

This article is intended for informational and educational purposes only and does not infringe on the rights of the copyright owners. If any copyrighted material has been used without proper credit or in violation of copyright laws, it is unintentional and we will rectify it promptly upon notification. Please note that the republishing, redistribution, or reproduction of part or all of the contents in any form is prohibited without express written permission from the author and website owner. For permissions or further inquiries, please contact us.

Leave a reply

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