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.
# /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.
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:
| Modifier | Meaning | Priority |
|---|---|---|
= /path | Exact match | Highest |
^~ /path | Prefix, stop regex search if matched | High |
~ regex | Case-sensitive regex | Medium |
~* regex | Case-insensitive regex | Medium |
/path | Plain 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.
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.
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.
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 "";
}
}
| Method | Behavior |
|---|---|
| round-robin (default) | Cycles through servers in order, respecting weights. |
least_conn | Picks the backend with the fewest active connections. |
ip_hash | Sticky 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.
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
gzip on;
gzip_comp_level 5;
gzip_min_length 256;
gzip_types text/plain text/css application/json
application/javascript text/xml;
Proxy Caching
# 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
# 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
- Write a server block that serves a static site from
/var/www/siteon port 80, with a custom 404 page andtry_filesfalling back toindex.html. - Configure Nginx as a reverse proxy for an app on
127.0.0.1:8000and forward the correctX-Forwarded-*headers. - Create an upstream with three backends using
least_conn, mark one asbackup, and verify traffic distribution. - Obtain a Let's Encrypt certificate, enable TLS 1.3 with HTTP/2, and redirect all HTTP requests to HTTPS.
- 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. - Enable gzip and proxy caching for a JSON API endpoint, then confirm cache hits via the
X-Cache-Statusheader.