Skip to main content

Command Palette

Search for a command to run...

Building a Secure and Scalable Node.js App with Nginx as a Reverse Proxy and Load Balancer

Published
4 min read

subtitle: "How to deploy containerized Node.js replicas behind Nginx with HTTPS and automatic HTTP-to-HTTPS redirection"

tags: ["Node.js", "Nginx", "Docker", "DevOps", "Load Balancing", "Reverse Proxy"]

Introduction

In this post, we’ll build a mini DevOps project that demonstrates how to use Nginx as a reverse proxy and load balancer for a containerized Node.js application.

We’ll deploy three Node.js replicas, serve them securely via HTTPS, and configure automatic HTTP-to-HTTPS redirection. This setup ensures:

  • High availability through load balancing
  • Secure traffic routing with SSL/TLS
  • Seamless horizontal scalability

Architecture Overview

Here’s a quick breakdown of what we’ll build:

Client → Nginx (Reverse Proxy + Load Balancer)
          ↳ Node.js App 1 (Container)
          ↳ Node.js App 2 (Container)
          ↳ Node.js App 3 (Container)

Nginx will distribute requests evenly among the Node.js containers and handle SSL termination.


Step 1: Create the Node.js Application

Let’s start by creating a simple Express.js app.

mkdir node-nginx-demo
cd node-nginx-demo
npm init -y
npm install express

Then create a file app.js:

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

app.get("/", (req, res) => {
  res.send(`Hello from Node.js container!  Hostname: ${process.env.HOSTNAME}`);
});

app.listen(port, () => {
  console.log(`App running on port ${port}`);
});

Step 2: Dockerize the Application

Create a Dockerfile:

FROM node:18-alpine

WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .

EXPOSE 3000
CMD ["node", "app.js"]

Then build the image:

docker build -t node-demo-app .

Now create three containers (replicas):

docker run -d --name app1 -p 3001:3000 node-demo-app
docker run -d --name app2 -p 3002:3000 node-demo-app
docker run -d --name app3 -p 3003:3000 node-demo-app

Each app instance is accessible on ports 3001, 3002, and 3003.


Step 3: Configure Nginx as Reverse Proxy and Load Balancer

Create an nginx.conf file:

events {}

http {
  upstream node_app {
    server app1:3000;
    server app2:3000;
    server app3:3000;
  }

  server {
    listen 80;
    server_name _;

    # Redirect all HTTP traffic to HTTPS
    return 301 https://$host$request_uri;
  }

  server {
    listen 443 ssl;
    server_name _;

    ssl_certificate     /etc/ssl/certs/selfsigned.crt;
    ssl_certificate_key /etc/ssl/private/selfsigned.key;

    location / {
      proxy_pass http://node_app;
      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;
    }
  }
}

Step 4: Generate a Self-Signed SSL Certificate

For testing, generate a certificate inside your project:

mkdir ssl
cd ssl
openssl req -x509 -nodes -days 365 -newkey rsa:2048   -keyout selfsigned.key -out selfsigned.crt   -subj "/C=KE/ST=Nairobi/L=Nairobi/O=DevOps/CN=localhost"

Step 5: Create a Docker Compose File

Let’s manage all services together using Docker Compose.

Create a docker-compose.yml:

version: '3'
services:
  app1:
    build: .
  app2:
    build: .
  app3:
    build: .

  nginx:
    image: nginx:latest
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf:ro
      - ./ssl:/etc/ssl/:ro
    depends_on:
      - app1
      - app2
      - app3

Then run everything:

docker-compose up -d

Step 6: Test the Setup

Visit:

  • http://localhost → will automatically redirect to https://localhost
  • https://localhost → you’ll see “Hello from Node.js container!” and the hostname changes on refresh — showing Nginx load balancing across your replicas.

Step 7: Verify Load Balancing

Run:

docker logs app1 | tail -n 2
docker logs app2 | tail -n 2
docker logs app3 | tail -n 2

You’ll notice that requests are distributed evenly across all containers — confirming load balancing works correctly.


Key Takeaways

  • Nginx Reverse Proxy: Hides backend details and distributes load.
  • Load Balancing: Ensures high availability and reliability.
  • SSL/TLS: Secures communication via HTTPS.
  • Docker Compose: Simplifies multi-container orchestration.

This setup mimics a real-world architecture where containerized apps run behind a reverse proxy with SSL termination — a fundamental DevOps skill.


Conclusion

You’ve just built a secure, scalable, and production-like environment using Node.js, Nginx, and Docker.

This project is a great foundation to explore:

  • Scaling with Kubernetes
  • Automated SSL with Let’s Encrypt
  • Continuous deployment pipelines for app updates

If you enjoyed this walkthrough, follow me for more DevOps hands-on projects and cloud automation tutorials.


Written by Gilbert Mutai — DevOps Engineer | Cloud Support Engineer