Setting up a Node.js application for production on Ubuntu 22.04 involves several crucial steps to ensure your application runs efficiently, securely, and reliably. This comprehensive guide will walk you through the process, from updating your system to deploying your application using PM2, Nginx, and securing it with SSL.

1: Deploying a Cloud Instance on Shape.host

  • Log in to Shape.host Dashboard:
  • Navigate to the Shape.host website and log in to your account.
  • Create a New Instance:
  • Click on the “Create” button located at the top right corner of the dashboard.
  • From the dropdown menu, select “Instances”.
Shape.host Resources dashboard showing the Create menu and instance rebuild controls.
  • Select Instance Location:
  • Choose the desired location for your server. For this tutorial, we’ll select “New York, USA”.
Shape.host Create New Instance form with the available data center locations.
  • Choose a Plan:
  • Select a plan that fits your requirements. For example, you might choose a plan with 2 cores CPU, 2 GB Memory, and 50 GB SSD disk space.
  • Select an Operating System:
  • Scroll down to the “Choose an image” section and select “Ubuntu 22.04”.
Shape.host instance form with a server plan and Ubuntu image selected.
  • Configure Additional Options:
  • (Optional) You can configure additional options like User Data Configuration and IPv6 Networking.
  • Enter a hostname for your instance, e.g., “Tutorial Ubuntu”.
  • Click on the “Create instance” button to deploy the instance.
Shape.host instance form showing hostname, instance count and the Create Instance button.

2: Connecting to Your Instance

  • Retrieve SSH Credentials:
  • Note the IP address of your newly created instance from the Shape.host dashboard.
  • Connect via SSH:
  • Open a terminal on your local machine.
  • Use the following command to connect to your instance:
    ssh root@your_instance_ip
  • Replace your_instance_ip with the actual IP address of your instance.

3:Update and Upgrade System Packages

First, ensure your system is up-to-date:

apt update && apt upgrade -y
Terminal showing APT package management commands and output.

4:Install Node.js and npm

We’ll use the NodeSource repository to install the latest LTS version of Node.js:

curl -fsSL https://deb.nodesource.com/setup_20.x | sudo bash -
sudo apt-get install -y nodejs
Terminal showing APT package management commands and output.

Verify the installation:

node --version
npm --version
Terminal displaying Node.js and npm version numbers.

5:Install Build Essentials

Some Node.js packages require compilation, so install build essentials:

apt install build-essential -y
Terminal showing APT package management commands and output.

6:Create a Node.js Application

Let’s create a simple Express application:

mkdir -p /var/www/myapp
cd /var/www/myapp
npm init -y
npm install express
Terminal showing npm package installation messages.

Create an app.js file:

nano app.js
Terminal showing nano commands or output.

Add the following content:

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

app.get('/', (req, res) => {
  res.send('Hello World from my Node.js app!');
});

app.listen(port, () => {
  console.log(`App listening at http://localhost:${port}`);
});
Terminal showing the GNU nano editor and configuration text.

7:Install PM2

PM2 is a process manager for Node.js applications:

npm install pm2@latest -g
Terminal showing PM2 application process management output.

Start your application with PM2:

pm2 start app.js --name myapp
Terminal showing PM2 application process management output.

Set PM2 to start on system boot:

pm2 startup systemd
Terminal showing PM2 application process management output.

Save the current PM2 process list:

pm2 save
Terminal showing PM2 application process management output.

8:Install and Configure Nginx

Install Nginx:

apt install nginx -y
Terminal showing PM2 application process management output.

Create an Nginx server block configuration:

nano /etc/nginx/sites-available/myapp
Terminal showing nano commands or output.

Add the following configuration:

server {
    listen 80;
    server_name yourdomain.com www.yourdomain.com;

    location / {
        proxy_pass http://localhost:3000;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection 'upgrade';
        proxy_set_header Host $host;
        proxy_cache_bypass $http_upgrade;
    }
}
Terminal showing the GNU nano editor and configuration text.

Enable the configuration:

ln -s /etc/nginx/sites-available/myapp /etc/nginx/sites-enabled/
Terminal showing nano commands or output.

Test Nginx configuration:

nginx -t
Terminal showing Nginx configuration test output.

If the test is successful, restart Nginx:

systemctl restart nginx

9:Set Up Firewall

Allow HTTP, HTTPS, and SSH traffic:

ufw allow 'Nginx Full'
ufw allow OpenSSH
ufw enable
Terminal showing Nginx configuration test output.

10:Secure Your Application with SSL

Install Certbot:

apt install certbot python3-certbot-nginx -y
Terminal showing APT package management commands and output.

Obtain an SSL certificate:

certbot --nginx -d yourdomain.com
Terminal showing Certbot certificate commands and messages.

Follow the prompts to complete the certificate installation.

11:Set Up Environment Variables

Create a .env file for environment variables:

nano /var/www/myapp/.env
Terminal showing Certbot certificate commands and messages.

Add your variables:

PORT=3000
NODE_ENV=production
Terminal showing the GNU nano editor and configuration text.

Modify your app.js to use these variables. Open the file:

nano /var/www/myapp/app.js
Terminal showing nano commands or output.

Update the content to:

require('dotenv').config();
const express = require('express');
const app = express();
const port = process.env.PORT || 3000;

app.get('/', (req, res) => {
  res.send('Hello World from my Node.js app!');
});

app.listen(port, () => {
  console.log(`App listening at http://localhost:${port}`);
});
Terminal showing the GNU nano editor and configuration text.

Install the dotenv package:

npm install dotenv
Terminal showing npm package installation messages.

12:Implement Basic Security Measures

Install and set up common security packages:

npm install helmet cors
Terminal showing an npm package installation command.

Modify your app.js. Open the file:

nano /var/www/myapp/app.js
Terminal showing the command to edit the Node.js application file.

Update the content to:

require('dotenv').config();
const express = require('express');
const helmet = require('helmet');
const cors = require('cors');

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

app.use(helmet());
app.use(cors());

app.get('/', (req, res) => {
  res.send('Hello World from my secure Node.js app!');
});

app.listen(port, () => {
  console.log(`App listening at http://localhost:${port}`);
});
Terminal showing the GNU nano editor and configuration text.

13:Set Up Monitoring

Install a monitoring tool like Keymetrics:

pm2 install pm2-server-monit
pm2 link <secret-key> <public-key>
Terminal showing PM2 application process management output.

Replace <secret-key> and <public-key> with your Keymetrics keys.

14:Implement Logging

Install Winston for logging:

npm install winston
Terminal showing PM2 application process management output.

Create a logger.js file:

nano /var/www/myapp/logger.js
Terminal showing nano commands or output.

Add the following content:

const winston = require('winston');

const logger = winston.createLogger({
  level: 'info',
  format: winston.format.json(),
  defaultMeta: { service: 'user-service' },
  transports: [
    new winston.transports.File({ filename: 'error.log', level: 'error' }),
    new winston.transports.File({ filename: 'combined.log' }),
  ],
});

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

module.exports = logger;
Terminal showing the GNU nano editor and configuration text.

Use the logger in your app.js. Open the file:

nano /var/www/myapp/app.js
Terminal showing nano commands or output.

Update the content to:

require('dotenv').config();
const express = require('express');
const helmet = require('helmet');
const cors = require('cors');
const logger = require('./logger');

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

app.use(helmet());
app.use(cors());

app.get('/', (req, res) => {
  logger.info('Home route accessed');
  res.send('Hello World from my secure Node.js app!');
});

app.listen(port, () => {
  logger.info(`App listening at http://localhost:${port}`);
});
Terminal showing the GNU nano editor and configuration text.

15:Set Up Database Connection (Optional)

If your app uses a database, install the appropriate package (e.g., for MongoDB):

npm install mongoose
Terminal showing nano commands or output.

Open your app.js file:

nano /var/www/myapp/app.js
Terminal showing nano commands or output.

Add database connection to your app.js:

require('dotenv').config();
const express = require('express');
const helmet = require('helmet');
const cors = require('cors');
const logger = require('./logger');
const mongoose = require('mongoose');

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

mongoose.connect(process.env.MONGODB_URI, { useNewUrlParser: true, useUnifiedTopology: true })
  .then(() => logger.info('MongoDB connected'))
  .catch(err => logger.error('MongoDB connection error:', err));

app.use(helmet());
app.use(cors());

app.get('/', (req, res) => {
  logger.info('Home route accessed');
  res.send('Hello World from my secure Node.js app!');
});

app.listen(port, () => {
  logger.info(`App listening at http://localhost:${port}`);
});
Terminal showing the GNU nano editor and configuration text.

Remember to add the MONGODB_URI to your .env file:

nano /var/www/myapp/.env
Terminal showing nano commands or output.

Add the MongoDB URI:

MONGODB_URI=mongodb://username:password@host:port/database
Terminal showing the GNU nano editor and configuration text.

By following these steps, you’ve set up a production-ready Node.js application on Ubuntu 22.04. Your application is now running with process management, reverse proxy, SSL encryption, basic security measures, monitoring, and logging.

For those seeking a more streamlined experience in deploying Node.js applications, Shape.host offers excellent Cloud VPS services. Their Ubuntu-based VPS solutions provide the perfect foundation for running Node.js applications in production, offering the flexibility and resources needed for optimal performance. With Shape.host’s Cloud VPS, you can focus on developing and scaling your Node.js applications, rather than worrying about the intricacies of server management and setup.