<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Nginx |</title><link>https://ad25aderram.github.io/my-portfolio/tags/nginx/</link><atom:link href="https://ad25aderram.github.io/my-portfolio/tags/nginx/index.xml" rel="self" type="application/rss+xml"/><description>Nginx</description><generator>HugoBlox Kit (https://hugoblox.com)</generator><language>en-us</language><lastBuildDate>Fri, 10 Apr 2026 00:00:00 +0000</lastBuildDate><image><url>https://ad25aderram.github.io/my-portfolio/media/icon_hu_b93c5070c370bd46.png</url><title>Nginx</title><link>https://ad25aderram.github.io/my-portfolio/tags/nginx/</link></image><item><title>Nginx Load Balancer &amp; Reverse Proxy</title><link>https://ad25aderram.github.io/my-portfolio/projects/nginx-load-balancer/</link><pubDate>Fri, 10 Apr 2026 00:00:00 +0000</pubDate><guid>https://ad25aderram.github.io/my-portfolio/projects/nginx-load-balancer/</guid><description>&lt;p&gt;A hands-on deep-dive into how production traffic actually gets distributed — built from scratch using Nginx as a reverse proxy in front of three Dockerized Node.js app instances.&lt;/p&gt;
&lt;h2 id="overview"&gt;Overview&lt;/h2&gt;
&lt;p&gt;The goal was to understand load balancing not just in theory but by building and operating it. The result is a fully containerized system where Nginx round-robins incoming HTTPS requests across three identical Node.js instances, each running in its own Docker container — with health checks, SSL termination, and a live dashboard that makes the load balancing visible in real time.&lt;/p&gt;
&lt;h2 id="approach"&gt;Approach&lt;/h2&gt;
&lt;p&gt;Each app instance is a lightweight Express server, containerized with Docker and orchestrated via Docker Compose. Nginx sits in front of all three, handling SSL termination and distributing requests using round-robin by default.&lt;/p&gt;
&lt;p&gt;Key areas explored:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Reverse proxying&lt;/strong&gt; — configuring Nginx to forward requests to upstream containers by service name over Docker&amp;rsquo;s internal network&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Load balancing algorithms&lt;/strong&gt; — round-robin as the default, with awareness of alternatives like &lt;code&gt;least_conn&lt;/code&gt; and &lt;code&gt;ip_hash&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;SSL/TLS&lt;/strong&gt; — terminating HTTPS at the Nginx layer so upstream apps only deal with plain HTTP internally&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Docker health checks&lt;/strong&gt; — using &lt;code&gt;wget&lt;/code&gt; to probe each container&amp;rsquo;s &lt;code&gt;/health&lt;/code&gt; endpoint, with configurable interval, timeout, retries, and start period&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Container networking&lt;/strong&gt; — understanding the difference between host-mapped ports and internal container ports, and why health checks use port &lt;code&gt;3000&lt;/code&gt; not &lt;code&gt;3001/3002/3003&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Server-side templating&lt;/strong&gt; — injecting the &lt;code&gt;APP_NAME&lt;/code&gt; environment variable into HTML at request time so the dashboard shows which instance is serving&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="architecture"&gt;Architecture&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;[Client / Browser]
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ↓
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; [Nginx :443] ← SSL termination + round-robin
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; / | \
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;App1 App2 App3 ← Node.js containers on internal port 3000
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;:3001 :3002 :3003 ← host-mapped for direct debug access
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;em&gt;Each reload through Nginx cycles to the next upstream instance — visible live on the dashboard.&lt;/em&gt;&lt;/p&gt;
&lt;h2 id="health-check-setup"&gt;Health Check Setup&lt;/h2&gt;
&lt;p&gt;Every container runs a health check via Docker Compose:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;healthcheck&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;test&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;CMD&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;wget&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;-qO-&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;http://localhost:3000/health&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;interval&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;30s&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;5s&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;retries&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;start_period&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;10s&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The &lt;code&gt;/health&lt;/code&gt; endpoint on each app logs every probe hit in memory, which the frontend fetches every 15 seconds via &lt;code&gt;/api/info&lt;/code&gt; to populate the live request log.&lt;/p&gt;
&lt;h2 id="tech-stack"&gt;Tech Stack&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Reverse Proxy&lt;/strong&gt;: Nginx 1.28&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;App Runtime&lt;/strong&gt;: Node.js 20 (Alpine)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Framework&lt;/strong&gt;: Express&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Containerization&lt;/strong&gt;: Docker + Docker Compose&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Security&lt;/strong&gt;: SSL/TLS with certificate termination at Nginx&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;OS&lt;/strong&gt;: Windows + Docker Desktop&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="key-takeaways"&gt;Key Takeaways&lt;/h2&gt;
&lt;p&gt;Building this made abstract networking concepts concrete. Understanding why a health check uses port &lt;code&gt;3000&lt;/code&gt; instead of &lt;code&gt;3001&lt;/code&gt;, why &lt;code&gt;__APP_NAME__&lt;/code&gt; needs server-side injection rather than client-side JS, and why a self-signed cert behaves inconsistently across round-robin rotations — these are the kinds of details that only surface when you actually operate the system yourself.&lt;/p&gt;</description></item></channel></rss>