SYSTEM DESIGN · CONCEPTS · HOW THE WEB WORKS · EPISODE 7

Nginx Explained: Reverse Proxy, Load Balancer and Web Server

What Nginx is, why its event-driven design handles huge traffic, and a real configuration line by line: static files, HTTPS, redirects, reverse proxy, load balancing, caching, compression and rate limiting.

  1. 1
  2. 2
  3. 3
  4. 4
  5. 5
  6. 6
  7. 7
  8. 8
  9. 9
  10. 10
  11. 11
  12. 12
  13. 13
  14. 14
  15. 15
  16. 16
  17. 17
  18. 18
  19. 19
  20. 20
  21. 21
  22. 22
  23. 23
  24. 24
  25. 25
  26. 26
  27. 27
  28. 28
  29. 29
  30. 30
  31. 31
  32. 32
  33. 33
  34. 34
  35. 35
  36. 36
  37. 37
  38. 38
  39. 39
  40. 40
  41. 41
1 / 16

Press play to hear the narration, or step through at your own pace. Every picture here is the same one used in the YouTube video.

Notes

What is Nginx?

Nginx, pronounced engine x, is free, open source software. It can be a web server that serves files, a reverse proxy, a load balancer, and a cache. Often all at once.

Why is it so fast? A master process reads the configuration and starts a few worker processes, usually one per CPU core. Each worker runs an event loop, like we saw in episode four, juggling thousands of connections without waiting on any one of them. That means very little memory per connection.

Why it’s fast
  • 1 master process: reads config
  • a few worker processes
  • (usually one per CPU core)
  • each worker: an event loop,
  • thousands of connections,
  • never blocking on one

The config

Everything is set in one file, nginx.conf. It's made of nested blocks. An http block holds server blocks, one per website. Each server has location blocks: rules for different URLs.

/etc/nginx/nginx.conf
http {
  server {        # one website
    listen 80;
    server_name algorung.com;
    location / {  # a URL rule
      …
    }
  }
}

1 · Static files

Use one: static files. A location for /static, a root folder on disk, and an expires rule so browsers cache the files for thirty days. Nginx serves files like these extremely fast, without touching your application.

Static files
server {
  location /static/ {
    root /var/www;
    expires 30d;
  }
}

2 · HTTPS

Use two: HTTPS. The first server block listens on port 80 and redirects every request to the HTTPS address with a 301. The second listens on 443 with SSL, and points to the certificate and private key. Free certificates from Let's Encrypt plug in right here.

HTTPS
server {
  listen 80;
  server_name algorung.com;
  return 301 https://$host$request_uri;
}
server {
  listen 443 ssl;
  ssl_certificate     /etc/ssl/site.crt;
  ssl_certificate_key /etc/ssl/site.key;
}

3 · Reverse proxy

Use three: reverse proxy. A location for /api, and proxy_pass sends those requests to the application server. The two header lines pass along the original host name and the client's real IP address, the X-Forwarded-For header from last episode.

Reverse proxy
location /api/ {
  proxy_pass http://10.0.0.11:8080;
  proxy_set_header Host $host;
  proxy_set_header X-Forwarded-For
                   $proxy_add_x_forwarded_for;
}

4 · Load balancing

Use four: load balancing. An upstream block names a group of servers. least_conn picks the server with the fewest active connections. The default is round robin. A backup server only gets traffic if the others are down. Then proxy_pass points at the group's name instead of one address.

If a server keeps failing, Nginx stops sending it traffic for a while: three failures in thirty seconds means skip it for thirty seconds. These are passive checks, based on real requests. Active health checks need the commercial version or a separate tool.

Load balancing
upstream app_servers {
  least_conn;
  server 10.0.0.11:8080;
  server 10.0.0.12:8080;
  server 10.0.0.13:8080 backup;
}
location /api/ {
  proxy_pass http://app_servers;
}
Failure handling
server 10.0.0.12:8080
       max_fails=3 fail_timeout=30s;

3 failures in 30 s → skip it for 30 s
(active health checks: Nginx Plus,
 or a separate checker)

5 · Extras

Use five: a few lines each for the extras. gzip on compresses responses. proxy_cache stores application responses, here for ten minutes. And limit_req allows each IP address about ten requests a second, with a small burst, which stops floods and scrapers.

A few more lines
gzip on;
proxy_cache my_cache;
proxy_cache_valid 200 10m;

limit_req_zone $binary_remote_addr
    zone=perip:10m rate=10r/s;
limit_req zone=perip burst=20;

Operating it

Running it day to day: nginx -t tests your config for mistakes before you apply it. nginx -s reload applies changes without dropping a single connection. And the access and error logs tell you what's happening.

Everyday commands
nginx -t            # test the config
nginx -s reload     # apply, no downtime

access.log  every request
error.log   what went wrong

The full picture

Put it together, and one Nginx in front of your app terminates HTTPS, serves static files, routes API calls, balances them across servers, caches, compresses, and rate limits. That's most of this series, in about forty lines of config.

Alternatives

Alternatives use the same ideas. HAProxy is a load balancing specialist. Envoy powers service meshes. Caddy gets HTTPS certificates automatically. Traefik configures itself from Docker and Kubernetes. And cloud load balancers do it with no servers to manage.

Alternatives
  • HAProxy: load balancing specialist
  • Envoy: service mesh, gRPC, observability
  • Caddy: automatic HTTPS, simple config
  • Traefik: auto-config from Docker / K8s
  • Cloud LBs: managed, no servers to run

Interview questions

What is Nginx and what is it used for?

An open-source, event-driven server used as a web server for static files, a reverse proxy, a load balancer, a TLS terminator and an HTTP cache, often all at once.

Why does Nginx handle so many connections?

A master process manages a few worker processes (about one per CPU core); each worker uses a non-blocking event loop (epoll/kqueue) to multiplex thousands of connections with little memory per connection, instead of one thread per connection.

Sketch an Nginx config for HTTPS in front of three app servers.

An upstream block listing the three servers (optionally least_conn and a backup); a server on port 80 that returns 301 to HTTPS; a server on 443 ssl with ssl_certificate and ssl_certificate_key, a location /static/ with root, and a location /api/ with proxy_pass http://upstream_name plus proxy_set_header Host and X-Forwarded-For.

Passive vs active health checks in Nginx?

Passive checks (max_fails, fail_timeout) mark a server down after real requests fail. Active checks probe a health endpoint on a schedule; open-source Nginx does passive only (active is in Nginx Plus or via external tools).

How do you change Nginx configuration with zero downtime?

Run nginx -t to validate, then nginx -s reload: the master starts new workers with the new config and lets old workers finish their open connections gracefully.