Understanding nginx: Web Server, Reverse Proxy, and Load Balancer Explained with Docker Compose

Modern web applications need to handle thousands of concurrent users while maintaining high performance and reliability. This is where nginx shines as one of the most powerful and versatile web servers available today. In this comprehensive guide, we'll explore how nginx functions as a web server, reverse proxy, and load balancer through a hands-on Docker example.

GitHub Repository: https://github.com/sachindumalshan/simple-nginx-app.git


What is nginx?

nginx (pronounced "engine-x") is a high-performance web server, reverse proxy server, and load balancer. Nginx has become one of the most popular web servers in the world, powering over 30% of all websites globally.

Key Features of Nginx:

  • High Performance: Can handle thousands of concurrent connections with low memory usage

  • Reverse Proxy: Routes client requests to backend servers

  • Load Balancing: Distributes incoming requests across multiple servers

  • Static Content Serving: Efficiently serves static files like HTML, CSS, and images

  • SSL/TLS Termination: Handles encryption and decryption

  • Caching: Improves performance by storing frequently requested content

Demo Application Architecture

Before diving into concepts, let's understand a practical example:

┌─────────────┐    ┌─────────────┐    ┌─────────────┐
│   Browser   │────│    Nginx    │────│  Backend 1  │
│             │    │  (Port 8080)│    │   (PHP)     │
└─────────────┘    │             │    └─────────────┘
                   │             │    
                   │             │    ┌─────────────┐
                   │             │────│  Backend 2  │
                   └─────────────┘    │   (PHP)     │
                                      └─────────────┘

The application consists of:

  • Frontend: Static HTML contact form served by nginx

  • Two PHP Back-ends: Process form submissions and show container IDs with a message

  • nginx: Acts as web server, reverse proxy, and load balancer

  • Docker: Orchestrates all services


Nginx as a Web Server

What is a Web Server?

A web server is software that serves web content to clients (browsers) over HTTP/HTTPS. When you type a URL in your browser, you're making a request to a web server.

How nginx Works as a Web Server

In this example, nginx serves the static HTML contact form directly to users:

server {
    listen 80;

    location / {
        root /usr/share/nginx/html;
        index index.html;
    }
}

Key Points:

  • Nginx listens on port 80 for incoming HTTP requests

  • Static files (HTML, CSS, JS) are served directly from /usr/share/nginx/html

  • Clean, minimal configuration for serving static content

Advantages of Nginx as a Web Server:

  • Speed: Handles static content incredibly fast

  • Low Memory Usage: Uses an event-driven architecture

  • Concurrent Connections: Can handle thousands of simultaneous connections

  • Compression: Built-in gzip compression reduces bandwidth usage


Nginx as a Reverse Proxy

What is a Reverse Proxy?

A reverse proxy sits between clients and servers, forwarding client requests to appropriate backend servers and then returning the server's response back to the client. Unlike a forward proxy (which acts on behalf of clients), a reverse proxy acts on behalf of servers.

How reverse proxy use in this example

When submit the contact form, nginx forwards the request to our PHP back-ends:

# Proxy API requests to backend servers
location /api/ {
    # Remove /api prefix when forwarding to backend
    rewrite ^/api/(.*)$ /$1 break;

    # Proxy to upstream backend servers
    proxy_pass http://backend;

    # Headers for proper proxying
    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;
}

How it works:

  1. Browser submits form to /api/contact.php

  2. nginx receives the request

  3. nginx removes /api prefix and forwards to backend

  4. Backend processes the request

  5. nginx returns the response to the browser

Benefits of Reverse Proxy:

  • Security: Hides backend server details from clients

  • SSL Termination: Handles encryption/decryption

  • Caching: Can cache responses to improve performance

  • Compression: Compresses responses before sending to clients

  • Request Routing: Routes requests based on various criteria


Nginx as a Load Balancer

What is Load Balancing?

Load balancing distributes incoming network traffic across multiple servers to ensure no single server becomes overwhelmed. This improves application responsiveness and availability.

Load balancing in this example

Our Nginx configuration uses upstream servers for load balancing:

upstream backend_servers {
    server backend1:80;
    server backend2:80;
    # Explicitly use round-robin (default, but let's be clear)
}

Critical Configuration for Effective Load Balancing:

location /api/ {
    proxy_pass http://backend_servers/;
    # These settings ensure proper load balancing
    proxy_http_version 1.1;
    proxy_set_header Connection "";
}8

Why these settings matter:

  • proxy_http_version 1.1: Forces HTTP/1.1 protocol

  • proxy_set_header Connection "": Prevents connection reuse

  • Result: Each request gets a fresh connection, ensuring visible round-robin distribution

Load Balancing Methods:

  1. Round Robin (this example): Requests are distributed sequentially

  2. Least Connections: Routes to server with fewest active connections

  3. IP Hash: Routes based on client IP hash

  4. Weighted: Assigns different weights to servers

More info: https://www.geeksforgeeks.org/system-design/load-balancing-algorithms/

Demonstrating Load Balancing

When you submit forms in our application, you'll notice:

  • First submission → Backend 1 (shows container ID)

  • Second submission → Backend 2 (shows different container ID)

  • Third submission → Backend 1 (cycle repeats)

This demonstrates round-robin load balancing in action!


Apache vs nginx: Backend Processing

Apache Web Server

Apache uses a process-based or thread-based approach:

Apache Architecture:
┌─────────────┐
│   Request   │
└─────┬───────┘
      │
┌─────▼───────┐
│   Process/  │ ← Each request gets its own process/thread
│   Thread    │
└─────┬───────┘
      │
┌─────▼───────┐
│   PHP       │ ← PHP processes the request
│  Handler    │
└─────────────┘

Apache Characteristics:

  • Process/Thread per connection: More memory usage

  • Synchronous processing: Blocks while waiting for I/O

  • Easier configuration: More straightforward for beginners

  • Module system: Extensive module ecosystem

How Our PHP Back-ends Work with Apache

In the Docker setup, each backend container runs Apache with PHP:

# Our backends use php:8.1-apache image which includes:
# - Apache web server
# - PHP interpreter
# - mod_php module for Apache

The PHP code processes form data:

<?php
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    $name = htmlspecialchars($_POST['name'] ?? '');
    $email = htmlspecialchars($_POST['email'] ?? '');
    $server = gethostname(); // Shows which container processed it

    // Clean, inline HTML response with styling
    echo "<div style='font-family: Arial, sans-serif; max-width: 400px; margin: 50px auto; padding: 30px; background: #f8f9fa; border-radius: 10px; box-shadow: 0 2px 10px rgba(0,0,0,0.1);'>";
    echo "<h2 style='color: #28a745; text-align: center;'>✓ Message Sent Successfully!</h2>";
    echo "<p><strong>Name:</strong> $name</p>";
    echo "<p><strong>Email:</strong> $email</p>";
    echo "<p style='font-size: 12px; color: #666; text-align: center; margin-top: 20px;'>Processed by Backend Server: $server</p>";
    echo "</div>";
}
?>

Nginx vs Apache Performance

AspectNginxApache
ArchitectureEvent-driven, asynchronousProcess/thread-based
Memory UsageLow, constantHigher, scales with connections
Static ContentExcellentGood
Dynamic ContentRequires backend (PHP-FPM)Built-in (mod_php)
ConfigurationSimpler syntaxMore complex but flexible
ModulesCompiled-inDynamic loading

Running the Demo Application

Prerequisites

# Install Docker and Docker Compose
sudo apt update
sudo apt install docker.io docker-compose

# Add user to docker group
sudo usermod -aG docker $USER

Setup and Run

# Clone or create the project structure
mkdir simple-nginx-app && cd simple-nginx-app

# Create all files as shown in the first artifact
# Then run:
docker-compose up -d

# Access at: http://localhost:8080

Testing Load Balancing

  1. Open http://localhost:8080

  2. Fill out the contact form

  3. Submit multiple times

  4. Notice how container IDs alternate between submissions

  5. This demonstrates round-robin load balancing!


Visual Demo: Round Robin Load Balancing in Action

Once the application is up and running, can observe the backend switching behavior using container IDs.

✅ Step 1: Check Running Containers

Run the following command to view active containers and their IDs:

docker ps

List of running containers with their names and container IDs

✅ Step 2: First Form Submission

Open the app in your browser at http://localhost:8080

  1. Fill in the contact form with test values (e.g., Alice, alice@example.com)

  2. Click Submit

Filled contact form before submission

Output showing “Message Sent Successfully!” along with a container ID (e.g., backend1)

✅ Step 3: Second Form Submission

Repeat the process with different values (e.g., Bob, bob@example.com):

Second form submission

Response showing success message with a different container ID (e.g., backend2)

✅ Step 4: Repeat to Observe Load Balancing

Each time you submit the form, you'll notice the container ID switches between backend1 and backend2. This confirms the round robin load balancing behavior configured in nginx.

Demonstration of backend switching between requests


Troubleshooting Common Issues

1. 502 Bad Gateway

  • Backend servers are down

  • Network connectivity issues

  • Check docker-compose logs

2. Load Balancing Not Working

  • Verify upstream configuration

  • Check backend server status

  • Review Nginx error logs

Conclusion

nginx's versatility as a web server, reverse proxy, and load balancer makes it an essential tool for modern web applications. This Docker example demonstrates these concepts in action:

  • Web Server: Efficiently serves static HTML content

  • Reverse Proxy: Routes API requests to backend services

  • Load Balancer: Distributes traffic across multiple PHP back-ends using round-robin

The combination of Nginx with containerized back-ends provides a robust, scalable architecture that can handle high traffic while maintaining excellent performance. Whether you're building microservices, implementing high availability, or optimizing web application performance, understanding these concepts is crucial for modern web development.

0
Subscribe to my newsletter

Read articles from Sachindu Malshan directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Sachindu Malshan
Sachindu Malshan