Skip to content

Nginx 速查表

高性能 HTTP 服务器和反向代理。

01

入门

基本配置

Nginx 配置使用分层块结构。worker_processes 设置进程数。http 块包含 server 块,server 块包含 location 块。重载前务必用 nginx -t 测试。

nginx
# /etc/nginx/nginx.conf
worker_processes auto;

events {
    worker_connections 1024;
}

http {
    server {
        listen 80;
        server_name example.com;

        location / {
            root /var/www/html;
            index index.html;
        }
    }
}

# Test config: nginx -t
# Reload: nginx -s reload
# Start: systemctl start nginx

指令与上下文

上下文嵌套:main > events/http/mail > server > location。简单指令以分号结尾;块指令使用大括号。某些指令只在特定上下文中有效。

nginx
# Contexts are hierarchical blocks
# Global context (main) - outside any block
user nginx;
worker_processes auto;

# events context
events {
    worker_connections 1024;
}

# http context
http {
    # server context
    server {
        listen 80;
        # location context
        location / {
            root /var/www/html;
        }
    }
}

# Directives: simple (name value;) or block (name { ... })

测试与重载

nginx -t 检查语法并尝试打开引用的文件(证书、日志)。重载前务必运行。reload 发送 SIGHUP——worker 完成当前请求后重载。restart 会导致短暂停机。

nginx
# test configuration for syntax errors
nginx -t

# test and print full config
nginx -T

# reload config without dropping connections
nginx -s reload

# stop gracefully (finishes current requests)
nginx -s quit

# stop immediately
nginx -s stop

# reopen log files (after log rotation)
nginx -s reopen

# systemd alternative
systemctl reload nginx
systemctl restart nginx

Main 与 HTTP 上下文

worker_processes 和 events 位于 main 上下文。路由、代理、缓存和 server 块位于 http。SSL、日志和 gzip 可在 http 中设置并被 server/location 继承。

nginx
# main context - global settings
user nginx;
worker_processes auto;
worker_rlimit_nofile 65535;
pid /var/run/nginx.pid;

events {
    worker_connections 4096;
}

