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

---
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.

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

Then create a file `app.js`:

```javascript
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`:

```dockerfile
FROM node:18-alpine

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

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

Then build the image:

```bash
docker build -t node-demo-app .
```

Now create three containers (replicas):

```bash
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:

```nginx
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:

```bash
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`:

```yaml
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:

```bash
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:

```bash
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](https://hashnode.com/@gilbertmutai) — DevOps Engineer | Cloud Support Engineer*
