好的,Nginx 内置变量是 Nginx 配置中极其强大且常用的功能。它们提供了关于当前请求、响应、连接状态等丰富的信息,使你能够实现动态配置、日志定制、访问控制、重写规则、代理设置等复杂逻辑。下面我将详细介绍常用的内置变量并辅以实际案例详解。
核心概念:
ngx_http_core_module
, ngx_http_proxy_module
, ngx_http_log_module
等)在请求处理的不同阶段自动设置。location
, server
, http
块以及 access_log
, rewrite
, proxy_pass
等指令中使用。常用内置变量分类及详解:
$uri
/ $document_uri
?
后面的部分)。Nginx 会对其进行解码、移除多余的斜杠、解析相对路径 .
和 ..
。location /images/ {
# 记录请求的图片路径 (如 /images/cat.jpg)
access_log /var/log/nginx/image_access.log '$uri';
}
$request_uri
# 重写规则:如果请求URI包含 'oldpath',则重定向到新路径,但保留原始查询参数
if ($request_uri ~* "^/oldpath/(.*)") {
return 301 /newpath/$1?$args;
}
$args
/ $query_string
?
后面的部分)。两者通常等价。# 根据查询参数做不同处理
location /search {
if ($args ~ "q=([^&]+)") {
# 提取搜索关键词 (需要小心 if 的使用陷阱,通常建议用 map 或 split_clients)
set $search_term $1;
proxy_pass http://search_backend?query=$search_term;
}
# ... 其他处理
}
$is_args
?
;否则为空字符串。常用于拼接 URL。# 在重写或代理时,优雅地添加或保留查询参数
location /api/ {
rewrite ^/api/(.*)$ /internal_api/$1$is_args$args break;
proxy_pass http://api_backend;
}
$request_method
GET
, POST
, PUT
, DELETE
等)。# 限制特定 location 只允许 GET 和 HEAD 方法
location /static/ {
if ($request_method !~ ^(GET|HEAD)$) {
return 405; # Method Not Allowed
}
# ... 静态文件服务配置
}
$http_HEADER_NAME
_
代替连字符 -
,并加上前缀 $http_
。# 获取 User-Agent
set $user_agent $http_user_agent;
# 获取 X-Forwarded-For (常用于获取真实客户端IP,当Nginx前有代理时)
set $real_client_ip $http_x_forwarded_for;
# 获取 Authorization 头 (用于基础认证或JWT)
set $auth_header $http_authorization;
# 记录特定请求头到日志
log_format custom_log '$remote_addr - [$time_local] "$request" $status "$http_referer" "$http_user_agent"';
$host
Host
请求头的值。server_name
配置块中的第一个名称。# 基于不同域名提供不同内容
server {
listen 80;
server_name site1.com;
root /var/www/site1;
}
server {
listen 80;
server_name site2.com;
root /var/www/site2;
}
# 在通用配置中使用 $host
location / {
add_header X-Served-By $host always;
}
$http_host
Host
请求头的值,包含端口号(如果客户端指定了端口,如 Host: example.com:8080
)。$host
区别: $host
不包含端口,且回退机制更强。通常 $host
更安全通用,除非明确需要端口信息。# 需要精确包含端口信息的情况 (较少见)
if ($http_host != "www.example.com:443") {
# ... 做一些处理
}
$remote_addr
$http_x_forwarded_for
。# 基础访问日志通常包含客户端IP
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;
# 简单的 IP 访问限制 (生产环境建议用 `allow`/`deny` 或 `ngx_http_geo_module`)
if ($remote_addr = "123.45.67.89") {
return 403;
}
$remote_port
log_format detailed ... '$remote_addr:$remote_port ...';
$remote_user
# 记录谁访问了受保护区域
location /admin/ {
auth_basic "Admin Area";
auth_basic_user_file /etc/nginx/.htpasswd;
access_log /var/log/nginx/admin_access.log '$remote_user - $remote_addr [$time_local] "$request"';
# ... 其他配置
}
$status
200
, 404
, 500
, 301
等)。极其常用。# 在访问日志中记录状态码
log_format main ... '$status' ...;
# 只记录错误状态 (4xx, 5xx) 到单独日志
map $status $loggable {
~^[23] 0; # 2xx, 3xx 不记录
default 1; # 4xx, 5xx 记录
}
access_log /var/log/nginx/error_access.log main if=$loggable;
# 根据状态码添加响应头 (例如标识错误来源)
location @backend {
proxy_pass http://backend;
proxy_intercept_errors on;
error_page 404 = @fallback;
add_header X-Backend-Status $status always; # 记录后端实际状态
}
$body_bytes_sent
Content-Length
的值。# 记录传输的数据量 (用于流量分析、监控)
log_format download '$remote_addr - [$time_local] "$request" $status $body_bytes_sent';
access_log /var/log/nginx/download.log download;
$bytes_sent
$sent_http_HEADER_NAME
_
代替连字符 -
,并加上前缀 $sent_http_
。# 记录后端设置的自定义响应头 (如 X-Backend-ID)
log_format backend_log ... '$sent_http_x_backend_id' ...;
# 检查是否设置了某个响应头 (例如 Cache-Control)
if ($sent_http_cache_control ~ "max-age=(\d+)") {
set $cache_max_age $1;
}
$connection
/ $connection_requests
$connection
: 当前连接的序列号(唯一标识一个连接)。在连接复用时,多个请求共享同一个 $connection
。$connection_requests
: 当前连接上已经处理过的 请求数量(对于 keepalive 连接)。# 记录连接信息 (用于分析长连接复用情况)
log_format connection_log '$remote_addr - $connection [$connection_requests] "$request"';
$request_time
# 记录慢请求 (例如超过 1 秒的请求)
log_format slow_log '$remote_addr - [$time_local] "$request" $status $request_time';
access_log /var/log/nginx/slow.log slow_log if=$slow_condition;
map $request_time $slow_condition {
~^\d\.\d{3}$ 0; # 小于1秒
default 1; # 大于等于1秒
}
$request_length
# 限制非常大的请求 (防止DoS)
client_max_body_size 10m; # 限制请求体大小
# 在日志中记录请求长度
log_format ... '$request_length' ...;
$request
GET /index.html?param=value HTTP/1.1
)。# 标准访问日志格式的核心部分
log_format main ... '"$request"' ...;
$time_iso8601
2023-10-27T14:31:15+08:00
)。日志记录推荐格式,便于解析和排序。# 使用标准时间格式的日志
log_format iso_log '$remote_addr - [$time_iso8601] "$request" $status';
access_log /var/log/nginx/access.log iso_log;
$time_local
27/Oct/2023:14:31:15 +0800
)。格式由 log_format
指令的默认值或配置决定。$msec
1698388275.123
)。用于需要高精度时间戳的场景。$proxy_host
proxy_pass
指令中指定的 代理目标服务器的主机名或 IP 地址和端口。如果 proxy_pass
只指定了 URI 部分,此变量可能为空。$proxy_port
proxy_pass
指令中指定的 代理目标服务器的端口。$proxy_add_x_forwarded_for
X-Forwarded-For
请求头以传递给上游服务器。它的值是 现有的 $http_x_forwarded_for
值(如果存在)后面加上逗号和 $remote_addr
。如果不存在 $http_x_forwarded_for
,则直接等于 $remote_addr
。这是设置 proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
的标准做法。location / {
proxy_pass http://backend;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; # 关键!传递真实客户端IP链
}
$upstream_addr
# 在日志中记录请求被转发到了哪个后端服务器
log_format upstream_log ... '$upstream_addr' ...;
access_log /var/log/nginx/upstream.log upstream_log;
$upstream_status
502
(Bad Gateway) 或 504
(Gateway Timeout)。5xx
) 还是 Nginx 本身无法连接后端。log_format backend_error ... '$upstream_status' ...;
$upstream_response_time
# 记录后端响应时间
log_format backend_perf ... '$upstream_response_time' ...;
$upstream_http_HEADER_NAME
_
代替连字符 -
,并加上前缀 $upstream_http_
。# 获取后端设置的 Cache-Control 头来决定是否缓存
proxy_cache_valid 200 302 10m if=($upstream_http_cache_control ~ "max-age=(\d+)");
# 记录后端自定义头
log_format ... '$upstream_http_x_backend_version' ...;
$scheme
http
或 https
)。# 强制 HTTPS 重定向
if ($scheme != "https") {
return 301 https://$host$request_uri;
}
$server_name
server_name
指令的第一个名称。$server_addr
/ $server_port
$server_addr
: 接受请求的服务器的 IP 地址。$server_port
: 接受请求的服务器的 端口号。$nginx_version
# 根据版本启用不同特性 (谨慎使用)
if ($nginx_version ~ "^1\.18\.") {
# 针对 1.18.x 的特定配置
}
综合案例详解:
动态访问日志:
log_format dynamic_log '$remote_addr - $remote_user [$time_iso8601] '
'"$request" $status $body_bytes_sent '
'"$http_referer" "$http_user_agent" '
'rt=$request_time uct="$upstream_connect_time" urt="$upstream_response_time" '
'ups_addr=$upstream_addr ups_status=$upstream_status '
'Host="$host" Real_IP="$http_x_forwarded_for"';
access_log /var/log/nginx/access.log dynamic_log;
基于 User-Agent 的访问控制:
map $http_user_agent $bad_bot {
default 0;
~*(googlebot|bingbot|yahoo) 0; # 允许好的爬虫
~*(scrapers|spammer|malicious) 1; # 阻止坏的用户代理
~*curl 1; # 阻止 curl (根据需求调整)
}
server {
...
if ($bad_bot) {
return 403 "Access Forbidden";
# 或者限制速率: limit_req zone=bot_zone burst=5 nodelay;
}
}
安全的文件下载限制:
location ~* \.(sql|conf|env|key)$ {
# 结合 $uri/$request_uri 匹配特定文件类型
if ($args !~ "downloadkey=secret123") { # 简单示例,生产环境用更安全方式
return 403 "Unauthorized access to sensitive files.";
}
# 或者只允许特定IP访问
allow 192.168.1.0/24;
deny all;
# ... 文件服务配置
}
负载均衡调试日志:
log_format lb_debug '$time_iso8601 | CLIENT: $remote_addr | REQ: "$request" | '
'UPSTREAM: $upstream_addr | STATUS: $upstream_status | '
'TIME: $request_time / $upstream_response_time';
access_log /var/log/nginx/lb_debug.log lb_debug;
自定义错误页面带信息:
error_page 404 /404.html;
location = /404.html {
internal; # 只能内部访问
# 在错误页面中嵌入变量 (需要SSI支持或动态生成)
ssi on;
add_header Content-Type text/html;
return 200 '404 Not Found
Oops! Page Not Found
The requested URL $uri
was not found on this server.
Error occurred at: $time_iso8601
';
}
重要注意事项:
if
指令的陷阱: if
在 location
上下文中有许多限制和令人惊讶的行为(有时会破坏继承关系)。尽量避免在 if
块中进行复杂的配置修改,优先使用 map
, try_files
, return
, rewrite
(带 last
/break
) 或 split_clients
等更安全的指令。如果必须用 if
,主要用于检查变量值然后 return
或 rewrite
。set
指令创建的自定义变量作用域是其所在的配置块(http
, server
, location
)。map
块可能会影响性能。在高流量环境中需谨慎优化。$args
, $request_uri
, $http_*
)直接用于敏感操作(如文件路径、命令执行)。始终进行验证、过滤或转义。$remote_user
未认证时,$upstream_*
未使用代理时)。在逻辑判断时要考虑这种情况。http://nginx.org/en/docs/varindex.html
。不同模块提供的变量在其模块文档中列出。通过熟练理解和灵活运用这些内置变量,你可以极大地提升 Nginx 配置的灵活性、可观测性和功能性,构建出更强大、更智能的 Web 服务器配置。