http {
    # all HTTP-related config lives here
    include       /etc/nginx/mime.types;
    default_type  application/octet-stream;
    sendfile      on;
    keepalive_timeout 65;

    include /etc/nginx/conf.d/*.conf;
}

包含文件

include 保持配置模块化。conf.d 通配符(*.conf)是现代约定。sites-enabled 配合符号链接是 Debian/Ubuntu 的模式。mime.types 将文件扩展名映射到 Content-Type。

nginx
# main config: /etc/nginx/nginx.conf
http {
    include /etc/nginx/mime.types;
    include /etc/nginx/conf.d/*.conf;
    include /etc/nginx/sites-enabled/*;
}

# /etc/nginx/conf.d/default.conf
server {
    listen 80;
    server_name example.com;
    root /var/www/html;
}

# Disable a site: remove the file from sites-enabled/
# Available sites kept in sites-available/, symlinked into enabled/

命令行操作

nginx -V 显示编译时选项,包括内置了哪些模块。-g 设置覆盖配置的指令。-c 用于在不改动默认配置的情况下测试备用配置。

nginx
# start nginx (if not managed by systemd)
nginx

# specify a custom config file
nginx -c /path/to/nginx.conf

# set a global directive from command line
nginx -g "worker_processes 4;"

# test config
nginx -t

# print version and build configuration
nginx -V

# send signal to master process
nginx -s reload
nginx -s stop
02

服务器块

基本服务器块

server 块定义一个虚拟主机。listen 设置端口。server_name 匹配 Host 头。root 设置文档根目录。index 指定目录请求时提供哪些文件。

nginx
server {
    listen 80;
    server_name example.com www.example.com;
    root /var/www/example;
    index index.html index.htm;

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

多个服务器名

Nginx 按精确匹配、通配符(*.foo.com)、正则的顺序匹配 server_name。第一个匹配的 server 块处理请求。在同一端口上为不同应用使用不同的块。

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

server {
    listen 80;
    server_name api.example.com;
    # ...
}

# Wildcard: matches any single-level subdomain
server_name *.example.com;

# Regex (starts with ~)
server_name ~^www\d+\.example\.com$;

默认服务器

default_server 处理 Host 头不匹配任何 server_name 的请求。下划线是一种约定(无效主机名,永不匹配真实流量)。return 444 不返回响应直接关闭连接。

nginx
# mark a server block as default for the port
server {
    listen 80 default_server;
    server_name _;
    return 444;  # drop connection (no response)
}

# handles all requests that don't match another server_name
# _ is an invalid hostname, used as catch-all convention
# useful to block requests with no matching Host header

listen 指令

listen 可以绑定到特定地址、IPv6、UNIX socket,或设置 default_server。ssl 参数为该监听器启用 TLS。一个 server 块中允许多个 listen 指令。

nginx
# listen on port 80
listen 80;

# IPv6
listen [::]:80;

# HTTPS (ssl flag)
listen 443 ssl;
listen [::]:443 ssl;

# specific address
listen 127.0.0.1:8080;

# default server for this port
listen 80 default_server;

# listen on UNIX socket
listen unix:/var/run/nginx.sock;

服务器名匹配

匹配优先级为精确 > 前导通配符 > 后置通配符 > 正则。正则按配置顺序检查,因此第一个匹配生效。如果 server 名很长或很多,请增大哈希桶大小。

nginx
# Order of precedence (highest to lowest):
# 1. Exact name: example.com
# 2. Longest wildcard starting with *: *.example.com
# 3. Longest wildcard ending with *: www.*
# 4. First matching regex (in config order): ~^www\d+\.

# If nothing matches, the default_server handles it

# Tune hash sizes for many server names
http {
    server_names_hash_bucket_size 64;
    server_names_hash_max_size 512;
}

全捕获服务器

全捕获服务器防止带有任意 Host 头的请求到达你的应用。return 301 重定向到已知主机。return 444 直接断开连接,适用于抵御直接访问你 IP 的扫描器。

nginx
# catch all requests regardless of host header
server {
    listen 80 default_server;
    server_name _;  # invalid name, never matches real host

    return 301 https://$host$request_uri;
}

# or drop the connection silently
server {
    listen 80 default_server;
    server_name _;
    return 444;
}

# redirect unknown subdomains to main site
server {
    listen 80 default_server;
    return 301 https://example.com;
}
03

Location 块

Location 修饰符

修饰符改变匹配行为:=(精确)、^~(前缀,跳过正则)、~(区分大小写正则)、~*(不区分大小写正则)、无(前缀)。正则适合文件扩展名;前缀适合路径树。

nginx
# exact match - fastest, stops search if matched
location = /favicon.ico {
    log_not_found off;
}

# prefix match with ^~ - skips regex if longest prefix matches
location ^~ /static/ {
    root /var/www;
}

# case-sensitive regex
location ~ \.php$ {
    fastcgi_pass unix:/run/php/php-fpm.sock;
}

# case-insensitive regex
location ~* \.(jpg|jpeg|png|gif|ico)$ {
    expires 30d;
}

# prefix match (no modifier)
location / {
    try_files $uri $uri/ /index.php;
}

Location 优先级

Nginx 先评估精确(=),然后前缀匹配(最长优先),然后按配置顺序的正则。^~ 使前缀匹配跳过正则评估。理解此顺序可避免重叠 location 的意外。

nginx
# Priority order (highest to lowest):
# 1. =     exact match
# 2. ^~    prefix match (skips regex if matched)
# 3. ~ ~*  regex (first match in config order)
# 4. (none) prefix match (longest wins)

location = /api {
    # exact, highest priority
}

location ^~ /assets/ {
    # prefix, beats regex
}

location ~ \.php$ {
    # regex
}

location /api/ {
    # prefix, lowest of these
}

root 与 alias

root 将 URI 追加到其值后;alias 替换匹配的前缀。当 location 映射到目录树的子目录时用 root。当 URI 路径与文件系统路径不同时用 alias。alias 配合正则需要 $1 捕获。

nginx
# root: appends the full URI to the path
# request /img/cat.png -> /var/www/img/cat.png
location /img/ {
    root /var/www;
}

# alias: replaces the matched location part
# request /img/cat.png -> /data/images/cat.png
location /img/ {
    alias /data/images/;
}

# alias with regex requires capturing the path
location ~ ^/img/(.*)$ {
    alias /data/images/$1;
}

try_files 指令

try_files 按顺序检查每个参数;最后一个是回退(不检查文件)。$uri 检查文件,$uri/ 检查目录。回退可以是 URI、=code 或 @named。对 SPA 和 CMS 路由至关重要。

nginx
# try files in order, fall back to last option
location / {
    try_files $uri $uri/ /index.html;
}

# serve index.php if file/dir not found (pretty URLs)
location / {
    try_files $uri $uri/ /index.php?$query_string;
}

# return 404 if nothing matches
location / {
    try_files $uri =404;
}

# named location as fallback
location / {
    try_files $uri $uri/ @drupal;
}

命名 Location

命名 location(@name)是内部专用的——永远不会被请求 URI 直接匹配。它们是 try_files、error_page 和 rewrite 的目标。适合在多个 location 间共享配置块(如回退处理器)。

nginx
# named locations start with @, can't be reached externally
# used as try_files fallback or rewrite target

location / {
    try_files $uri $uri/ @app;
}

location @app {
    fastcgi_pass unix:/run/php.sock;
    # ... fastcgi config
}

# useful for organizing common error logic
location @error_page {
    return 500 "Internal Server Error";
}

# can also be used with error_page
error_page 404 @fallback;
location @fallback {
    proxy_pass http://backend;
}

嵌套 Location

location 可以嵌套。内部 location 继承并可以覆盖外部 location 的指令。适用于在更宽泛的前缀内特殊处理某个路径(例如上传端点设置更大的 body 大小,或从某个子路径提供静态文件)。

nginx
location /api {
    # /api/users, /api/posts handled here
    proxy_pass http://backend;

    location /api/upload {
        # override for upload endpoint
        client_max_body_size 50M;
        proxy_pass http://upload_backend;
    }

    location ~ ^/api/static/(.*)$ {
        # regex inside prefix location
        root /var/www;
    }
}
04

反向代理

基本 proxy_pass

proxy_pass 将请求转发到上游服务器。proxy_pass 末尾的斜杠会导致匹配的 location 前缀被替换。没有末尾斜杠时,原始 URI 原样传递。

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

    location / {
        proxy_pass http://localhost:3000;
    }
}

# proxy_pass with URI replacement (trailing slash matters)
location /api/ {
    proxy_pass http://backend/;  # /api/foo -> /foo
}

# without trailing slash, URI is preserved
location /api/ {
    proxy_pass http://backend;   # /api/foo -> /api/foo
}

代理头

默认 nginx 不传递客户端 IP 或原始主机。设置 X-Real-IP 和 X-Forwarded-For 让后端识别真实客户端。X-Forwarded-Proto 告知原始协议,对 HTTPS 重定向逻辑至关重要。

nginx
location / {
    proxy_pass http://backend;

    # preserve original host
    proxy_set_header Host $host;

    # real client IP (not nginx's)
    proxy_set_header X-Real-IP $remote_addr;

    # forwarded-for chain
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

    # scheme (http or https)
    proxy_set_header X-Forwarded-Proto $scheme;

    # original host
    proxy_set_header X-Forwarded-Host $host;
}

Upstream 代理

upstream 块定义一个命名的服务器池,供 proxy_pass 引用。这实现了负载均衡、健康检查和 keepalive 连接。upstream 变量可用于记录哪个后端处理了每个请求。

nginx
# define a named pool of backend servers
upstream backend {
    server 10.0.0.1:8080;
    server 10.0.0.2:8080;
    server 10.0.0.3:8080;
}

server {
    listen 80;

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

# useful upstream variables:
# $upstream_addr, $upstream_status,
# $upstream_response_time, $upstream_cache_status

代理超时

proxy_connect_timeout 是 TCP 握手限制;读/写超时适用于数据传输期间的空闲期。为慢端点(导出、AI 推理)增大 read_timeout。upstream keepalive 复用连接以降低延迟。

nginx
location / {
    proxy_pass http://backend;

    # connect timeout (TCP handshake to upstream)
    proxy_connect_timeout 5s;

    # time between reads from upstream (response)
    proxy_read_timeout 60s;

    # time between writes to upstream (request body)
    proxy_send_timeout 60s;

    # for slow backends (large uploads/downloads)
    proxy_read_timeout 300s;
}

# keepalive connections to upstream
upstream backend {
    server 10.0.0.1:8080;
    keepalive 32;
}

代理缓冲

缓冲将上游响应存储在内存/磁盘后再发送给客户端——对慢客户端高效但会破坏流式传输。对 SSE、WebSocket 或分块流式传输请关闭。buffer_size 是第一部分(头);buffers 持有其余部分。

nginx
location / {
    proxy_pass http://backend;

    # enable buffering (default on)
    proxy_buffering on;
    proxy_buffer_size 4k;
    proxy_buffers 8 4k;

    # busy buffers during streaming
    proxy_busy_buffers_size 16k;

    # disable for streaming/SSE
    proxy_buffering off;
    proxy_cache off;
}

# Server-Sent Events or streaming responses
location /events {
    proxy_pass http://backend;
    proxy_buffering off;
    proxy_cache off;
    chunked_transfer_encoding on;
}

WebSocket 升级

WebSocket 需要 HTTP/1.1 加上 Upgrade 和 Connection 头。map 块将 Upgrade 头转换为正确的 Connection 值(upgrade 对 close)。较长的 proxy_read_timeout 防止空闲 WebSocket 被关闭。

nginx
# Map the Upgrade header to a Connection value
map $http_upgrade $connection_upgrade {
    default upgrade;
    ''      close;
}

server {
    listen 80;

    location /ws {
        proxy_pass http://backend;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection $connection_upgrade;

        # keepalive for long-lived connections
        proxy_read_timeout 3600s;
    }
}
05

负载均衡

Upstream 块

upstream 块定义后端服务器。默认负载均衡是轮询。max_fails 和 fail_timeout 配置被动健康检查:在 fail_timeout 内失败 max_fails 次后,服务器被标记为下线 fail_timeout 秒。

nginx
upstream backend {
    server backend1.example.com weight=5;
    server backend2.example.com;
    server backend3.example.com max_fails=3 fail_timeout=30s;
}

server {
    location / {
        proxy_pass http://backend;
    }
}

# default algorithm: round-robin
# max_fails: failed attempts before marking server down
# fail_timeout: window for counting fails AND downtime duration

负载均衡方法

round-robin 均匀分配;least_conn 发送给负载最低的服务器;ip_hash 让客户端保持在同一服务器(通过 IP 实现粘性会话);hash 按自定义键分配。consistent 参数在增删服务器时最小化重分布。

nginx
# round-robin (default) - even distribution
upstream backend {
    server s1.example.com;
    server s2.example.com;
}

# least connections - fewest active connections
upstream backend {
    least_conn;
    server s1.example.com;
    server s2.example.com;
}

# ip_hash - sticky by client IP (session persistence)
upstream backend {
    ip_hash;
    server s1.example.com;
    server s2.example.com;
}

# generic hash (key-based, e.g., by URI or header)
upstream backend {
    hash $request_uri consistent;
    server s1.example.com;
    server s2.example.com;
}

加权轮询

weight 按比例控制流量分配。默认权重为 1。为更强大的服务器设置更高权重,使其承担更大份额。weight 适用于 round-robin、least_conn 和 ip_hash。

nginx
upstream backend {
    server s1.example.com weight=3;  # 3x traffic
    server s2.example.com weight=2;  # 2x traffic
    server s3.example.com weight=1;  # 1x traffic (default)
}

# total weight = 6
# s1 gets 3/6 = 50%, s2 gets 2/6 = 33%, s3 gets 1/6 = 17%

# useful when servers have different capacity
# e.g., a 4-core server gets weight=4, a 2-core gets weight=2

健康检查

开源 nginx 使用基于真实请求失败的被动健康检查(max_fails/fail_timeout)。NGINX Plus 增加了定期轮询 URI 的主动健康检查。服务器必须连续通过 'passes' 次检查才能重新标记为健康。

nginx
upstream backend {
    server s1.example.com max_fails=3 fail_timeout=30s;
    server s2.example.com max_fails=3 fail_timeout=30s;

    # passive health check (default behavior):
    # 3 failures within 30s marks the server down
    # it stays down for fail_timeout (30s) before retry
}

# NGINX Plus active health check
upstream backend {
    server s1.example.com;
    server s2.example.com;
    health_check interval=10s fails=3 passes=2;
    health_check uri=/health;
}

会话保持

ip_hash 通过客户端 IP 提供粘性会话,但如果 IP 变化(移动网络)则会失效。hash 指令配合 consistent 在池变化时提供最小的键重分布。sticky cookie(Plus 版)设置 cookie 将客户端固定到某台服务器。

nginx
# ip_hash: routes by client IP (uses first 3 IPv4 octets)
upstream backend {
    ip_hash;
    server s1.example.com:8080;
    server s2.example.com:8080;
}

# sticky cookie (NGINX Plus only)
upstream backend {
    sticky cookie srv_id expires=1h domain=.example.com path=/;
    server s1.example.com;
    server s2.example.com;
}

# hash with consistent parameter for minimal redistribution
upstream backend {
    hash $http_x_session_id consistent;
    server s1.example.com;
    server s2.example.com;
}

备用服务器

标记了 backup 参数的服务器仅在所有主服务器全部故障时才接收流量。常见模式:主服务器提供应用;备用服务器提供静态维护页面。备用服务器不参与正常负载均衡。

nginx
upstream backend {
    server s1.example.com;
    server s2.example.com;

    # used only when all primaries are down
    server s3.example.com backup;
    server s4.example.com backup;
}

# backup servers receive traffic ONLY when all
# non-backup servers are unavailable
# useful for a maintenance page or DR fallback

upstream backend {
    server primary.example.com;
    server standby.example.com backup;
}
06

SSL/TLS 配置

基本 HTTPS 服务器

listen 上的 ssl 参数启用 TLS。ssl_certificate 指向证书链(服务器证书 + 中间证书);ssl_certificate_key 指向私钥。始终使用 fullchain.pem 以便客户端能验证证书链。

nginx
server {
    listen 443 ssl;
    server_name example.com;

    ssl_certificate     /etc/ssl/certs/example.com.crt;
    ssl_certificate_key /etc/ssl/private/example.com.key;

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

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

SSL 证书设置

Let's Encrypt 将证书存储在 /etc/letsencrypt/live/<域名>/ 下。fullchain.pem 包含中间证书;privkey.pem 是私钥。nginx 支持每个服务器多种证书类型,根据客户端能力选择。

nginx
# Let's Encrypt paths
ssl_certificate     /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;

# multiple cert types (RSA + ECDSA) on one server
server {
    listen 443 ssl;
    server_name example.com;

    ssl_certificate     /etc/ssl/example.com.rsa.crt;
    ssl_certificate_key /etc/ssl/example.com.rsa.key;

    ssl_certificate     /etc/ssl/example.com.ecdsa.crt;
    ssl_certificate_key /etc/ssl/example.com.ecdsa.key;
}

HTTP 到 HTTPS 重定向

最干净的模式是使用专用的 80 端口服务器进行重定向。$host 保留原始主机名。在合并块中使用 if ($scheme) 可以工作,但独立服务器方式更受推荐,可避免 'if is evil' 陷阱。

nginx
# redirect all HTTP traffic to HTTPS
server {
    listen 80;
    server_name example.com www.example.com;
    return 301 https://$host$request_uri;
}

# single server block handling both
server {
    listen 80;
    listen 443 ssl;
    server_name example.com;

    ssl_certificate     /etc/ssl/example.com.crt;
    ssl_certificate_key /etc/ssl/example.com.key;

    if ($scheme != "https") {
        return 301 https://$host$request_uri;
    }
}

SSL 协议与密码套件

禁用旧协议(SSLv3、TLSv1.0/1.1)。TLS 1.3 最快最安全。使用 Mozilla SSL 配置生成器获取经过测试的配置。shared:SSL 缓存可加速跨 worker 的会话恢复。

nginx
server {
    listen 443 ssl;
    server_name example.com;

    ssl_certificate     /etc/ssl/example.com.crt;
    ssl_certificate_key /etc/ssl/example.com.key;

    # modern configuration (Mozilla SSL Config Generator)
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256;
    ssl_prefer_server_ciphers off;

    # session settings
    ssl_session_tickets off;
    ssl_session_timeout 1d;
    ssl_session_cache shared:SSL:50m;
}

HSTS

HSTS 告知浏览器始终对该域名使用 HTTPS,防止 SSL 剥离攻击。max-age 单位为秒(31536000 = 1 年)。includeSubDomains 覆盖所有子域名。preload 将站点提交到浏览器内置列表——基本不可逆,谨慎使用。

nginx
server {
    listen 443 ssl;
    server_name example.com;

    # HSTS: force HTTPS for 1 year (include subdomains)
    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;

    # preload list (be very careful — requires permanent HTTPS)
    add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
}

# Preload commits your domain to a browser-baked HTTPS-only list.
# Only enable preload after verifying all subdomains work on HTTPS.

OCSP 装订

OCSP 装订让 nginx 获取并提供证书的吊销状态,省去客户端访问 OCSP 响应器的一趟往返。ssl_trusted_certificate 用于验证 OCSP 响应。需要 resolver 让 nginx 能访问 OCSP 服务器。

nginx
server {
    listen 443 ssl;
    server_name example.com;

    ssl_certificate     /etc/ssl/example.com/fullchain.pem;
    ssl_certificate_key /etc/ssl/example.com/privkey.pem;

    ssl_stapling on;
    ssl_stapling_verify on;

    # trust chain for verification
    ssl_trusted_certificate /etc/ssl/example.com/chain.pem;

    # DNS resolver needed for OCSP requests
    resolver 8.8.8.8 8.8.4.4 valid=300s;
    resolver_timeout 5s;
}
07

静态文件服务

提供静态文件

root 定义文档根目录;nginx 将请求 URI 映射到文件路径。try_files 检查文件、然后目录,然后返回 404。在 server 级别设置 root 让所有 location 继承,但可以按 location 覆盖。

nginx
server {
    listen 80;
    server_name example.com;
    root /var/www/html;

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

# root set at server level is inherited by all locations
# request /images/logo.png -> /var/www/html/images/logo.png
# request /css/style.css   -> /var/www/html/css/style.css

index 指令

index 列出当请求针对目录时要尝试的文件。第一个存在的文件将被提供。如果都不存在且 autoindex 关闭,nginx 返回 403。顺序很重要——将首选文件列在前面。

nginx
server {
    listen 80;
    root /var/www/html;

    # try these files in order when request targets a directory
    index index.html index.htm index.php;

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

# request / -> tries /var/www/html/index.html, then index.htm
# request /about/ -> tries /var/www/html/about/index.html
# if no index file found and autoindex off -> 403 Forbidden

SPA 的 try_files

SPA 使用客户端路由,因此未知路径必须提供 index.html。try_files $uri $uri/ /index.html 正好做到这一点。用 immutable 缓存哈希资源一年,因为其内容寻址文件名保证内容变化时 URL 也变化。

nginx
# Single Page Application (React, Vue, Angular)
server {
    listen 80;
    root /var/www/spa;

    location / {
        # serve static files, fall back to index.html for client routing
        try_files $uri $uri/ /index.html;
    }

    # cache static assets aggressively (filename usually hashed)
    location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff2?)$ {
        expires 1y;
        add_header Cache-Control "public, immutable";
    }
}

自动索引(目录列表)

autoindex 生成 HTML 目录列表。适用于文件下载站点。关闭 exact_size 提高可读性,开启 localtime 显示正确时间戳。切勿在有敏感文件的目录上启用——务必配合访问控制。

nginx
location /files/ {
    autoindex on;
    autoindex_exact_size off;  # human-readable sizes (KB, MB)
    autoindex_localtime on;    # show local time instead of UTC
    root /var/www;
}

# /files/        -> directory listing
# /files/doc.pdf -> downloads the file
# /files/sub/    -> listing of subdirectory

# disable for security on sensitive directories
location /private/ {
    autoindex off;
    deny all;
}

过期头

expires 设置 Cache-Control 的 max-age。immutable 告知浏览器文件永不变(跳过重新验证)。对于哈希资源(file.abc123.js),1 年 + immutable 是理想选择。HTML 应始终重新验证以便用户立即获取更新。

nginx
# cache static assets for a year
location ~* \.(jpg|jpeg|png|gif|ico|css|js|svg|woff|woff2)$ {
    expires 1y;
    add_header Cache-Control "public, immutable";
    access_log off;
}

# never cache HTML (always revalidate)
location ~* \.html$ {
    add_header Cache-Control "no-cache, no-store, must-revalidate";
}

# short cache for API responses
location /api/ {
    add_header Cache-Control "no-cache";
}

禁用静态文件日志

静态资源和 favicon/robots 请求会使访问日志变得杂乱。access_log off 停止记录;log_not_found off 抑制 404 日志条目。这使日志聚焦于有意义的流量,并减少繁忙站点的磁盘 I/O。

nginx
# don't log requests for static assets
location ~* \.(jpg|jpeg|png|gif|ico|css|js|svg|woff|woff2|ttf|eot)$ {
    access_log off;
    log_not_found off;
    expires 1y;
}

# don't log favicon requests (often 404 from browsers)
location = /favicon.ico {
    log_not_found off;
    access_log off;
}

# don't log robots.txt
location = /robots.txt {
    log_not_found off;
    access_log off;
}
08

Gzip 压缩

启用 Gzip

gzip 压缩响应,减少带宽。gzip_vary 为缓存添加 'Vary: Accept-Encoding'。gzip_proxied any 为代理请求压缩。comp_level 6 平衡 CPU 和大小。min_length 256 跳过微小响应(开销超过收益)。

nginx
http {
    gzip on;
    gzip_vary on;
    gzip_proxied any;
    gzip_comp_level 6;
    gzip_min_length 256;
    gzip_types
        text/plain
        text/css
        text/xml
        application/json
        application/javascript
        application/xml+rss
        application/atom+xml
        image/svg+xml;
}

Gzip 类型

gzip_types 列出要压缩的 MIME 类型。text/html 默认总是被压缩。不要压缩已经压缩过的二进制格式(图片、视频、归档)——浪费 CPU 却没有体积减少,甚至可能增大输出。

nginx
http {
    gzip on;
    gzip_types
        text/plain
        text/css
        text/xml
        text/javascript
        application/javascript
        application/x-javascript
        application/json
        application/xml
        application/xml+rss
        application/rss+xml
        application/atom+xml
        image/svg+xml
        font/ttf
        font/otf
        application/vnd.ms-fontobject;

    # text/html is always compressed (implicitly, cannot disable)
    # DON'T compress already-compressed: jpg, png, gif, mp4, zip, gz
}

Gzip 压缩级别

comp_level 在 CPU 与压缩之间权衡。6 是最佳点——9 使用多得多的 CPU 却只换来不到 2% 的体积减少。min_length 避免压缩 gzip 头开销超过节省量的微小响应。gzip_disable 排除 gzip 支持损坏的旧版 IE6。

nginx
http {
    gzip on;
    gzip_comp_level 6;  # 1-9, higher = more compression, more CPU

    # level 1: fast, low compression
    # level 6: good balance (recommended default)
    # level 9: slow, marginal size gain

    # minimum response size to compress (bytes)
    gzip_min_length 256;

    # disable gzip for old browsers
    gzip_disable "MSIE [1-6]\.";
}

Gzip 静态

gzip_static 提供预构建的 .gz 文件,消除运行时压缩的 CPU 开销。构建步骤在原始文件旁创建 .gz 文件。nginx 向发送 Accept-Encoding: gzip 的客户端提供 .gz,向其他客户端提供原始文件。非常适合静态资源管线。

nginx
# serve pre-compressed .gz files (no runtime compression cost)
# requires ngx_http_gzip_static_module

location / {
    gzip_static on;
    # on:     serve .gz if it exists
    # always: serve .gz even without Accept-Encoding header

    root /var/www/html;
}

# pre-compress files at build time:
# find /var/www/html -type f ( -name "*.css" -o -name "*.js" \
#   -o -name "*.html" ) -exec gzip -k -9 {} \;
# creates file.css.gz, file.js.gz alongside the originals

Brotli

Brotli 在文本内容上优于 gzip,尤其在较高压缩级别。它需要第三方 ngx_brotli 模块。brotli_static 提供预构建的 .br 文件。现代浏览器在 Accept-Encoding 中声明 'br',因此 nginx 选择最佳可用编码。

nginx
# requires ngx_brotli module (not built-in)
http {
    brotli on;
    brotli_comp_level 6;
    brotli_types
        text/plain
        text/css
        application/javascript
        application/json
        image/svg+xml;

    # serve pre-compressed .br files
    brotli_static on;
}

# Brotli typically 10-20% smaller than gzip for text
# supported by all modern browsers (not IE)
# nginx can serve both .br and .gz; picks based on Accept-Encoding

Gzip 代理

默认 nginx 不压缩代理响应(避免对已压缩的上游双重压缩)。gzip_proxied any 为所有代理请求启用压缩。gzip_vary 确保缓存为压缩/未压缩客户端存储不同版本。

nginx
http {
    gzip on;
    gzip_proxied any;
    # gzip_proxied controls compression for proxied requests:
    # off:           never compress proxied requests
    # expired:       compress if response has Expires header
    # no-cache:      compress if Cache-Control: no-cache
    # no-store:      compress if Cache-Control: no-store
    # private:       compress if Cache-Control: private
    # no_last_modified: compress if no Last-Modified header
    # no_etag:       compress if no ETag header
    # auth:          compress if Authorization header present
    # any:           compress all proxied requests

    gzip_vary on;  # add "Vary: Accept-Encoding" header
}
09

缓存

代理缓存

proxy_cache 将上游响应存储在磁盘上。keys_zone 定义缓存键的共享内存区(10m 约等于 80,000 个键)。max_size 限制磁盘使用;inactive 移除在该时间内未访问的条目。use_temp_path=off 直接写入缓存以提高速度。

nginx
http {
    # define cache zone on disk
    proxy_cache_path /var/cache/nginx levels=1:2
        keys_zone=api_cache:10m
        max_size=1g
        inactive=60m
        use_temp_path=off;

    server {
        location /api/ {
            proxy_pass http://backend;
            proxy_cache api_cache;
            proxy_cache_valid 200 10m;
            proxy_cache_valid 404 1m;
        }
    }
}

缓存区

levels=1:2 从缓存键的 MD5 哈希创建两级目录树,避免一个目录中文件过多。按内容类型分设独立缓存区可分别调优 max_size 和 inactive——静态资源缓存更久,API 缓存更短。

nginx
http {
    proxy_cache_path /var/cache/nginx
        levels=1:2              # directory hierarchy depth
        keys_zone=my_cache:10m  # zone name + shared memory size
        max_size=1g             # max disk size for cache
        inactive=60m            # remove if not accessed in 60min
        use_temp_path=off;      # write directly (faster, no temp copy)

    # multiple zones for different content types
    proxy_cache_path /var/cache/static levels=1:2
        keys_zone=static:10m max_size=500m inactive=24h;
    proxy_cache_path /var/cache/api levels=1:2
        keys_zone=api:10m max_size=200m inactive=10m;
}

缓存键

缓存键决定缓存唯一性。默认键可能在不同虚拟主机间产生冲突——请包含 $host。对于用户特定内容,请包含用户标识。proxy_cache_bypass 跳过缓存读取(但仍会存储);proxy_no_cache 不存储响应。

nginx
location / {
    proxy_pass http://backend;
    proxy_cache api_cache;

    # default key: $scheme$proxy_host$request_uri
    proxy_cache_key "$scheme$request_method$host$request_uri";

    # include user-specific data to avoid cross-user leaks
    proxy_cache_key "$scheme$host$request_uri$http_x_user_id";

    # bypass cache for authenticated users
    proxy_cache_bypass $http_authorization;
    proxy_no_cache $http_authorization;
}

缓存有效期

proxy_cache_valid 按状态码设置 TTL。0s 或省略某状态码可阻止缓存。proxy_cache_lock 串行化同一键的并发缓存未命中——只有一个请求访问上游;其余等待缓存响应。

nginx
location / {
    proxy_pass http://backend;
    proxy_cache api_cache;

    # cache by response status code
    proxy_cache_valid 200 302 10m;
    proxy_cache_valid 404 1m;
    proxy_cache_valid 500 502 503 504 0s;  # don't cache errors

    # fallback for any other status
    proxy_cache_valid any 1m;

    # cache lock: collapse concurrent misses into one upstream call
    proxy_cache_lock on;
    proxy_cache_lock_timeout 5s;
}

绕过缓存

proxy_cache_bypass 跳过读取缓存(访问上游)但可能仍会存储响应。proxy_no_cache 跳过存储。X-Cache-Status 头对调试非常宝贵——HIT 表示从缓存提供,MISS 表示从上游获取。

nginx
location / {
    proxy_pass http://backend;
    proxy_cache api_cache;

    # bypass cache (always go to upstream)
    proxy_cache_bypass $http_authorization $arg_nocache;

    # don't store in cache
    proxy_no_cache $http_authorization;

    # add header showing cache status for debugging
    add_header X-Cache-Status $upstream_cache_status;
}

# $upstream_cache_status values:
# MISS, BYPASS, EXPIRED, STALE, UPDATING, REVALIDATED, HIT

浏览器缓存

浏览器缓存降低重复访问延迟。immutable 完全跳过重新验证(仅对内容寻址文件名安全)。no-cache 表示每次重新验证(仍允许条件 GET)。no-store 表示永不存储。配合 ETag 可实现高效的 304 响应。

nginx
# cache static assets in the browser
location ~* \.(jpg|jpeg|png|gif|ico|css|js|woff2?)$ {
    expires 1y;
    add_header Cache-Control "public, immutable";
}

# HTML - revalidate always
location ~* \.html$ {
    add_header Cache-Control "no-cache, must-revalidate";
    etag on;
}

# API responses - never cache
location /api/ {
    add_header Cache-Control "no-store";
}

# public:    any cache (browser, CDN) may store
# private:   only the browser may store
# immutable: file never changes, skip revalidation
# no-store:  never cache
10

URL 重写

return 指令

return 是最简单的重定向或响应方式,立即停止处理。用 301 做永久重定向,302 做临时重定向。可以返回带 body 的状态码(200、418)或重定向到 URL。重定向时优先用 return 而非 rewrite。

nginx
# simplest redirect/rewrite - return stops processing
server {
    location /old {
        return 301 /new;
    }

    location /external {
        return 301 https://example.com$request_uri;
    }

    location /api {
        return 200 "OK\n";
    }

    location /teapot {
        return 418 "I'm a teapot";
    }
}

# return is preferred over rewrite for simple redirects

rewrite 指令

rewrite 用正则匹配 URI 并替换。last 用新 URI 重新评估 location 匹配(相当于从头开始)。break 停止重写但留在当前 location。redirect/permanent 向浏览器发送重定向。

nginx
# rewrite regex replacement [flag]
server {
    # rewrite /old/* to /new/* preserving the captured part
    rewrite ^/old/(.*)$ /new/$1 permanent;

    # rewrite old blog URLs to new structure
    rewrite ^/blog/(\d{4})/(\d{2})/(.*)$ /articles/$1/$2/$3 last;

    # capture with named groups
    rewrite ^/download/(.*)$ /assets/$1 last;
}

# flags:
# last:      re-search locations with the new URI
# break:     stop rewrite, continue in current location
# redirect:  302 temporary redirect
# permanent: 301 permanent redirect

rewrite 标志

last 与 break 是最令人困惑的部分。last 重新运行 location 搜索(当重写指向不同 location 时有用)。break 停止重写但留下(当重写只是调整内部路径时有用)。redirect/permanent 改变浏览器 URL。

nginx
location / {
    # last: restart location matching with new URI
    rewrite ^/api/(.*)$ /backend/$1 last;

    # break: stop processing rewrites, stay in current location
    rewrite ^/static/(.*)$ /assets/$1 break;

    # redirect: 302 temporary redirect (browser URL changes)
    rewrite ^/old/(.*)$ /new/$1 redirect;

    # permanent: 301 permanent redirect (browser URL changes)
    rewrite ^/old/(.*)$ /new/$1 permanent;
}

# avoid rewrite inside if blocks - use location + try_files instead

命名捕获

命名捕获 (?<名称>模式) 创建变量($名称),比位置式 $1、$2 更清晰。它们在 location 正则和 rewrite 中都有效。捕获的值在该 location 内可用于 proxy_pass、fastcgi_param、add_header 和 log_format。

nginx
# named captures in regex locations
location ~ ^/users/(?<username>[a-z]+)/?$ {
    proxy_pass http://backend/users/$username;
}

# named captures in rewrite
location / {
    rewrite ^/articles/(?<year>\d{4})/(?<slug>[a-z-]+)/?$
        /blog?year=$year&slug=$slug last;
}

# named captures become variables: $year, $slug, $username
# accessible in proxy_pass, fastcgi_pass, headers, etc.
# safer than $1, $2 positional captures for readability

map 指令

map 构建从一个变量到另一个变量的查找表,惰性求值。比 if 语句链高效得多且更易读。default 分支处理未匹配的值。正则匹配(以 ~ 为前缀)按顺序检查。

nginx
# map creates a new variable based on another variable's value
map $http_host $backend {
    default            "http://default.backend";
    "a.com"            "http://a.backend";
    "b.com"            "http://b.backend";
    "~^.*\.app\.com$" "http://app.backend";
}

server {
    server_name a.com b.com;
    location / {
        proxy_pass $backend;
    }
}

# map is evaluated lazily and cached per request
# more efficient than if/else chains

if 指令(谨慎使用)

if 在 location 块内有令人意外的行为——可能以不可预测的方式重写配置。唯一普遍安全的用法是 return 和 rewrite。文件存在检查请用 try_files。复杂逻辑请用 map 或独立的 location 块。参见 wiki:'If Is Evil'。

nginx
# "if is evil" — use sparingly! Only safe for:
# return / rewrite, and with these checks:
#   -f, -d, -e, -x (file/dir/exists/executable)
#   =, !=, ~, ~* (string/regex comparison)

location / {
    if ($request_method = POST) {
        return 405;
    }

    if ($http_user_agent ~* "bot") {
        return 403;
    }

    # BAD: don't do this inside if
    # if (-f $request_filename) { ... }
    # use try_files instead
    try_files $uri $uri/ /index.html;
}
11

重定向

永久重定向(301)

301 表示'永久移动'——浏览器和搜索引擎会缓存它,因此只用于真正永久性的变更。先用 302 测试重定向以避免锁定错误。301 将链接权重(SEO)传递到新 URL。

nginx
# 301 - permanent redirect (cached by browsers indefinitely)
server {
    listen 80;
    server_name old-domain.com;

    # redirect entire site to a new domain
    return 301 https://new-domain.com$request_uri;
}

location /old-page {
    return 301 /new-page;
}

# 301 caches indefinitely in browsers — test with 302 first!
# Search engines treat 301 as a permanent move and update indexes.

临时重定向(302)

302 在重定向时将 POST 改为 GET(历史行为)。307 和 308 保留方法——对重新提交 POST 很重要。303 显式转换为 GET(表单提交后有用,可防止刷新时重复提交)。根据是否需要保留方法来选择。

nginx
# 302 - temporary redirect (not cached by default)
location /maintenance {
    return 302 /maintenance.html;
}

# 307 - temporary, preserves HTTP method (POST stays POST)
location /api/v1 {
    return 307 /api/v2;
}

# 308 - permanent, preserves HTTP method
location /api/v1 {
    return 308 /api/v2;
}

# 303 - see other (always uses GET for the new request)
location /form-submit {
    return 303 /success;
}

www 重定向到非 www

选择一种规范形式(www 或非 www)并重定向另一种。两个 server 块是最干净的模式:非规范的那个只做重定向。$request_uri 保留路径和查询字符串。HTTPS 重定向也适用相同逻辑。

nginx
# www to non-www
server {
    listen 80;
    server_name www.example.com;
    return 301 https://example.com$request_uri;
}

server {
    listen 80;
    server_name example.com;
    # ... main site config
}

# non-www to www (reverse direction)
server {
    listen 80;
    server_name example.com;
    return 301 https://www.example.com$request_uri;
}

HTTP 重定向到 HTTPS

$host 保留原始主机名,因此重定向适用于任何 server_name。$request_uri 保留路径和查询。对于非标准端口,在 $host 后追加 :端口。专用的 80 端口 server 块比按 location 的 if 检查更干净。

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

# with a non-standard HTTPS port
server {
    listen 8080;
    return 301 https://$host:8443$request_uri;
}

# redirect only a specific path
location /secure {
    if ($scheme = http) {
        return 301 https://$host$request_uri;
    }
}

重定向到其他域名

域名迁移时,$request_uri 保留完整路径和查询,使链接不断裂。当路径结构变化时使用带捕获的 rewrite。301 适用于永久迁移——它将 SEO 排名转移到新域名。

nginx
# domain migration — preserve all paths
server {
    listen 80;
    server_name old.com www.old.com;
    return 301 https://new.com$request_uri;
}

# preserve path and query string
location /docs/ {
    return 301 https://docs.new.com$request_uri;
}

# with path rewrite using capture groups
location /old-blog/ {
    rewrite ^/old-blog/(.*)$ https://blog.new.com/$1 permanent;
}

基于路径的重定向

路径重定向适用于 URL 清理(移除末尾斜杠、文件扩展名)和内容迁移。带捕获($1)的正则 location 可实现灵活的路径重写。基于查询参数的重定向使用 $arg_名称 读取单个查询参数。

nginx
# redirect specific paths
location /promo/2023 {
    return 301 /promo/2024;
}

# remove trailing slash
location ~ ^/(.*)/$ {
    return 301 /$1;
}

# remove .html extension
location ~ ^/(.*)\.html$ {
    return 301 /$1;
}

# redirect based on query parameter
location /download {
    if ($arg_version = "1") {
        return 301 /download?v=2;
    }
}
12

FastCGI(PHP-FPM)

PHP-FPM 基本设置

fastcgi_pass 通过 UNIX socket(更快,仅本地)或 TCP 端口将 PHP 请求发送给 PHP-FPM。include fastcgi_params 设置标准 CGI 变量。SCRIPT_FILENAME 告知 PHP 执行哪个文件——结合文档根目录和脚本路径。

nginx
server {
    listen 80;
    server_name example.com;
    root /var/www/laravel/public;
    index index.php;

    location ~ \.php$ {
        fastcgi_pass unix:/run/php/php8.2-fpm.sock;
        # or: fastcgi_pass 127.0.0.1:9000;
        fastcgi_index index.php;
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
    }
}

FastCGI 参数

fastcgi_params 设置传递给 PHP 的环境变量。SCRIPT_FILENAME 最关键——没有它 PHP 找不到脚本。自定义参数(APP_ENV、DB_HOST)可在不修改应用的情况下注入配置。PATH_INFO 在某些框架中启用简洁 URL。

nginx
location ~ \.php$ {
    fastcgi_pass unix:/run/php/php-fpm.sock;

    # standard CGI parameters
    include fastcgi_params;
    fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
    fastcgi_param PATH_INFO $fastcgi_path_info;

    # custom application parameters
    fastcgi_param APP_ENV production;
    fastcgi_param DB_HOST db.internal;

    # flag for HTTPS (so PHP knows the original scheme)
    fastcgi_param HTTPS $https_flag;
}

FastCGI 缓存

fastcgi_cache 存储 PHP 响应,大幅降低可缓存页面的负载。fastcgi_cache_use_stale 在后端出错或超时时提供过期缓存响应——提高韧性。注意不要缓存用户特定或认证内容。

nginx
http {
    fastcgi_cache_path /var/cache/nginx/php
        levels=1:2
        keys_zone=phpcache:100m
        max_size=1g
        inactive=60m;

    server {
        location ~ \.php$ {
            fastcgi_pass unix:/run/php/php-fpm.sock;
            include fastcgi_params;
            fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;

            fastcgi_cache phpcache;
            fastcgi_cache_valid 200 10m;
            fastcgi_cache_key $scheme$host$request_uri;
            fastcgi_cache_use_stale error timeout invalid_header;
        }
    }
}

FastCGI 缓冲区

缓冲区在发送给客户端前持有 PHP 响应。小缓冲区会导致 nginx 对大响应写入磁盘(慢)。如果看到 'upstream sent too big header' 错误,请增大 fastcgi_buffer_size。对于大型 API 响应,使用更大/更少的缓冲区。

nginx
location ~ \.php$ {
    fastcgi_pass unix:/run/php/php-fpm.sock;

    # buffer settings
    fastcgi_buffering on;
    fastcgi_buffer_size 16k;
    fastcgi_buffers 16 16k;
    fastcgi_busy_buffers_size 32k;

    # for large responses (e.g., PHP generating big JSON)
    fastcgi_buffers 64 16k;
    fastcgi_buffer_size 32k;

    include fastcgi_params;
    fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
}

FastCGI 超时

fastcgi_read_timeout 是 nginx 等待 PHP 产生输出的时间。长时间运行的脚本(数据导入、报表生成、AI 调用)需要更高的值。确保 PHP 的 max_execution_time 至少一样高,否则 PHP 会先超时。

nginx
location ~ \.php$ {
    fastcgi_pass unix:/run/php/php-fpm.sock;

    fastcgi_connect_timeout 5s;
    fastcgi_read_timeout 300s;    # for long-running scripts
    fastcgi_send_timeout 300s;

    # increase for very slow PHP scripts (imports, exports, reports)
    fastcgi_read_timeout 600s;
}

# also adjust max_execution_time in php.ini to match
# otherwise PHP kills the script before nginx times out

PHP 的 try_files

try_files $uri $uri/ /index.php?$args 是 PHP 框架(WordPress、Laravel、Drupal)的标准模式。它提供真实文件/目录,否则路由到 index.php 并带查询字符串。$args 保留原始查询参数。

nginx
# WordPress-style pretty URLs
server {
    root /var/www/wordpress;

    location / {
        try_files $uri $uri/ /index.php?$args;
    }

    location ~ \.php$ {
        fastcgi_pass unix:/run/php/php-fpm.sock;
        fastcgi_index index.php;
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
    }

    # deny access to sensitive files
    location ~* /wp-config.php {
        deny all;
    }
}
13

WebSocket 代理

WebSocket 代理

WebSocket 需要 HTTP/1.1 加上 Upgrade 和 Connection 头。map 块将客户端的 Upgrade 头转换为正确的 Connection 值:WebSocket 用 'upgrade',普通 HTTP 用 'close'。较长的 read_timeout 保持空闲 WebSocket 连接存活。

nginx
# Map the Upgrade header to a Connection value
map $http_upgrade $connection_upgrade {
    default upgrade;
    ''      close;
}

server {
    listen 80;
    server_name ws.example.com;

    location / {
        proxy_pass http://backend;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection $connection_upgrade;

        proxy_read_timeout 3600s;  # 1 hour
        proxy_send_timeout 3600s;
    }
}

升级头

WebSocket 强制要求 HTTP/1.1(HTTP/2 以不同方式处理多路复用)。Connection 可以硬编码为 'upgrade',但 map 变量方式也可处理同一 location 上的非 WebSocket 请求。传递 Host 以便后端验证 Origin 头。

nginx
location /ws {
    proxy_pass http://backend;

    # HTTP/1.1 is required for the Upgrade mechanism
    proxy_http_version 1.1;

    # pass the Upgrade header through
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";

    # preserve host for origin checks on the backend
    proxy_set_header Host $host;

    # real client IP
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}

WebSocket 超时

默认 proxy_read_timeout(60s)会关闭空闲 WebSocket 连接。将其设高(86400s = 24 小时)以保持消息间的连接存活。proxy_buffering off 确保消息立即到达客户端而非批量发送。

nginx
location /ws {
    proxy_pass http://backend;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection $connection_upgrade;

    # WebSocket connections are long-lived
    # default 60s would close them during idle periods
    proxy_read_timeout 86400s;  # 24 hours
    proxy_send_timeout 86400s;

    # disable buffering for real-time messages
    proxy_buffering off;
}

Socket.io 代理

Socket.io 从 HTTP 长轮询开始,然后如果可用则升级到 WebSocket。两种传输都通过同一 proxy_pass,因此一个 location 处理一切。传递 X-Forwarded-Proto 让 socket.io 知道它在 HTTPS 后面,从而构造正确的 URL。

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

    location / {
        proxy_pass http://backend;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection $connection_upgrade;
        proxy_set_header Host $host;

        proxy_read_timeout 86400s;

        # socket.io needs these for correct client IP and scheme
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

# socket.io uses both polling (HTTP) and WebSocket transports
# both are handled by the proxy_pass above

多个 WebSocket 路径

不同的 WebSocket 服务可按路径路由到不同后端。每个 location 需要相同的升级头。map 块在 http 级别定义一次并共享。按服务调优 read_timeout——聊天可能频繁活跃;通知可能空闲数小时。

nginx
map $http_upgrade $connection_upgrade {
    default upgrade;
    ''      close;
}

server {
    listen 80;

    # chat application
    location /ws/chat/ {
        proxy_pass http://chat_backend;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection $connection_upgrade;
        proxy_read_timeout 3600s;
    }

    # notifications service
    location /ws/notify/ {
        proxy_pass http://notify_backend;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection $connection_upgrade;
        proxy_read_timeout 86400s;
    }
}

WSS(安全 WebSocket)

WSS 是基于 TLS 的 WebSocket。nginx 终止 TLS(处理证书)并代理纯 WebSocket 到后端。后端不需要 TLS 配置。这是标准的生产设置——浏览器对 HTTPS 页面要求 WSS(混合内容规则)。

nginx
server {
    listen 443 ssl;
    server_name ws.example.com;

    ssl_certificate     /etc/ssl/ws.example.com.crt;
    ssl_certificate_key /etc/ssl/ws.example.com.key;

    location / {
        proxy_pass http://backend:3000;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection $connection_upgrade;
        proxy_set_header Host $host;
        proxy_read_timeout 86400s;
    }
}

# browser connects with wss://ws.example.com
# nginx terminates TLS, proxies plain WebSocket to the backend
14

安全头

X-Frame-Options

X-Frame-Options 防止你的站点被嵌入 iframe,阻止点击劫持攻击。SAMEORIGIN 允许自己的页面嵌套自身。现代替代方案是更灵活的 CSP frame-ancestors。always 确保错误响应也发送此头。

nginx
# prevent clickjacking (your site embedded in an iframe)
add_header X-Frame-Options "SAMEORIGIN" always;

# options:
# DENY          - no framing at all
# SAMEORIGIN    - only same-site can frame
# ALLOW-FROM    - deprecated, use CSP frame-ancestors instead

# modern equivalent via Content-Security-Policy
add_header Content-Security-Policy "frame-ancestors 'self'" always;

X-Content-Type-Options

nosniff 告知浏览器信任 Content-Type 头,不嗅探内容。没有它,浏览器可能在嗅探到 HTML 内容时将上传的图片当作 HTML 执行。这是一个低投入高回报的头——始终启用。

nginx
# prevent MIME-type sniffing
add_header X-Content-Type-Options "nosniff" always;

# forces the browser to respect the declared Content-Type
# prevents XSS via uploaded files (e.g., an image containing HTML
# being executed as a script)

# related: block Adobe Flash / PDF cross-domain policies
add_header X-Permitted-Cross-Domain-Policies "none" always;

内容安全策略(CSP)

CSP 是抵御 XSS 最强大的防线——它白名单列出脚本、样式、图片等的允许来源。先用 Report-Only 模式在不破坏任何东西的情况下发现违规,再启用强制。'self' 表示同源;'unsafe-inline' 允许内联脚本/样式(许多应用需要)。

nginx
# strict CSP — restricts where resources can load from
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' https://fonts.gstatic.com; connect-src 'self' https://api.example.com; frame-ancestors 'none'; base-uri 'none'; form-action 'self'" always;

# report-only mode (logs violations without blocking)
add_header Content-Security-Policy-Report-Only "default-src 'self'; report-uri /csp-report" always;

X-XSS-Protection

X-XSS-Protection 启用浏览器内置的反射型 XSS 过滤器。现代浏览器已移除此功能,转而使用 CSP。mode=block 阻止了净化(可能引入新漏洞)而直接阻止页面。对旧浏览器无害,但 CSP 才是真正的防御。

nginx
# legacy XSS auditor (older browsers)
add_header X-XSS-Protection "1; mode=block" always;

# values:
# 0              - disable the filter
# 1              - enable (sanitize)
# 1; mode=block  - enable and block the page entirely

# NOTE: deprecated in modern browsers (Chrome, Edge, Safari removed it)
# CSP is the modern replacement, but this doesn't hurt to include
# for legacy browser support

Referrer-Policy

Referrer-Policy 控制用户点击链接时 Referer 头泄露多少 URL 信息。strict-origin-when-cross-origin(浏览器默认)对同源请求发送完整 URL,对跨源只发送源,在降级到 HTTP 时不发送。

nginx
# control what the Referer header contains
add_header Referrer-Policy "strict-origin-when-cross-origin" always;

# options:
# no-referrer                  - never send Referer
# no-referrer-when-downgrade   - default; no Referer on HTTPS->HTTP
# same-origin                  - only same-site
# origin                       - send only scheme+host
# strict-origin                - origin, but not on downgrade
# origin-when-cross-origin     - full for same, origin for cross
# strict-origin-when-cross-origin - recommended default
# unsafe-url                   - send full URL (not recommended)

Permissions-Policy

Permissions-Policy 让你禁用浏览器功能(摄像头、麦克风、地理位置等),即使页面请求它们。空的 () 完全禁用该功能。这限制了 XSS 的影响范围——如果策略禁止,被入侵的脚本无法开启摄像头。

nginx
# control which browser features the page can use
# (formerly Feature-Policy)
add_header Permissions-Policy "geolocation=(), microphone=(), camera=(), payment=(), usb=(), magnetometer=(), gyroscope=()" always;

# allow only for same origin
add_header Permissions-Policy "geolocation=(self), microphone=(self)" always;

# allow for specific origins
add_header Permissions-Policy "camera=(self https://trusted.com)" always;
15

访问控制

按 IP 允许/拒绝

allow/deny 规则按顺序评估;第一个匹配生效。最后的 deny all 阻止未明确允许的一切。适用于将管理面板锁定到公司 IP。规则可使用 CIDR 范围或单个 IP,并支持 IPv4 和 IPv6。

nginx
location /admin {
    allow 192.168.1.0/24;     # internal network
    allow 10.0.0.0/8;         # VPN range
    allow 203.0.113.5;        # specific IP
    deny all;

    proxy_pass http://backend;
}

# rules are checked in order — first match wins
# if no rule matches, access is allowed by default

拒绝所有

deny all 阻止所有人;配合 allow 形成白名单。反过来——拒绝特定 IP,允许所有——是黑名单。白名单更安全(默认拒绝)。没有显式 allow/deny 时默认允许,因此受限区域务必以 deny all 结尾。

nginx
# deny all access to a location
location /private {
    deny all;
    return 403;
}

# deny specific IPs, allow everyone else
location / {
    deny 192.168.1.100;        # block one IP
    deny 203.0.113.0/24;       # block a range
    allow all;
}

# deny a range, allow the rest
location /api {
    deny 10.0.0.0/8;
    allow all;
    proxy_pass http://backend;
}

允许子网

私有 IP 范围(10.0.0.0/8、172.16.0.0/12、192.168.0.0/16)在互联网上不可路由,因此允许它们将访问限制在内部网络。IPv6 中 ::1 是 localhost。配合 VPN 可安全地远程访问内部工具。

nginx
# allow only local/private networks
location /internal {
    allow 192.168.0.0/16;     # private class B
    allow 172.16.0.0/12;      # private class B range
    allow 10.0.0.0/8;         # private class A
    deny all;

    root /var/www/internal;
}

# IPv6
location / {
    allow 2001:db8::/32;      # example IPv6 range
    allow ::1;                # IPv6 localhost
    deny all;
}

Geo 模块

geo 使用基数树优化 IP 查找——比冗长的 allow/deny 列表快得多。它根据客户端 IP 设置变量(此处为 $allowed_ip)。'ranges' 形式支持起始-结束 IP 范围。在 if() 或 map 中使用该变量实现灵活的访问控制。

nginx
# map IPs to values (binary search, very fast)
geo $allowed_ip {
    default        0;
    192.168.0.0/24 1;
    10.0.0.0/8     1;
    203.0.113.5    1;
}

server {
    location /admin {
        if ($allowed_ip = 0) {
            return 403;
        }
        proxy_pass http://backend;
    }
}

# geo with IP ranges (uses 'ranges' directive)
geo $country_code {
    ranges;
    default                 ZZ;
    10.0.0.0-10.255.255.255 US;
}

按 IP 限流(limit_req)

limit_req_zone 使用 $binary_remote_addr(紧凑形式)定义每 IP 速率。limit_req 应用它。burst 允许短暂突发(排队最多 20 个)。nodelay 立即提供突发而非节流——适合 API。没有 nodelay 时,超额请求被延迟而非拒绝。

nginx
http {
    # define rate limit zone (10 requests/sec per IP)
    limit_req_zone $binary_remote_addr zone=mylimit:10m rate=10r/s;

    server {
        location /api/ {
            limit_req zone=mylimit burst=20 nodelay;
            proxy_pass http://backend;
        }
    }
}

# $binary_remote_addr uses binary form (more memory-efficient)
# 10m zone stores ~160,000 unique IP addresses
# rate=10r/s: 10 requests per second per IP
# burst=20: allow 20 requests to queue
# nodelay: serve the burst immediately (no artificial delay)

国家封锁(GeoIP)

GeoIP 模块使用 MaxMind 数据库将客户端 IP 映射到国家代码。配合 map 可以封锁或允许整个国家。注意:MaxMind 的免费 GeoLite2 需要第三方 maxminddb 模块(旧版 GeoIP 模块使用旧的 .dat 格式)。准确度不是 100%——仅用于粗粒度过滤。

nginx
# requires the GeoIP module and a MaxMind database
geoip_country /usr/share/GeoIP/GeoIP.dat;

map $geoip_country_code $allowed_country {
    default yes;
    CN no;
    RU no;
    KP no;
}

server {
    location / {
        if ($allowed_country = no) {
            return 403;
        }
        proxy_pass http://backend;
    }
}

# GeoIP2 (maxminddb module) for the newer GeoLite2 format
# maxminddb /etc/nginx/geoip2/GeoLite2-Country.mmdb;
# $geoip2_country_code source=$remote_addr country iso_code;
16

认证

基本认证设置

auth_basic 触发浏览器原生的用户名/密码对话框。realm 字符串('Admin Area')会展示给用户。auth_basic_user_file 指向 htpasswd 文件。使用 auth_basic off 可在应公开的子 location 中禁用认证。

nginx
location /admin {
    auth_basic "Admin Area";
    auth_basic_user_file /etc/nginx/.htpasswd;

    proxy_pass http://backend;
}

location /public {
    # disable auth inherited from parent location
    auth_basic off;
}

# create users with htpasswd (apache2-utils / httpd-tools):
# htpasswd -c /etc/nginx/.htpasswd user1   (create file)
# htpasswd /etc/nginx/.htpasswd user2      (append, no -c)

htpasswd 文件

htpasswd 文件存储用户名:哈希 对。bcrypt(-B)是最强哈希;MD5(-m,默认)可接受;SHA1(-s)较弱;PLAIN 不安全。限制文件权限以便只有 nginx 能读取——该文件包含不可泄露的密码哈希。

nginx
# /etc/nginx/.htpasswd format:
# user1:$apr1$qHR...$hashedpassword
# user2:{PLAIN}plaintext       (insecure!)
# user3:{SHA}base64sha1hash

# generate with htpasswd:
# -c  create new file (overwrites!)
# -B  bcrypt (most secure, recommended)
# -m  MD5 (default, APR1)
# -s  SHA1

# bcrypt (recommended)
# htpasswd -B /etc/nginx/.htpasswd user1

# protect the file permissions
chown root:nginx /etc/nginx/.htpasswd
chmod 640 /etc/nginx/.htpasswd

特定 Location 的认证

auth_basic 被嵌套 location 继承。要使子路径公开,设置 auth_basic off。当 /admin 的大部分需要认证但 /admin/health(健康检查端点)应可被监控工具无需凭据访问时很有用。

nginx
server {
    # no auth at server level

    location /admin {
        auth_basic "Admin";
        auth_basic_user_file /etc/nginx/.htpasswd;
    }

    location /admin/api {
        # inherits auth from /admin
        proxy_pass http://admin_api;
    }

    location /admin/public {
        # explicitly disable inherited auth
        auth_basic off;
        proxy_pass http://public_api;
    }
}

认证配合 allow/deny

satisfy any 表示通过 IP 检查或认证任一即可授予访问——对内部用户便捷(无需密码)同时仍要求外部用户提供密码。satisfy all 要求两者都通过——最严格的选项,适用于高度敏感的区域。

nginx
# satisfy: ALL (default) or ANY
location / {
    # allow corporate IPs without password
    # require password from outside the corporate network
    satisfy any;

    allow 192.168.0.0/16;
    deny all;

    auth_basic "Restricted";
    auth_basic_user_file /etc/nginx/.htpasswd;

    proxy_pass http://backend;
}

# satisfy all: must pass BOTH IP check AND auth
# satisfy any: pass EITHER IP check OR auth

auth_request 模块

auth_request 在主请求之前向认证服务发送内部子请求。该服务验证令牌(JWT、会话 cookie 等)并返回 2xx(允许)或 401/403(拒绝)。internal 阻止外部访问 /auth。这将认证与应用解耦。

nginx
# delegate authentication to a subrequest (OAuth, JWT, etc.)
location / {
    auth_request /auth;
    proxy_pass http://backend;
}

location = /auth {
    internal;                    # can't be called directly
    proxy_pass http://auth-service/validate;
    proxy_pass_request_body off;
    proxy_set_header Content-Length "";
    proxy_set_header X-Original-URI $request_uri;
}

# auth-service returns:
# 2xx -> allow (continue to backend)
# 401/403 -> deny (return to client)
# other -> error

JWT 认证

JWT 验证可使用 njs(nginx JavaScript 模块)在进程内完成以获得低延迟,或通过 auth_request 委托给外部服务。njs 在 nginx 内运行 JavaScript 子集——快速但有限。auth_request 方式更灵活且语言无关。

nginx
# requires the njs (nginx JavaScript) module
js_import auth.js;

location /api/ {
    js_content auth.validateJwt;
    proxy_pass http://backend;
}

# auth.js defines the validateJwt function:
# function validateJwt(r) {
#     var token = r.headersIn.Authorization;
#     if (!token || !verifyJwt(token)) {
#         r.return(401);
#         return;
#     }
# }

# alternative: use auth_request with an external JWT validator service
# more portable than njs, but adds a network hop per request
17

日志

访问日志

access_log 记录每个请求。log_format 定义捕获哪些字段。默认的 'combined' 格式与 Apache 兼容。对于高流量站点,buffer+flush 通过批量写入减少磁盘 I/O。syslog 将日志发送到中央服务器进行聚合。

nginx
http {
    log_format main '$remote_addr - $remote_user [$time_local] '
                    '"$request" $status $body_bytes_sent '
                    '"$http_referer" "$http_user_agent"';

    access_log /var/log/nginx/access.log main;
    # or use the built-in 'combined' format
    access_log /var/log/nginx/access.log combined;
}

# disable access log
access_log off;

# log to syslog
access_log syslog:server=localhost,facility=local7 main;

# buffer writes (reduce I/O)
access_log /var/log/nginx/access.log main buffer=32k flush=5s;

错误日志

error_log 捕获 nginx 错误和上游问题。级别过滤严重性——warn 是良好的默认值。debug 非常详细(仅用于排障)且需要 debug 构建。memory: 在内存中缓冲日志——用于诊断磁盘日志无法捕获的启动问题。

nginx
# error log location and severity level
error_log /var/log/nginx/error.log warn;

# levels (low to high severity):
# debug, info, notice, warn, error, crit, alert, emerg

# debug requires nginx built with --with-debug
error_log /var/log/nginx/error.log debug;

# log to memory (for debugging, lost on restart)
error_log memory:32m debug;

# disable (NOT recommended — hides real errors)
error_log /dev/null crit;

日志格式

自定义日志格式捕获对你重要的内容。request_time 是总处理时间;upstream_response_time 隔离后端。escape=json 为日志聚合器(ELK、Loki、Datadog)生成有效 JSON。没有 escape=json,URI 中的特殊字符可能破坏 JSON 解析。

nginx
http {
    # combined (Apache-compatible, the default)
    log_format combined '$remote_addr - $remote_user [$time_local] '
                        '"$request" $status $body_bytes_sent '
                        '"$http_referer" "$http_user_agent"';

    # main 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" '
                    'urt="$upstream_response_time"';

    # JSON format for log aggregation (ELK, Loki, etc.)
    log_format json escape=json
        '{"time":"$time_iso8601",'
        '"remote":"$remote_addr",'
        '"method":"$request_method",'
        '"uri":"$request_uri",'
        '"status":$status,'
        '"bytes":$body_bytes_sent,'
        '"ua":"$http_user_agent"}';
}

条件日志

if= 参数基于变量有条件地记录日志。map $status 可让你只记录错误(4xx/5xx),减少高流量站点的噪声和磁盘使用。变量必须为 0/空才跳过记录,非零才记录。

nginx
http {
    # don't log successful 2xx/3xx, only errors
    map $status $loggable {
        ~^[23]  0;
        default 1;
    }

    server {
        access_log /var/log/nginx/access.log main if=$loggable;
    }
}

# skip logging for static assets
map $request_uri $log_skip {
    default                    1;
    ~*\.(jpg|png|gif|css|js)$  0;
}
access_log /var/log/nginx/access.log main if=$log_skip;

日志轮转

logrotate 重命名并压缩旧日志。postrotate 向 nginx 发送 USR1,使其重新打开日志文件——没有这个,nginx 会继续写入已重命名的文件。sharedscripts 只运行一次 postrotate(而非每个文件)。delaycompress 在第二次轮转时压缩,因此最近的日志是未压缩的。

nginx
# logrotate config: /etc/logrotate.d/nginx
/var/log/nginx/*.log {
    daily
    missingok
    rotate 14
    compress
    delaycompress
    notifempty
    create 0640 www-data adm
    sharedscripts
    postrotate
        if [ -f /var/run/nginx.pid ]; then
            kill -USR1 $(cat /var/run/nginx.pid)
        fi
    endscript
}

# USR1 signal tells nginx to reopen log files
# without dropping connections
# nginx -s reopen does the same thing

禁用日志

为健康检查和静态资源禁用 access_log 可显著减少日志噪声和磁盘 I/O。log_not_found off 抑制 404 日志条目(对 favicon.ico 有用)。始终保持 error_log 至少在 'error' 级别启用——静默故障很难诊断。

nginx
# disable access log entirely
access_log off;

# disable for a specific location
location /health {
    access_log off;
    return 200 "ok\n";
}

# disable for static assets
location ~* \.(jpg|png|gif|ico|css|js)$ {
    access_log off;
    log_not_found off;
}

# only log errors (reduce error log verbosity)
error_log /var/log/nginx/error.log error;
18

限流

limit_req_zone

limit_req_zone 定义以变量(通常是客户端 IP)为键的速率限制。区域大小(10m)决定可跟踪多少唯一键。rate 是允许的请求频率。在 http 上下文中定义区域,在 location 上下文中用 limit_req 应用。

nginx
http {
    # 10 requests per second per IP
    limit_req_zone $binary_remote_addr zone=api:10m rate=10r/s;

    # 1 request per second per IP (for login)
    limit_req_zone $binary_remote_addr zone=login:10m rate=1r/s;

    # 100 requests per minute per IP
    limit_req_zone $binary_remote_addr zone=general:10m rate=100r/m;
}

# $binary_remote_addr uses binary form (more memory-efficient than text)
# 10m zone stores ~160,000 unique IP addresses
# rate can be per second (r/s) or per minute (r/m)

limit_req

limit_req 将区域应用到 location。burst 允许短暂突发(排队最多 N 个超额请求)。nodelay 以全速提供突发而非间隔分发——适合对延迟敏感的 API。没有 burst 时,每个超过速率的请求都会立即被拒绝。

nginx
http {
    limit_req_zone $binary_remote_addr zone=api:10m rate=10r/s;

    server {
        location /api/ {
            limit_req zone=api burst=20 nodelay;
            proxy_pass http://backend;
        }

        location /login {
            limit_req zone=api burst=5 nodelay;
            proxy_pass http://backend;
        }
    }
}

# burst: max number of requests allowed to queue
# nodelay: serve the burst immediately (no artificial throttling)
# without burst: requests exceeding the rate get 503 immediately

limit_conn

limit_conn 限制并发连接数,而非请求速率。适用于防止单个 IP 开启数百个同时下载。与 limit_req(速率)结合可同时提供突发和并发保护。limit_conn_zone 使用相同的基于键的区域模式。

nginx
http {
    # limit concurrent connections per IP
    limit_conn_zone $binary_remote_addr zone=conn_per_ip:10m;

    # limit total connections per server name
    limit_conn_zone $server_name zone=conn_per_server:10m;

    server {
        limit_conn conn_per_ip 10;
        limit_conn conn_per_server 1000;

        location /download {
            limit_conn conn_per_ip 1;  # one download at a time per IP
            proxy_pass http://backend;
        }
    }
}

突发队列

burst 是超额请求的队列大小。nodelay 立即提供整个突发(适合 API)。delay=N 立即提供 N 个,其余按配置速率节流(更平滑)。没有 nodelay 时,所有超额请求都被延迟以匹配速率,均匀分散负载。

nginx
http {
    limit_req_zone $binary_remote_addr zone=api:10m rate=10r/s;

    server {
        location /api/ {
            # burst=20: allow 20 requests to queue
            # nodelay: serve them immediately (no artificial delay)
            limit_req zone=api burst=20 nodelay;

            # delay: first 10 immediate, next 10 delayed, rest rejected
            limit_req zone=api burst=20 delay=10;

            proxy_pass http://backend;
        }
    }
}

# without burst: requests over rate get 503 immediately
# with burst: excess requests queue (default delayed)
# nodelay: queue served at full speed (allows spikes)
# delay=N: first N immediate, rest throttled to the rate

按 IP 限流

为不同端点类型定义具有适当速率的独立区域。API 端点容忍较高速率;认证端点(登录、密码重置)需要严格限制以防止暴力破解;上传需要极低速率以防止滥用。burst 为合法突发提供小幅余量。

nginx
http {
    # different limits for different endpoints
    limit_req_zone $binary_remote_addr zone=api:10m rate=10r/s;
    limit_req_zone $binary_remote_addr zone=auth:10m rate=1r/s;
    limit_req_zone $binary_remote_addr zone=upload:10m rate=2r/m;

    server {
        location /api/ {
            limit_req zone=api burst=20 nodelay;
        }

        location /login {
            limit_req zone=auth burst=5 nodelay;
        }

        location /upload {
            limit_req zone=upload burst=3 nodelay;
            client_max_body_size 100M;
        }
    }
}

自定义限流响应

默认情况下,被限流的请求获得 503(服务不可用)。429(请求过多)语义更正确,也是大多数 API 期望的。使用 error_page 返回解释限制的 JSON body。包含 Retry-After 头以便客户端知道何时重试。

nginx
http {
    limit_req_zone $binary_remote_addr zone=api:10m rate=10r/s;
    limit_req_status 429;  # default is 503

    server {
        location /api/ {
            limit_req zone=api burst=20 nodelay;

            # custom error response
            error_page 429 = @rate_limited;
        }

        location @rate_limited {
            default_type application/json;
            return 429 '{"error":"rate_limit_exceeded","retry_after":60}';
        }
    }
}

# also available: limit_conn_status 429;
19

性能调优

worker_processes

worker_processes 应匹配 CPU 核心数——auto 自动完成此操作。每个 worker 是单线程进程,通过事件循环处理许多连接。worker_cpu_affinity 将 worker 固定到特定核心,改善缓存局部性。auto affinity 让操作系统调度器决定。

nginx
# main context
worker_processes auto;  # one per CPU core (recommended)

# or set an explicit count
worker_processes 4;

# each worker handles connections independently
# 'auto' uses the number of available CPU cores

# CPU affinity (pin workers to specific cores)
worker_processes auto;
worker_cpu_affinity auto;

# or manual bitmask (one mask per worker)
worker_processes 4;
worker_cpu_affinity 0001 0010 0100 1000;

worker_connections

worker_connections 是每个 worker 的连接数;总容量是 worker_processes * worker_connections。这还受操作系统文件描述符限制(worker_rlimit_nofile)的约束。必须同时提高 nginx 限制和操作系统限制(limits.conf 或 systemd 的 LimitNOFILE)。multi_accept 一次获取所有待处理连接。

nginx
events {
    # connections per worker process
    worker_connections 4096;

    # accept multiple connections at once under high load
    multi_accept on;
}

# total max connections = worker_processes * worker_connections
# but also bounded by worker_rlimit_nofile (file descriptor limit)

# main context — raise the FD limit
worker_rlimit_nofile 65535;

# also raise system limits (outside nginx):
# /etc/security/limits.conf:
#   * soft nofile 65535
#   * hard nofile 65535

keepalive_timeout

客户端 keepalive 复用连接处理多个请求,降低延迟。keepalive_timeout 是空闲连接保持打开的时间。上游 keepalive(upstream 中的 keepalive 指令)缓存到后端的连接——将 Connection 设为空以便 nginx 不发送 'close'。

nginx
http {
    # keep-alive timeout (client side)
    keepalive_timeout 65s;
    keepalive_requests 1000;  # max requests per connection

    # upstream keepalive (reuse connections to backends)
    upstream backend {
        server 10.0.0.1:8080;
        keepalive 32;  # cache of 32 idle upstream connections
    }

    location / {
        proxy_pass http://backend;
        proxy_http_version 1.1;
        proxy_set_header Connection "";  # clear to enable keepalive
    }
}

sendfile 与 tcp_nopush

sendfile 使用内核级零拷贝路径发送文件,绕过用户空间缓冲区——对静态文件快得多。tcp_nopush 与 sendfile 配合,将头和文件起始部分在一个包中发送。tcp_nodelay 禁用 Nagle 以实现低延迟。aio threads 将磁盘 I/O 从事件循环中卸载。

nginx
http {
    # use kernel sendfile() for static files (zero-copy)
    sendfile on;

    # send headers + start of file in one TCP packet
    tcp_nopush on;

    # disable Nagle's algorithm (send small packets immediately)
    tcp_nodelay on;

    # for static file serving
    location /static/ {
        sendfile on;
        tcp_nopush on;
        aio threads;  # asynchronous I/O via thread pool
        root /var/www;
    }
}

# sendfile: zero-copy file transfer (kernel space, no user-space copy)
# tcp_nopush: coalesce headers + file data into one packet
# tcp_nodelay: don't wait to batch small packets (low latency)

缓冲区大小

根据流量调优缓冲区。client_max_body_size 限制上传大小(默认 1M——对文件上传太小)。proxy/fastcgi 缓冲区持有上游响应;太小的缓冲区会溢出到磁盘。large_client_header_buffers 处理长 URL/cookie。监控 'buffer overflow' 错误。

nginx
http {
    # client body buffer (in-memory before writing to disk)
    client_body_buffer_size 16k;
    client_max_body_size 50m;

    # client header buffers
    client_header_buffer_size 1k;
    large_client_header_buffers 4 8k;

    # proxy buffers (responses from upstream)
    proxy_buffer_size 4k;
    proxy_buffers 8 4k;
    proxy_busy_buffers_size 16k;

    # fastcgi buffers (responses from PHP-FPM, etc.)
    fastcgi_buffer_size 4k;
    fastcgi_buffers 8 4k;
}

# if a request body exceeds client_body_buffer_size,
# nginx writes it to a temp file (slower)

打开文件缓存

open_file_cache 缓存文件描述符和元数据,避免对热门文件重复 open()/stat() 系统调用。这显著加速静态文件提供。valid 控制 nginx 重新检查文件的频率(捕获更新)。min_uses 避免缓存一次性请求。也可缓存错误以避免重复失败打开。

nginx
http {
    open_file_cache max=10000 inactive=20s;
    open_file_cache_valid 30s;
    open_file_cache_min_uses 2;
    open_file_cache_errors on;

    server {
        root /var/www/html;

        location / {
            open_file_cache max=10000 inactive=20s;
            open_file_cache_valid 30s;
            try_files $uri $uri/ =404;
        }
    }
}

# caches file descriptors, sizes, modification times
# reduces stat() and open() syscalls for popular files
# max=10000: cache up to 10000 files
# inactive=20s: remove if not accessed in 20 seconds
# min_uses=2: only cache files accessed 2+ times
# valid=30s: re-validate cache every 30s
20

Docker 部署

Docker 中的 Nginx

官方 nginx 镜像默认提供 /usr/share/nginx/html 并从 /etc/nginx 读取配置。将配置和内容挂载为卷。:ro(只读)防止容器修改宿主文件。nginx -s reload 无需重建容器即可拾取配置变更。

nginx
# run the official nginx image
docker run -d --name nginx \
    -p 80:80 -p 443:443 \
    -v /etc/nginx/nginx.conf:/etc/nginx/nginx.conf:ro \
    -v /var/www/html:/usr/share/nginx/html:ro \
    -v /etc/nginx/conf.d:/etc/nginx/conf.d:ro \
    nginx:latest

# interactive shell into the running container
docker exec -it nginx sh

# reload config without restarting the container
docker exec nginx nginx -s reload

# view logs
docker logs nginx
docker logs -f nginx

Docker Compose

Docker Compose 编排 nginx 及其后端。expose 使端口可供关联服务使用(不对宿主开放)。depends_on 确保后端先启动。restart: unless-stopped 在重启后存活但尊重手动停止。日志需以写入权限挂载(无 :ro)以便 nginx 能写入。

nginx
version: '3.8'
services:
  nginx:
    image: nginx:latest
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf:ro
      - ./conf.d:/etc/nginx/conf.d:ro
      - ./html:/usr/share/nginx/html:ro
      - ./certs:/etc/nginx/certs:ro
      - ./logs:/var/log/nginx
    depends_on:
      - backend
    restart: unless-stopped

  backend:
    image: myapp:latest
    expose:
      - "3000"

自定义 Nginx 镜像

将配置烘焙到镜像中使部署不可变且可重现——无需运行时卷挂载。alpine 变体保持镜像小巧(基础约 7MB)。这非常适合 Kubernetes 或 CI/CD 中需要自包含制品的场景。更新配置需重新构建。

nginx
# Dockerfile
FROM nginx:alpine

# copy custom config
COPY nginx.conf /etc/nginx/nginx.conf
COPY conf.d/ /etc/nginx/conf.d/

# copy static files
COPY html/ /usr/share/nginx/html/

# copy SSL certificates
COPY certs/ /etc/nginx/certs/

# build and run
# docker build -t mynginx .
# docker run -d -p 80:80 mynginx

# alpine is ~7MB; the image stays small
# bake config in for immutable deployments

卷挂载

卷挂载让你无需重建镜像即可迭代配置——非常适合开发。挂载目录很稳健;挂载单个文件可能在宿主文件不存在时出问题(Docker 会创建目录)。命名卷在容器重建间持久存在并由 Docker 管理。

nginx
# mount config and content as volumes (dev workflow)
docker run -d --name nginx \
    -p 80:80 \
    -v $(pwd)/nginx.conf:/etc/nginx/nginx.conf:ro \
    -v $(pwd)/conf.d:/etc/nginx/conf.d:ro \
    -v $(pwd)/html:/usr/share/nginx/html:ro \
    -v $(pwd)/logs:/var/log/nginx \
    nginx:alpine

# read-only mounts (:ro) for security
# logs need write access (no :ro)
# use named volumes for persistent data:
#   -v nginx_logs:/var/log/nginx

# IMPORTANT: mounting a single file (nginx.conf) requires
# the file to exist on the host, or Docker creates a directory

Docker 中的反向代理

在 Docker Compose 中,服务按服务名(app1、app2)互相解析。nginx 的 upstream 可直接引用这些名称——无需 IP 地址。这使 nginx 成为容器化应用的强大负载均衡器。使用默认 Compose 网络,或定义自定义网络以实现隔离。

nginx
# nginx as reverse proxy for other containers
version: '3.8'
services:
  nginx:
    image: nginx:alpine
    ports:
      - "80:80"
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf:ro
    depends_on:
      - app1
      - app2
    restart: unless-stopped

  app1:
    image: myapp:latest

  app2:
    image: myapp:latest

# nginx.conf:
# upstream apps {
#     server app1:3000;
#     server app2:3000;
# }
# server {
#     listen 80;
#     location / { proxy_pass http://apps; }
# }

健康检查

健康检查让 Docker(及 Swarm 等编排器)知道 nginx 是否真正在服务,而不仅仅是运行。wget --spider 检查 HTTP 状态而不下载 body。start_period 延迟首次检查以便 nginx 启动。在 Kubernetes 中,使用 readiness/liveness 探针而非 Docker HEALTHCHECK。

nginx
# Dockerfile with a health check
FROM nginx:alpine

HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
    CMD wget --quiet --tries=1 --spider http://localhost/ || exit 1

COPY nginx.conf /etc/nginx/nginx.conf

# docker-compose health check
version: '3.8'
services:
  nginx:
    image: nginx:alpine
    healthcheck:
      test: ["CMD", "wget", "--quiet", "--tries=1", "--spider", "http://localhost/"]
      interval: 30s
      timeout: 3s
      retries: 3
      start_period: 5s

# start_period gives nginx time to boot before checking
# a failed health check marks the container unhealthy

这篇内容对您有帮助吗?