NGINX Guide — Web Server, Reverse Proxy, and Load Balancer
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://localhostExpected 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.comCertbot 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=31536000Load 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 UnavailableCommon 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:
- Install NGINX on a Linux server (local VM or cloud instance)
- Configure static file serving for a frontend app at
/var/www/app - Set up reverse proxy to a Node.js API running on port 3000
- Obtain and configure SSL with Certbot for
https://app.example.com - 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 503This stack is similar to what powers Doda Browser update infrastructure and Durga Antivirus Pro signature distribution.
FAQ
Built by the developers of Doda Browser, DodaZIP, and Durga Antivirus Pro.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro