contentintech
Intermediate~20 min read

Nginx

Nginx configuration structure, server blocks and location matching, reverse proxying with proxy_pass and headers, upstream load balancing, serving static files, SSL/TLS termination, gzip compression, caching, rate limiting, and worker process tuning.

Reverse ProxyLoad BalancingTLSCaching

What Nginx Is

Nginx is an event-driven web server and reverse proxy built for high concurrency. A small number of worker processes handle thousands of connections each using non-blocking I/O, which is why it excels at serving static files, terminating TLS, load balancing, and fronting application servers.

Configuration Structure

The main config lives at /etc/nginx/nginx.conf. It is organized into nested contexts (blocks) with directives inside. Per-site config is usually split into files under /etc/nginx/conf.d/ or sites-available/ and pulled in with include.

nginx
# /etc/nginx/nginx.conf
user  nginx;
worker_processes  auto;          # one worker per CPU core

events {
    worker_connections  1024;    # max connections per worker
}

http {
    include       /etc/nginx/mime.types;
    default_type  application/octet-stream;
    sendfile      on;
    keepalive_timeout  65;

    include /etc/nginx/conf.d/*.conf;   # pull in server blocks

    server {
        # ... a virtual host lives here
    }
}

Test before you reload

Always run nginx -t to validate syntax, then nginx -s reload for a zero-downtime reload. A reload spawns new workers with the new config and gracefully retires the old ones.

Server Blocks (Virtual Hosts)

A server block defines one virtual host. Nginx picks a block by matching the request's Host header against server_name and the port in listen.

nginx
server {
    listen       80;
    server_name  example.com www.example.com;

    root   /var/www/example;
    index  index.html;

    location / {
        try_files $uri $uri/ =404;
    }
}

Location Matching

Location blocks decide how a request path is handled. Match precedence is not top-to-bottom — Nginx follows a defined order:

ModifierMeaningPriority
= /pathExact matchHighest
^~ /pathPrefix, stop regex search if matchedHigh
~ regexCase-sensitive regexMedium
~* regexCase-insensitive regexMedium
/pathPlain prefix (longest wins)Lowest

Serving Static Files

For assets, set root and let Nginx serve straight from disk. Add long cache headers for fingerprinted files.

nginx
location /static/ {
    root       /var/www/app;         # serves /var/www/app/static/...
    expires    30d;
    add_header Cache-Control "public, immutable";
    access_log off;
}

Note the difference between root (appends the URI path to the given directory) and alias (replaces the matched location prefix entirely).

Reverse Proxy

As a reverse proxy, Nginx forwards requests to an upstream application server and returns its response to the client. Forwarding the right headers is essential so the backend knows the real client IP and scheme.

nginx
location /api/ {
    proxy_pass http://127.0.0.1:3000;

    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_http_version 1.1;
    proxy_read_timeout 60s;
}

Trailing slash gotcha

A trailing slash in proxy_pass http://backend/; strips the matched location prefix from the forwarded path; without it the full URI is passed through. This one character changes behavior — be deliberate.

Load Balancing

An upstream block groups backend servers so Nginx can distribute traffic across them.

nginx
upstream app_backend {
    least_conn;                    # send to server with fewest active conns
    server 10.0.0.11:3000 weight=3;
    server 10.0.0.12:3000;
    server 10.0.0.13:3000 backup; # only used if others are down
    keepalive 32;                  # reuse upstream connections
}

server {
    location / {
        proxy_pass http://app_backend;
        proxy_set_header Connection "";
    }
}
MethodBehavior
round-robin (default)Cycles through servers in order, respecting weights.
least_connPicks the backend with the fewest active connections.
ip_hashSticky sessions — same client IP always hits the same backend.

SSL / TLS Termination

Nginx commonly terminates TLS: it decrypts HTTPS at the edge and talks plain HTTP to backends. Certificates are typically issued with Let's Encrypt (via Certbot or acme.sh). Redirect all HTTP to HTTPS.

nginx
server {
    listen 80;
    server_name example.com;
    return 301 https://$host$request_uri;   # force HTTPS
}

server {
    listen 443 ssl;
    http2  on;
    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         HIGH:!aNULL:!MD5;
    add_header Strict-Transport-Security "max-age=63072000" always;

    location / {
        proxy_pass http://app_backend;
    }
}

Compression, Caching, and Rate Limiting

Gzip

nginx
gzip on;
gzip_comp_level 5;
gzip_min_length 256;
gzip_types text/plain text/css application/json
           application/javascript text/xml;

Proxy Caching

nginx
# in http {} context
proxy_cache_path /var/cache/nginx levels=1:2
                 keys_zone=api_cache:10m max_size=1g inactive=60m;

# in location {}
proxy_cache        api_cache;
proxy_cache_valid  200 10m;
add_header X-Cache-Status $upstream_cache_status;

Rate Limiting

nginx
# in http {} context: 10 req/s per client IP
limit_req_zone $binary_remote_addr zone=api:10m rate=10r/s;

# in location {}: allow short bursts of 20, queue the rest
location /api/ {
    limit_req zone=api burst=20 nodelay;
    proxy_pass http://app_backend;
}

Worker Process Tuning

Set worker_processes auto; to match CPU cores and raise worker_connections for high-traffic hosts. The theoretical max simultaneous connections is worker_processes × worker_connections. Ensure the OS file-descriptor limit (worker_rlimit_nofile) is high enough to back it.

Practice Exercises

  1. Write a server block that serves a static site from /var/www/site on port 80, with a custom 404 page and try_files falling back to index.html.
  2. Configure Nginx as a reverse proxy for an app on 127.0.0.1:8000 and forward the correct X-Forwarded-* headers.
  3. Create an upstream with three backends using least_conn, mark one as backup, and verify traffic distribution.
  4. Obtain a Let's Encrypt certificate, enable TLS 1.3 with HTTP/2, and redirect all HTTP requests to HTTPS.
  5. Add a rate limit of 5 requests per second per IP with a burst of 10 to your /api/ location and test it with a load tool.
  6. Enable gzip and proxy caching for a JSON API endpoint, then confirm cache hits via the X-Cache-Status header.

Section navigation