Nginx
Reverse proxy, load balancing, SSL termination, caching.
Theory
Nginx is an event-driven, asynchronous web server and reverse proxy. Unlike Apache's thread-per-request model, Nginx uses a non-blocking I/O event loop, allowing it to handle tens of thousands of concurrent connections with low memory footprint.
The configuration hierarchy: nginx.conf includes conf.d/*.conf or sites-enabled/ files. Each server block defines a virtual host — server_name, listen port, and root directory. Requests match server blocks by Host header, then location blocks by URI prefix or regex.
As a reverse proxy, Nginx accepts client connections and forwards them to upstream application servers (Gunicorn, Node, FastAPI). The proxy_pass directive routes requests; proxy_set_header forwards the original Host and client IP so apps can log and auth correctly.
SSL termination at Nginx decrypts HTTPS from clients, forwarding plain HTTP to backend services. ssl_certificate/ssl_certificate_key point to cert and key files. Modern configs add ssl_protocols TLSv1.2 TLSv1.3 and ssl_ciphers to enforce secure cipher suites.
Load balancing is configured via upstream blocks: round-robin (default), least_conn (fewest active connections), ip_hash (sticky sessions by client IP), or weighted. Health checks via proxy_next_upstream retry failed backends automatically.
Nginx serves static files efficiently with sendfile on and open_file_cache to avoid repeated disk I/O. Cache-Control headers set via add_header or expires directive. gzip on with gzip_types compresses text-based responses for faster delivery.
Rate limiting uses limit_req_zone to define a shared memory zone with a key (e.g. $binary_remote_addr) and rate, then limit_req in location blocks to enforce burst limits. This protects upstream apps from traffic spikes and brute-force attacks.
nginx -t validates config syntax before reload. nginx -s reload does a zero-downtime config reload (workers drain existing connections, new workers use new config). This is the standard deployment pattern for config changes.
Architecture Diagram
Clients (HTTPS)
|
Nginx (worker processes)
|
+----+----+
v v
upstream static/cache
app poolExamples
server {
listen 80;
server_name api.example.com;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl http2;
server_name api.example.com;
ssl_certificate /etc/nginx/ssl/cert.pem;
ssl_certificate_key /etc/nginx/ssl/key.pem;
ssl_protocols TLSv1.2 TLSv1.3;
location / {
proxy_pass http://app_upstream;
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_read_timeout 60s;
}
location /static/ {
root /var/www;
expires 1y;
add_header Cache-Control "public, immutable";
}
}
upstream app_upstream {
least_conn;
server 127.0.0.1:8000 weight=3;
server 127.0.0.1:8001;
keepalive 32;
}
http {
limit_req_zone $binary_remote_addr zone=api_limit:10m rate=10r/s;
limit_req_zone $binary_remote_addr zone=login_limit:1m rate=1r/s;
server {
location /api/ {
limit_req zone=api_limit burst=20 nodelay;
proxy_pass http://app_upstream;
}
location /auth/login {
limit_req zone=login_limit burst=5;
proxy_pass http://app_upstream;
}
}
}
# Test config before applying
nginx -t
# Zero-downtime reload
nginx -s reload
# Check which ports nginx listens on
ss -tlnp | grep nginx
# View access logs with request timing
tail -f /var/log/nginx/access.log | awk '{print $7, $NF}'
# Check worker process count
ps aux | grep 'nginx: worker'
# Debug upstream 502s
cat /var/log/nginx/error.log | grep 'connect() failed'
Interview Questions
What is the difference between proxy_pass and upstream in Nginx?
proxy_pass points to a single server or upstream group. upstream defines a named pool with load balancing, health checks, and keepalive settings. Use upstream when you have multiple backend instances or need keepalive connection pooling.
How does Nginx handle SSL termination?
Nginx decrypts TLS at the edge, forwarding plain HTTP to backends. Certificates are configured via ssl_certificate/ssl_certificate_key. Backends receive X-Forwarded-Proto: https header so they can enforce HTTPS-only behavior without managing certs.
What does nginx -s reload do vs restarting the process?
reload sends SIGUSR2 to the master process. It starts new workers with the new config and gracefully drains the old workers (they finish existing requests then exit). No connections are dropped. A full restart (systemctl restart nginx) drops active connections.
How would you debug a 502 Bad Gateway from Nginx?
Check /var/log/nginx/error.log for "connect() failed" or "upstream timed out". Verify backend is running (curl localhost:8000), check proxy_pass URL, increase proxy_read_timeout if backend is slow, ensure upstream keepalive doesn't exceed backend's max connections.
Explain the location block matching order.
Nginx matches location blocks in this priority: 1) exact match (=), 2) preferential prefix (^~), 3) regex in file order (~, ~*), 4) longest prefix match. This is confusing — use nginx -T to trace config and test with curl -v to verify which block matches.
How do you configure Nginx as a load balancer with health checks?
Define an upstream block with multiple servers and a method (least_conn, ip_hash). Add proxy_next_upstream error timeout to retry failed backends. For active health checks (not in open source), use nginx_upstream_check_module or Nginx Plus. For passive, configure proxy_connect_timeout and max_fails.
FAANG: How would you handle 100k req/s through Nginx?
Tune worker_processes to CPU count, worker_connections to 10240+. Enable sendfile, tcp_nopush, tcp_nodelay. Use keepalive 64 on upstream blocks. Add caching (proxy_cache) for idempotent responses. Profile with nginx-module-vts for per-upstream latency histograms. At 100k req/s, multiple Nginx instances behind a cloud LB are typical.
What is the C10k problem and how does Nginx solve it?
C10k: handling 10,000 concurrent connections. Thread-per-connection servers (Apache prefork) run out of memory (each thread ~1MB). Nginx uses an event loop (epoll on Linux, kqueue on BSD) — one worker handles thousands of connections via non-blocking I/O, using ~2MB total per worker regardless of connection count.
Best Practices
- Always run
nginx -tto validate config beforenginx -s reload— one syntax error kills all workers. - Version-control nginx.conf and every conf.d/ file; review changes via pull request like application code.
- Use named
upstreamblocks even for single backends — makes adding load balancing later a one-line change. - Set
proxy_set_header X-Real-IP $remote_addrso application logs capture real client IPs, not 127.0.0.1. - Use
ssl_protocols TLSv1.2 TLSv1.3and explicitly disable older protocols (SSLv3, TLS 1.0, 1.1). - Add
proxy_read_timeoutappropriate for your slowest upstream response — default 60s causes 504s for long-running requests.
Common Mistakes
- Running nginx as root in production — use
user www-data;and drop privileges after binding privileged ports. - Forgetting
nginx -tbefore reload — a single typo in nginx.conf drops all traffic immediately. - Using
try_files $uri $uri/ =404without understanding the request chain — causes recursive location matching bugs with PHP backends. - Setting
worker_connectionswithout increasing the OS open file limit (ulimit -n) — connections silently fail at OS ceiling. - Logging to a single large access.log with no rotation — fills disk silently over weeks, crashes nginx when disk is full.
Cheat Sheet
Practical Exercises
Run a local Flask or Node app on port 5000. Configure Nginx to proxy :80 → :5000 with limit_req_zone limiting to 5 req/s. Test with Apache Bench: ab -n 100 -c 10 http://localhost/ and observe 429 responses.
Generate a self-signed cert with openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout /etc/nginx/ssl/key.pem -out /etc/nginx/ssl/cert.pem. Configure an HTTPS server block that redirects HTTP → HTTPS. Test with curl -k https://localhost.
Start two instances of a simple HTTP server on ports 8001 and 8002. Configure an upstream block with least_conn. Add proxy_next_upstream error timeout. Kill one backend and verify Nginx fails over to the other without returning an error to the client.