<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Gilbert Mutai]]></title><description><![CDATA[Gilbert Mutai]]></description><link>https://blog.gilbertmutai.com</link><generator>RSS for Node</generator><lastBuildDate>Thu, 28 May 2026 00:35:31 GMT</lastBuildDate><atom:link href="https://blog.gilbertmutai.com/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Building a Secure and Scalable Node.js App with Nginx as a Reverse Proxy and Load Balancer]]></title><description><![CDATA[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 D...]]></description><link>https://blog.gilbertmutai.com/building-a-secure-and-scalable-nodejs-app-with-nginx-as-a-reverse-proxy-and-load-balancer</link><guid isPermaLink="true">https://blog.gilbertmutai.com/building-a-secure-and-scalable-nodejs-app-with-nginx-as-a-reverse-proxy-and-load-balancer</guid><dc:creator><![CDATA[Gilbert Mutai]]></dc:creator><pubDate>Tue, 28 Oct 2025 18:10:48 GMT</pubDate><content:encoded><![CDATA[<hr />
<p>subtitle: "How to deploy containerized Node.js replicas behind Nginx with HTTPS and automatic HTTP-to-HTTPS redirection"</p>
<h2 id="heading-tags-nodejs-nginx-docker-devops-load-balancing-reverse-proxy">tags: ["Node.js", "Nginx", "Docker", "DevOps", "Load Balancing", "Reverse Proxy"]</h2>
<h2 id="heading-introduction">Introduction</h2>
<p>In this post, we’ll build a <strong>mini DevOps project</strong> that demonstrates how to use <strong>Nginx as a reverse proxy and load balancer</strong> for a <strong>containerized Node.js application</strong>.</p>
<p>We’ll deploy <strong>three Node.js replicas</strong>, serve them securely via <strong>HTTPS</strong>, and configure <strong>automatic HTTP-to-HTTPS redirection</strong>. This setup ensures:</p>
<ul>
<li>High availability through load balancing</li>
<li>Secure traffic routing with SSL/TLS</li>
<li>Seamless horizontal scalability</li>
</ul>
<hr />
<h2 id="heading-architecture-overview">Architecture Overview</h2>
<p>Here’s a quick breakdown of what we’ll build:</p>
<pre><code>Client → Nginx (Reverse <span class="hljs-built_in">Proxy</span> + Load Balancer)
          ↳ Node.js App <span class="hljs-number">1</span> (Container)
          ↳ Node.js App <span class="hljs-number">2</span> (Container)
          ↳ Node.js App <span class="hljs-number">3</span> (Container)
</code></pre><p>Nginx will distribute requests evenly among the Node.js containers and handle SSL termination.</p>
<hr />
<h2 id="heading-step-1-create-the-nodejs-application">Step 1: Create the Node.js Application</h2>
<p>Let’s start by creating a simple Express.js app.</p>
<pre><code class="lang-bash">mkdir node-nginx-demo
<span class="hljs-built_in">cd</span> node-nginx-demo
npm init -y
npm install express
</code></pre>
<p>Then create a file <code>app.js</code>:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> express = <span class="hljs-built_in">require</span>(<span class="hljs-string">"express"</span>);
<span class="hljs-keyword">const</span> app = express();
<span class="hljs-keyword">const</span> port = <span class="hljs-number">3000</span>;

app.get(<span class="hljs-string">"/"</span>, <span class="hljs-function">(<span class="hljs-params">req, res</span>) =&gt;</span> {
  res.send(<span class="hljs-string">`Hello from Node.js container!  Hostname: <span class="hljs-subst">${process.env.HOSTNAME}</span>`</span>);
});

app.listen(port, <span class="hljs-function">() =&gt;</span> {
  <span class="hljs-built_in">console</span>.log(<span class="hljs-string">`App running on port <span class="hljs-subst">${port}</span>`</span>);
});
</code></pre>
<hr />
<h2 id="heading-step-2-dockerize-the-application">Step 2: Dockerize the Application</h2>
<p>Create a <code>Dockerfile</code>:</p>
<pre><code class="lang-dockerfile"><span class="hljs-keyword">FROM</span> node:<span class="hljs-number">18</span>-alpine

<span class="hljs-keyword">WORKDIR</span><span class="bash"> /app</span>
<span class="hljs-keyword">COPY</span><span class="bash"> package*.json ./</span>
<span class="hljs-keyword">RUN</span><span class="bash"> npm install</span>
<span class="hljs-keyword">COPY</span><span class="bash"> . .</span>

<span class="hljs-keyword">EXPOSE</span> <span class="hljs-number">3000</span>
<span class="hljs-keyword">CMD</span><span class="bash"> [<span class="hljs-string">"node"</span>, <span class="hljs-string">"app.js"</span>]</span>
</code></pre>
<p>Then build the image:</p>
<pre><code class="lang-bash">docker build -t node-demo-app .
</code></pre>
<p>Now create three containers (replicas):</p>
<pre><code class="lang-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
</code></pre>
<p>Each app instance is accessible on ports <code>3001</code>, <code>3002</code>, and <code>3003</code>.</p>
<hr />
<h2 id="heading-step-3-configure-nginx-as-reverse-proxy-and-load-balancer">Step 3: Configure Nginx as Reverse Proxy and Load Balancer</h2>
<p>Create an <code>nginx.conf</code> file:</p>
<pre><code class="lang-nginx"><span class="hljs-section">events</span> {}

<span class="hljs-section">http</span> {
  <span class="hljs-attribute">upstream</span> node_app {
    <span class="hljs-attribute">server</span> app1:<span class="hljs-number">3000</span>;
    <span class="hljs-attribute">server</span> app2:<span class="hljs-number">3000</span>;
    <span class="hljs-attribute">server</span> app3:<span class="hljs-number">3000</span>;
  }

  <span class="hljs-section">server</span> {
    <span class="hljs-attribute">listen</span> <span class="hljs-number">80</span>;
    <span class="hljs-attribute">server_name</span> _;

    <span class="hljs-comment"># Redirect all HTTP traffic to HTTPS</span>
    <span class="hljs-attribute">return</span> <span class="hljs-number">301</span> https://<span class="hljs-variable">$host</span><span class="hljs-variable">$request_uri</span>;
  }

  <span class="hljs-section">server</span> {
    <span class="hljs-attribute">listen</span> <span class="hljs-number">443</span> ssl;
    <span class="hljs-attribute">server_name</span> _;

    <span class="hljs-attribute">ssl_certificate</span>     /etc/ssl/certs/selfsigned.crt;
    <span class="hljs-attribute">ssl_certificate_key</span> /etc/ssl/private/selfsigned.key;

    <span class="hljs-attribute">location</span> / {
      <span class="hljs-attribute">proxy_pass</span> http://node_app;
      <span class="hljs-attribute">proxy_set_header</span> Host <span class="hljs-variable">$host</span>;
      <span class="hljs-attribute">proxy_set_header</span> X-Real-IP <span class="hljs-variable">$remote_addr</span>;
      <span class="hljs-attribute">proxy_set_header</span> X-Forwarded-For <span class="hljs-variable">$proxy_add_x_forwarded_for</span>;
      <span class="hljs-attribute">proxy_set_header</span> X-Forwarded-Proto <span class="hljs-variable">$scheme</span>;
    }
  }
}
</code></pre>
<hr />
<h2 id="heading-step-4-generate-a-self-signed-ssl-certificate">Step 4: Generate a Self-Signed SSL Certificate</h2>
<p>For testing, generate a certificate inside your project:</p>
<pre><code class="lang-bash">mkdir ssl
<span class="hljs-built_in">cd</span> ssl
openssl req -x509 -nodes -days 365 -newkey rsa:2048   -keyout selfsigned.key -out selfsigned.crt   -subj <span class="hljs-string">"/C=KE/ST=Nairobi/L=Nairobi/O=DevOps/CN=localhost"</span>
</code></pre>
<hr />
<h2 id="heading-step-5-create-a-docker-compose-file">Step 5: Create a Docker Compose File</h2>
<p>Let’s manage all services together using Docker Compose.</p>
<p>Create a <code>docker-compose.yml</code>:</p>
<pre><code class="lang-yaml"><span class="hljs-attr">version:</span> <span class="hljs-string">'3'</span>
<span class="hljs-attr">services:</span>
  <span class="hljs-attr">app1:</span>
    <span class="hljs-attr">build:</span> <span class="hljs-string">.</span>
  <span class="hljs-attr">app2:</span>
    <span class="hljs-attr">build:</span> <span class="hljs-string">.</span>
  <span class="hljs-attr">app3:</span>
    <span class="hljs-attr">build:</span> <span class="hljs-string">.</span>

  <span class="hljs-attr">nginx:</span>
    <span class="hljs-attr">image:</span> <span class="hljs-string">nginx:latest</span>
    <span class="hljs-attr">ports:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">"80:80"</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">"443:443"</span>
    <span class="hljs-attr">volumes:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">./nginx.conf:/etc/nginx/nginx.conf:ro</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">./ssl:/etc/ssl/:ro</span>
    <span class="hljs-attr">depends_on:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">app1</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">app2</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">app3</span>
</code></pre>
<p>Then run everything:</p>
<pre><code class="lang-bash">docker-compose up -d
</code></pre>
<hr />
<h2 id="heading-step-6-test-the-setup">Step 6: Test the Setup</h2>
<p>Visit:</p>
<ul>
<li><strong>http://localhost</strong> → will automatically redirect to <strong>https://localhost</strong></li>
<li><strong>https://localhost</strong> → you’ll see “Hello from Node.js container!” and the hostname changes on refresh — showing Nginx load balancing across your replicas.</li>
</ul>
<hr />
<h2 id="heading-step-7-verify-load-balancing">Step 7: Verify Load Balancing</h2>
<p>Run:</p>
<pre><code class="lang-bash">docker logs app1 | tail -n 2
docker logs app2 | tail -n 2
docker logs app3 | tail -n 2
</code></pre>
<p>You’ll notice that requests are distributed evenly across all containers — confirming load balancing works correctly.</p>
<hr />
<h2 id="heading-key-takeaways">Key Takeaways</h2>
<ul>
<li><strong>Nginx Reverse Proxy:</strong> Hides backend details and distributes load.  </li>
<li><strong>Load Balancing:</strong> Ensures high availability and reliability.  </li>
<li><strong>SSL/TLS:</strong> Secures communication via HTTPS.  </li>
<li><strong>Docker Compose:</strong> Simplifies multi-container orchestration.</li>
</ul>
<p>This setup mimics a real-world architecture where containerized apps run behind a reverse proxy with SSL termination — a fundamental DevOps skill.</p>
<hr />
<h2 id="heading-conclusion">Conclusion</h2>
<p>You’ve just built a secure, scalable, and production-like environment using <strong>Node.js</strong>, <strong>Nginx</strong>, and <strong>Docker</strong>.  </p>
<p>This project is a great foundation to explore:</p>
<ul>
<li>Scaling with <strong>Kubernetes</strong></li>
<li>Automated SSL with <strong>Let’s Encrypt</strong></li>
<li>Continuous deployment pipelines for app updates</li>
</ul>
<p>If you enjoyed this walkthrough, follow me for more DevOps hands-on projects and cloud automation tutorials.  </p>
<hr />
<p><em>Written by <a target="_blank" href="https://hashnode.com/@gilbertmutai">Gilbert Mutai</a> — DevOps Engineer | Cloud Support Engineer</em></p>
]]></content:encoded></item></channel></rss>