Skip to content
NGINX Guide — Web Server, Reverse Proxy, and Load Balancer

NGINX Guide — Web Server, Reverse Proxy, and Load Balancer

DodaTech Updated Jun 7, 2026 6 min read

NGINX is a high-performance web server, reverse proxy, and load balancer used by over 30% of the internet’s busiest websites. It excels at handling thousands of concurrent connections with minimal memory footprint.

In this tutorial, you will learn how to serve static files, configure NGINX as a reverse proxy for Node.js applications, set up SSL termination with Let’s Encrypt, distribute traffic across multiple backend servers, implement rate limiting, and write effective location blocks. DodaTech uses NGINX to serve Doda Browser update servers and route traffic for Durga Antivirus Pro APIs.

What You’ll Learn

By the end of this guide, you will deploy NGINX as a static file server, configure it as a reverse proxy for Bash-managed backend services, terminate SSL with automated certificates, load balance across upstream servers, and protect your applications with rate limiting.

Why NGINX Matters

NGINX is the industry standard for serving web traffic at scale. Its event-driven architecture handles 10,000+ concurrent connections on a single server. Understanding NGINX is essential for any developer deploying production applications on Linux.

NGINX Learning Path

    flowchart LR
  A[Static Serving] --> B[Reverse Proxy]
  B --> C[SSL Termination]
  C --> D[Load Balancing]
  D --> E[Rate Limiting]
  E --> F{You Are Here}
  style F fill:#f90,color:#fff
  

Installation

# Ubuntu / Debian
sudo apt update && sudo apt install nginx -y

# RHEL / CentOS / Fedora
sudo dnf install nginx -y

# Verify
sudo systemctl start nginx
sudo systemctl enable nginx
curl http://localhost

Expected output

<!DOCTYPE html>
<html>
<head><title>Welcome to nginx!</title></head>
<body><h1>Welcome to nginx!</h1></body>
</html>

Static File Serving

NGINX excels at serving static assets (HTML, CSS, JS, images). Configure a server block in /etc/nginx/sites-available/example.com:

server {
    listen 80;
    server_name example.com www.example.com;
    root /var/www/example.com;
    index index.html;

    location / {
        try_files $uri $uri/ /index.html;
    }

    location /assets/ {
        expires 7d;
        add_header Cache-Control "public, immutable";
    }

    location ~* \.(css|js|jpg|jpeg|png|gif|ico)$ {
        expires 30d;
        add_header Cache-Control "public, no-transform";
    }
}

Expected behavior

curl -I http://example.com/style.css
# HTTP/1.1 200 OK
# Cache-Control: public, no-transform
# Expires: ...

Reverse Proxy

Route requests to a backend application (Node.js, Python, Go) running on an internal port:

server {
    listen 80;
    server_name api.example.com;

    location / {
        proxy_pass http://127.0.0.1:3000;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection 'upgrade';
        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;
        proxy_cache_bypass $http_upgrade;
    }
}

Testing the proxy

# Start your backend on port 3000, then:
curl http://api.example.com/health
# Expected: forwarded to http://127.0.0.1:3000/health
# Response: {"status":"ok"}

SSL Termination

Use Certbot to obtain and auto-renew Let’s Encrypt certificates:

sudo apt install certbot python3-certbot-nginx -y
sudo certbot --nginx -d example.com -d www.example.com

Certbot modifies your server block to add SSL:

server {
    listen 443 ssl http2;
    server_name example.com;

    ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;

    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256;
    ssl_prefer_server_ciphers on;

    location / {
        proxy_pass http://127.0.0.1:3000;
    }
}

server {
    listen 80;
    server_name example.com;
    return 301 https://$server_name$request_uri;
}

Expected output

curl -I https://example.com
# HTTP/2 200
# strict-transport-security: max-age=31536000

Load Balancing

Distribute traffic across multiple backend servers:

upstream backend {
    least_conn;
    server 10.0.1.1:3000 weight=3;
    server 10.0.1.2:3000 weight=2;
    server 10.0.1.3:3000 backup;
}

server {
    listen 80;
    server_name app.example.com;

    location / {
        proxy_pass http://backend;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
    }
}

Load balancing methods: round_robin (default), least_conn, ip_hash (session stickiness), random.

Rate Limiting

Protect your backend from abuse:

# Define a limit zone
limit_req_zone $binary_remote_addr zone=api_limit:10m rate=10r/s;

server {
    listen 80;
    server_name api.example.com;

    location /api/ {
        limit_req zone=api_limit burst=20 nodelay;
        proxy_pass http://backend;
    }

    location /api/login {
        limit_req zone=api_limit burst=5 nodelay;
        proxy_pass http://backend;
    }
}

Expected behavior

# Rapid requests will get 503 after the burst is exhausted
curl -I http://api.example.com/api/users
# If rate limited:
# HTTP/1.1 503 Service Temporarily Unavailable

Common Errors

1. 403 Forbidden

NGINX cannot read the web root. Check permissions: sudo chown -R www-data:www-data /var/www/example.com and ensure the directory has execute permission: chmod +x /var/www/example.com.

2. 502 Bad Gateway

The backend server is down or unreachable. Verify: systemctl status your-app and curl http://127.0.0.1:3000. Check proxy_pass URL matches.

3. SSL Certificate Not Found

Certbot certificates expire after 90 days. Verify renewal: sudo certbot renew --dry-run. Ensure the renewal systemd timer is active.

4. Cannot Connect After Config Change

Syntax error in config. Test with sudo nginx -t. Reload with sudo systemctl reload nginx (zero downtime).

5. WebSocket Connection Fails

WebSocket requires Upgrade and Connection headers in the proxy config. Ensure proxy_set_header Upgrade $http_upgrade; and proxy_set_header Connection 'upgrade'; are present.

6. Rate Limiting Too Aggressive

Legitimate clients hitting 503. Increase the burst size or rate. Monitor with tail -f /var/log/nginx/error.log | grep limiting.

7. Port Already in Use

Apache may be running on port 80. Stop it: sudo systemctl stop apache2. Change NGINX’s default port in /etc/nginx/sites-enabled/default.

Practice Questions

1. What is the difference between a web server and a reverse proxy?

A web server serves static files directly. A reverse proxy forwards requests to a backend application server, adding SSL termination, caching, and load balancing.

2. How do you test an NGINX configuration for syntax errors?

Run sudo nginx -t. It reports syntax validity and the location of any errors.

3. What does proxy_pass http://backend; do when backend is defined as an upstream block?

It load balances requests across the servers listed in the upstream backend {} block using the specified balancing method.

4. How do you redirect HTTP to HTTPS in NGINX?

Create a server block listening on port 80 with return 301 https://$server_name$request_uri; and no root or proxy directives.

5. Challenge: Configure a caching reverse proxy

Extend the reverse proxy config to cache responses: proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=my_cache:10m; and add proxy_cache my_cache; in the location block.

Mini Project: Production NGINX Stack

Deploy a three-tier architecture with NGINX:

  1. Install NGINX on a Linux server (local VM or cloud instance)
  2. Configure static file serving for a frontend app at /var/www/app
  3. Set up reverse proxy to a Node.js API running on port 3000
  4. Obtain and configure SSL with Certbot for https://app.example.com
  5. Add rate limiting: 20 requests/second with a burst of 30

Test each layer:

# Test static files
curl -I http://localhost/index.html
# Expected: 200 OK

# Test API proxy
curl http://localhost/api/health
# Expected: {"status":"ok"}

# Test rate limiting
for i in $(seq 1 50); do curl -s -o /dev/null -w "%{http_code}\n" http://localhost/api/; done
# Expected: first ~30 return 200, rest return 503

This stack is similar to what powers Doda Browser update infrastructure and Durga Antivirus Pro signature distribution.

FAQ

Can NGINX serve dynamic content?
NGINX serves static content natively. For dynamic content, it proxies requests to application servers (Node.js, Python, PHP-FPM) or uses the ngx_http_fastcgi_module for PHP.
What is the event-driven architecture?
Unlike Apache’s process-per-connection model, NGINX uses a single master process and multiple worker processes that handle thousands of connections with a single thread, using asynchronous I/O.
Is NGINX better than Apache?
For static files and high concurrency, yes. Apache has more features via modules (mod_rewrite, .htaccess). Many production setups use NGINX as a reverse proxy in front of Apache.
How do I monitor NGINX in production?
Use the NGINX status module (stub_status), export metrics to Prometheus with nginx-lua-prometheus, and visualize with Grafana.
Does NGINX support HTTP/3?
Yes — NGINX supports HTTP/3 (QUIC) since version 1.25. Enable it by compiling with the --with-http_v3_module flag.

Built by the developers of Doda Browser, DodaZIP, and Durga Antivirus Pro.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro