Nginx Cheat Sheet
Nginx is an event-driven web server, reverse proxy, and load balancer. This cheatsheet focuses on real-world configuration patterns and ops workflows.
Install & Service Control
Common paths
- Main config:
/etc/nginx/nginx.conf - Site configs (Debian/Ubuntu):
/etc/nginx/sites-available/+/etc/nginx/sites-enabled/ - Logs:
/var/log/nginx/access.logand/var/log/nginx/error.log - PID (common):
/run/nginx.pidor configured viapidinnginx.conf
Service commands (systemd)
sudo systemctl status nginx
sudo systemctl start nginx
sudo systemctl stop nginx
sudo systemctl restart nginx
sudo systemctl reload nginx
Validate config and reload safely
sudo nginx -t
sudo nginx -T # dump full config (includes)
sudo nginx -s reload # signal master to reload
sudo nginx -s quit # graceful shutdown
sudo nginx -s stop # fast stop
Config File Structure
Top-level blocks
# global (main context)
user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log warn;
pid /run/nginx.pid;
events { worker_connections 1024; }
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
log_format main '$remote_addr - $remote_user [$time_local] '
'"$request" $status $body_bytes_sent '
'"$http_referer" "$http_user_agent" '
'rt=$request_time uct=$upstream_connect_time '
'uht=$upstream_header_time urt=$upstream_response_time';
access_log /var/log/nginx/access.log main;
sendfile on;
tcp_nopush on;
keepalive_timeout 65;
include /etc/nginx/conf.d/*.conf;
include /etc/nginx/sites-enabled/*;
}
Directive scope
Some directives are valid only in specific contexts (http, server, location, etc.). If NGINX complains about “directive is not allowed here”, it’s usually a scope issue.
Server Blocks
Minimal HTTP server
server {
listen 80;
server_name example.com www.example.com;
root /var/www/example;
index index.html;
location / {
try_files $uri $uri/ =404;
}
}
Default server (catch-all)
server {
listen 80 default_server;
server_name _;
return 444; # drop connection (optional)
}
Location Matching (Important)
NGINX chooses a location using a defined order. Practical tips:
- Use = for exact matches
- Use ^~ to prefer a prefix match over regex matches
- Regex locations (~, ~*) are evaluated after prefix selection
location = /healthz { return 200 "ok\n"; }
location ^~ /static/ { root /srv/site; }
location ~* \.(png|jpg|css|js)$ { expires 30d; }
location / { try_files $uri $uri/ /index.html; }
Static Files
Efficient static delivery
server {
listen 80;
server_name static.example.com;
root /srv/static;
location / {
try_files $uri =404;
}
location ~* \.(css|js|png|jpg|jpeg|gif|svg|ico|woff2?)$ {
access_log off;
expires 30d;
add_header Cache-Control "public, max-age=2592000, immutable";
try_files $uri =404;
}
}
Single Page App (SPA) fallback
location / {
try_files $uri $uri/ /index.html;
}
Reverse Proxy
Basic proxy to an upstream app
upstream app_backend {
server 127.0.0.1:8080;
keepalive 32;
}
server {
listen 80;
server_name app.example.com;
location / {
proxy_pass http://app_backend;
proxy_http_version 1.1;
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_connect_timeout 5s;
proxy_send_timeout 30s;
proxy_read_timeout 30s;
}
}
WebSockets
location /ws/ {
proxy_pass http://app_backend;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
Load Balancing (Upstreams)
Round robin (default)
upstream api {
server 10.0.0.10:9000;
server 10.0.0.11:9000;
}
Weighted
upstream api {
server 10.0.0.10:9000 weight=3;
server 10.0.0.11:9000 weight=1;
}
Failure handling
upstream api {
server 10.0.0.10:9000 max_fails=3 fail_timeout=10s;
server 10.0.0.11:9000 max_fails=3 fail_timeout=10s;
}
Caching (Reverse Proxy Cache)
proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=appcache:50m
max_size=5g inactive=60m use_temp_path=off;
server {
listen 80;
server_name cache.example.com;
location / {
proxy_pass http://app_backend;
proxy_cache appcache;
proxy_cache_key "$scheme$request_method$host$request_uri";
proxy_cache_valid 200 302 10m;
proxy_cache_valid 404 1m;
add_header X-Cache-Status $upstream_cache_status;
}
}
Compression
gzip
gzip on;
gzip_comp_level 5;
gzip_min_length 1024;
gzip_types
text/plain text/css application/json application/javascript
application/xml image/svg+xml;
gzip_vary on;
TLS (HTTPS)
Minimal TLS server
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;
location / {
proxy_pass http://app_backend;
}
}
Redirect HTTP → HTTPS
server {
listen 80;
server_name example.com;
return 301 https://$host$request_uri;
}
HSTS (be careful in production)
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
Security Headers (Common Baseline)
add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "DENY" always;
add_header Referrer-Policy "no-referrer" always;
add_header Permissions-Policy "geolocation=(), microphone=(), camera=()" always;
# CSP is app-specific; start strict and iterate
# add_header Content-Security-Policy "default-src 'self';" always;
Rate Limiting & Connection Limiting
Requests per second (token bucket)
limit_req_zone $binary_remote_addr zone=req_zone:10m rate=10r/s;
server {
location /api/ {
limit_req zone=req_zone burst=20 nodelay;
proxy_pass http://api;
}
}
Concurrent connections
limit_conn_zone $binary_remote_addr zone=conn_zone:10m;
server {
location / {
limit_conn conn_zone 20;
proxy_pass http://app_backend;
}
}
Access Control
Allow/Deny by IP
location /admin/ {
allow 10.0.0.0/8;
deny all;
proxy_pass http://app_backend;
}
Basic Auth
location /private/ {
auth_basic "Restricted";
auth_basic_user_file /etc/nginx/.htpasswd;
proxy_pass http://app_backend;
}
Create password file:
sudo apt-get install -y apache2-utils
htpasswd -c /etc/nginx/.htpasswd user1
Logging
Custom log format with upstream timing
log_format main '$remote_addr - $remote_user [$time_local] '
'"$request" $status $body_bytes_sent '
'"$http_referer" "$http_user_agent" '
'rt=$request_time uct=$upstream_connect_time '
'uht=$upstream_header_time urt=$upstream_response_time';
access_log /var/log/nginx/access.log main;
error_log /var/log/nginx/error.log warn;
Common variables
$remote_addr,$host,$request,$request_uri,$status$http_user_agent,$http_referer$request_time$upstream_addr,$upstream_status,$upstream_response_time
Troubleshooting Playbook
1) Syntax and effective config
nginx -t
nginx -T | less
2) Check logs fast
tail -f /var/log/nginx/error.log
tail -f /var/log/nginx/access.log
3) Validate upstream connectivity from the NGINX host
curl -v http://127.0.0.1:8080/health
4) Common symptoms
- 403: file permissions,
deny all, missingindex, wrongroot/alias - 404:
rootpath mismatch,try_filesorder,aliasusage - 502: upstream down, wrong
proxy_pass, bad DNS, timeout, app crash - 504: upstream slow; increase
proxy_read_timeout, fix app performance
5) Debug logging (temporary)
error_log /var/log/nginx/error.log debug;
Root vs Alias (Classic Gotcha)
# root appends the URI to the path
location /static/ {
root /srv/site; # /static/app.css -> /srv/site/static/app.css
}
# alias replaces the matching prefix
location /static/ {
alias /srv/site/static; # /static/app.css -> /srv/site/static/app.css
}