Install & Service#
#
Install & Service#
Ubuntu/Debian
$ sudo apt update && sudo apt install -y nginx
RHEL/CentOS
$ sudo yum install -y epel-release nginx && sudo systemctl enable --now nginx
Service
$ sudo systemctl status nginx $ sudo systemctl reload nginx $ sudo systemctl restart nginx $ sudo nginx -t # test config $ nginx -V # built modules
Key Paths#
/etc/nginx/nginx.conf(main config)/etc/nginx/conf.d/*.conf(drop‑ins)/etc/nginx/sites-available/+sites-enabled/(Debian style)/var/www/html(default docroot)logs:/var/log/nginx/access.log,/var/log/nginx/error.log
Minimal HTTP Server#
# /etc/nginx/conf.d/example.conf
server {
listen 80;
server_name example.com;
root /var/www/example/public;
location / {
try_files $uri $uri/ =404;
}
}
Config Structure#
Core Blocks#
main(global)events(worker connections)http→server→locationstream(TCP/UDP)upstream(load balancers)
user www-data;
worker_processes auto;
events { worker_connections 1024; }
http {
include mime.types;
default_type application/octet-stream;
sendfile on;
keepalive_timeout 65;
# servers / includes go here...
}
Context & Order#
locationmatch order:Exact
=^~(no regex if matched)Regex
~/~*(first match)Prefix (longest path)
try_filesevaluates in order then falls back.
location = /healthz { return 204; }
location ^~ /static/ { expires 7d; }
location ~* \.(png|jpg|css|js)$ { expires 7d; }
location / { try_files $uri $uri/ /index.html; }
Common Includes#
http {
include /etc/nginx/conf.d/*.conf;
include /etc/nginx/snippets/*.conf; # Ubuntu/Debian
}
Virtual Hosts & Redirects#
Basic Server Block#
server {
listen 80;
server_name example.com www.example.com;
root /var/www/example/public;
index index.html index.htm;
}
Redirect HTTP→HTTPS#
server {
listen 80;
server_name example.com www.example.com;
return 301 https://example.com$request_uri;
}
Canonical Host#
# Force non-www
server {
listen 80;
server_name www.example.com;
return 301 $scheme://example.com$request_uri;
}
TLS/SSL#
Basic 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;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers off;
root /var/www/example/public;
}
HSTS & Security Headers#
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header Referrer-Policy "no-referrer-when-downgrade" always;
add_header Permissions-Policy "geolocation=(), microphone=(), camera=()" always;
Let’s Encrypt (Certbot)#
$ sudo apt install -y certbot python3-certbot-nginx
$ sudo certbot --nginx -d example.com -d www.example.com
$ sudo systemctl list-timers | grep certbot # auto-renew
Reverse Proxy#
Basic Proxy#
upstream app {
server 127.0.0.1:3000;
# server unix:/run/app.sock; # alternative
}
server {
listen 80;
server_name api.example.com;
location / {
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_pass http://app;
}
}
WebSockets / HTTP Upgrade#
location /socket.io/ {
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_pass http://app;
}
Timeouts & Buffers#
proxy_connect_timeout 5s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
proxy_buffering on;
proxy_buffers 16 16k;
proxy_busy_buffers_size 24k;
Static, Compression, Caching#
Static Files#
location /assets/ {
alias /var/www/example/assets/;
access_log off;
expires 7d;
add_header Cache-Control "public, max-age=604800, immutable";
}
Gzip#
gzip on;
gzip_types text/plain text/css application/javascript application/json image/svg+xml;
gzip_min_length 1024;
gzip_comp_level 5;
(Optional) Brotli (if compiled)#
brotli on;
brotli_comp_level 5;
brotli_types text/plain text/css application/javascript application/json image/svg+xml;
Caching & Microcaching#
Proxy Cache Zone#
proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=micro:10m max_size=1g inactive=10m use_temp_path=off;
map $request_method $no_cache { default 0; POST 1; PUT 1; PATCH 1; DELETE 1; }
Use the Cache#
location /api/ {
proxy_cache micro;
proxy_cache_bypass $no_cache;
proxy_no_cache $no_cache;
proxy_cache_valid 200 301 302 10s;
proxy_cache_valid any 1s;
add_header X-Cache-Status $upstream_cache_status;
proxy_pass http://app;
}
Conditional Bypass#
# Skip cache when logged in (example cookie)
map $http_cookie $logged_in {
default 0;
~*"(session|auth|logged_in)" 1;
}
proxy_cache_bypass $logged_in;
proxy_no_cache $logged_in;
PHP‑FPM / FastCGI#
Basic PHP Handler#
location ~ \.php$ {
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
fastcgi_param DOCUMENT_ROOT $realpath_root;
fastcgi_pass unix:/run/php/php8.2-fpm.sock;
fastcgi_buffers 16 16k;
fastcgi_read_timeout 60s;
}
try_files Front Controller#
location / {
try_files $uri $uri/ /index.php?$args;
}
Security Tips#
location ~* \.(?:ini|log|sh|sql|bak)$ { deny all; }
location ~ /\.(?!well-known) { deny all; }
Rewrites & Routing#
try_files#
location / {
try_files $uri $uri/ /index.html;
}
Regex Rewrites#
# Remove trailing slash (except root)
if ($request_uri ~* "^(.+)/+$") { return 301 $1; }
# Legacy path to new path
rewrite ^/old/(.*)$ /new/$1 permanent;
SPA / History API#
location / {
try_files $uri /index.html;
}
Rate Limiting & DoS Mitigation#
Request Rate#
# 10 req/s with burst 20 per IP
limit_req_zone $binary_remote_addr zone=reqs:10m rate=10r/s;
server {
location /api/ {
limit_req zone=reqs burst=20 nodelay;
}
}
Concurrent Connections#
limit_conn_zone $binary_remote_addr zone=conns:10m;
server {
location /download/ {
limit_conn conns 10;
}
}
Body Size & Timeouts#
client_max_body_size 25m;
client_body_timeout 30s;
keepalive_timeout 65s;
Security Headers & Access#
Basic Hardening#
server_tokens off;
add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header Referrer-Policy "no-referrer-when-downgrade" always;
Allow/Deny#
location /admin/ {
allow 192.168.0.0/16;
deny all;
}
CORS (Example)#
location /api/ {
add_header Access-Control-Allow-Origin "https://app.example.com" always;
add_header Access-Control-Allow-Credentials "true" always;
if ($request_method = OPTIONS) {
add_header Access-Control-Allow-Methods "GET, POST, OPTIONS";
add_header Access-Control-Allow-Headers "Authorization, Content-Type";
return 204;
}
proxy_pass http://app;
}
Logging & Debug#
Formats#
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for" '
'$request_time $upstream_response_time';
access_log /var/log/nginx/access.log main;
error_log /var/log/nginx/error.log warn;
Per‑Location Logging#
location /healthz { access_log off; }
Debugging#
$ sudo nginx -t
$ sudo nginx -s reload
$ tail -f /var/log/nginx/error.log
Upstreams & LB#
Strategies#
Directive |
Meaning |
|---|---|
(default) |
round‑robin |
|
least connections |
|
sticky by client IP |
|
hash by custom key |
{.show-header}
Example Upstream#
upstream api_backends {
least_conn;
server 10.0.0.11:8080 max_fails=3 fail_timeout=30s;
server 10.0.0.12:8080 max_fails=3 fail_timeout=30s;
# server backup.example:8080 backup;
}
Health & Failover#
proxy_next_upstream error timeout http_502 http_503 http_504;
proxy_next_upstream_tries 3;
Useful Variables#
Request & Client#
Variable |
Description |
|---|---|
|
Host header / server name |
|
Chosen server_name |
|
Client IP |
|
User‑Agent |
|
GET/POST/… |
{.show-header .bold-first}
Paths & Files#
Variable |
Description |
|---|---|
|
Current root |
|
Symlink‑resolved root |
|
Path + query |
|
Normalized URI |
|
Raw query string |
{.show-header .bold-first}
Upstream#
Variable |
Description |
|---|---|
|
Upstream server(s) |
|
Upstream status |
|
Time from upstream |
{.show-header .bold-first}
Stream (TCP/UDP)#
TCP Proxy#
stream {
upstream db {
server 10.0.0.10:5432;
server 10.0.0.11:5432;
}
server {
listen 5432;
proxy_pass db;
}
}
UDP Proxy#
stream {
server {
listen 53 udp;
proxy_responses 1;
proxy_timeout 2s;
proxy_pass 1.1.1.1:53;
}
}
Access Control#
stream {
server {
listen 6379;
allow 10.0.0.0/8;
deny all;
proxy_pass 127.0.0.1:6379;
}
}
Snippets#
Security Snippet#
# /etc/nginx/snippets/security.conf
server_tokens off;
add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header Referrer-Policy "no-referrer-when-downgrade" always;
PHP Snippet#
# /etc/nginx/snippets/fastcgi-php.conf
location ~ \.php$ {
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
fastcgi_param DOCUMENT_ROOT $realpath_root;
fastcgi_pass unix:/run/php/php8.2-fpm.sock;
}
Proxy Headers Snippet#
# /etc/nginx/snippets/proxy-headers.conf
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;