总字符数: 33.74K
代码: 21.74K, 文本: 8.64K
预计阅读时间: 2.20 小时
Nginx 配置
Nginx 的默认配置文件为
nginx.conf
.
nginx -c xxx.conf
- 以指定的文件作为配置文件,启动 Nginx.
配置文件实例
以下为一个 nginx.conf
配置文件实例:
1 | #定义 nginx 运行的用户和用户组 |
基本规则
管理 Nginx 配置
随着 Nginx 配置的增长,您有必要组织、管理配置内容.
当您的 Nginx 配置增加时,组织配置的需求也会增加. 井井有条的代码是:
- 易于理解
- 易于维护
- 易于使用
使用
include
指令可将常用服务器配置移动到单独的文件中,并将特定代码附加到全局配置,上下文等中.
我总是尝试在配置树的根目录中保留多个目录. 这些目录存储所有附加到主文件的配置文件. 我更喜欢以下结构:
html
- 用于默认静态文件,例如 全局 5xx 错误页面master
- 用于主要配置,例如 ACL,侦听指令和域
_acls
- 用于访问控制列表,例如 地理或地图模块_basic
- 用于速率限制规则,重定向映射或代理参数_listen
- 用于所有侦听指令; 还存储 SSL 配置_server
- 用于域(localhost)配置; 还存储所有后端定义modules
- 用于动态加载到 Nginx 中的模块snippets
- 用于 Nginx 别名,配置模板如果有必要,我会将其中一些附加到具有
server
指令的文件中.
示例:
1 | ## Store this configuration in https.conf for example: |
重加载 Nginx 配置
示例:
1 | ## 1) |
监听 80 和 443 端口
如果您使用完全相同的配置为 HTTP 和 HTTPS 提供服务(单个服务器同时处理 HTTP 和 HTTPS 请求),Nginx 足够智能,可以忽略通过端口 80 加载的 SSL 指令.
Nginx 的最佳实践是使用单独的服务器进行这样的重定向(不与您的主要配置的服务器共享),对所有内容进行硬编码,并且完全不使用正则表达式.
我不喜欢复制规则,但是单独的监听指令无疑可以帮助您维护和修改配置.
如果将多个域固定到一个 IP 地址,则很有用. 这使您可以将一个侦听指令(例如,如果将其保留在配置文件中)附加到多个域配置.
如果您使用的是 HTTPS,则可能还需要对域进行硬编码,因为您必须预先知道要提供的证书.
示例:
1 | ## For HTTP: |
显示指定监听的地址和端口
Nginx 的 listen 指令用于监听指定的 IP 地址和端口号,配置形式为:listen <address>:<port>
.若 IP 地址或端口缺失,Nginx 会以默认值来替换.
而且,仅当需要区分与 listen 指令中的同一级别匹配的服务器块时,才会评估 server_name 指令.
示例:
1 | server { |
防止使用未定义的服务器名称处理请求
Nginx 应该阻止使用未定义的服务器名称(也使用 IP 地址)处理请求.它可以防止配置错误,例如流量转发到不正确的后端.通过创建默认虚拟虚拟主机可以轻松解决该问题,该虚拟虚拟主机可以捕获带有无法识别的主机标头的所有请求.
如果没有一个 listen 指令具有 default_server 参数,则具有 address:port 对的第一台服务器将是该对的默认服务器(这意味着 Nginx 始终具有默认服务器).
如果有人使用 IP 地址而不是服务器名称发出请求,则主机请求标头字段将包含 IP 地址,并且可以使用 IP 地址作为服务器名称来处理请求.
在现代版本的 Nginx 中,不需要服务器名称_.如果找不到具有匹配的 listen 和 server_name 的服务器,Nginx 将使用默认服务器.如果您的配置分散在多个文件中,则评估顺序将不明确,因此您需要显式标记默认服务器.
Nginx 使用 Host 标头进行 server_name 匹配.它不使用 TLS SNI.这意味着对于 SSL 服务器,Nginx 必须能够接受 SSL 连接,这归结为具有证书/密钥.证书/密钥可以是任意值,例如自签名.
示例:
1 | # 将其放置在配置文件的开始位置以避免错误 |
不要在 listen 或 upstream 中使用 hostname
通常,在 listen 或上游指令中使用主机名是一种不好的做法.
在最坏的情况下,Nginx 将无法绑定到所需的 TCP 套接字,这将完全阻止 Nginx 启动.
最好和更安全的方法是知道需要绑定的 IP 地址,并使用该地址代替主机名. 这也可以防止 Nginx 查找地址并消除对外部和内部解析器的依赖.
在 server_name 指令中使用$ hostname(计算机的主机名)变量也是不当行为的示例(类似于使用主机名标签).
我认为也有必要设置 IP 地址和端口号对,以防止可能难以调试的软错误.
示例:
❌ 错误配置
1 | upstream { |
⭕ 正确配置
1 | upstream { |
指令中只配置一个 SSL
此规则使调试和维护更加容易.
请记住,无论 SSL 参数如何,您都可以在同一监听指令(IP 地址)上使用多个 SSL 证书.
我认为要在多个 HTTPS 服务器之间共享一个 IP 地址,您应该使用一个 SSL 配置(例如协议,密码,曲线).这是为了防止错误和配置不匹配.
还请记住有关默认服务器的配置.这很重要,因为如果所有 listen 指令都没有 default_server 参数,则配置中的第一台服务器将是默认服务器.因此,您应该只使用一个 SSL 设置,并且在同一 IP 地址上使用多个名称.
从 Nginx 文档中:
这是由 SSL 协议行为引起的.在浏览器发送 HTTP 请求之前,已建立 SSL 连接,nginx 不知道所请求服务器的名称.因此,它可能仅提供默认服务器的证书.
还要看看这个:
TLS 服务器名称指示扩展名(SNI,RFC 6066)是在单个 IP 地址上运行多个 HTTPS 服务器的更通用的解决方案,它允许浏览器在 SSL 握手期间传递请求的服务器名称,因此,服务器将知道哪个用于连接的证书.
另一个好主意是将常用服务器设置移到单独的文件(即 common / example.com.conf)中,然后将其包含在单独的服务器块中.
示例:
1 | # 将此配置存储在例如 https.conf 中: |
使用 geo/map 模块替代 allow/deny
使用地图或地理模块(其中之一)可以防止用户滥用您的服务器.这样就可以创建变量,其值取决于客户端 IP 地址.
由于仅在使用变量时才对其进行求值,因此甚至仅存在大量已声明的变量.地理位置变量不会为请求处理带来任何额外费用.
这些指令提供了阻止无效访问者的完美方法,例如使用 ngx_http_geoip_module.例如,geo 模块非常适合有条件地允许或拒绝 IP.
geo 模块(注意:不要将此模块误认为是 GeoIP)在加载配置时会构建内存基数树.这与路由中使用的数据结构相同,并且查找速度非常快.如果每个网络有许多唯一值,那么较长的加载时间是由在数组中搜索数据重复项引起的.否则,可能是由于插入基数树引起的.
我将两个模块都用于大型列表.您应该考虑一下,因为此规则要求使用多个 if 条件.我认为,对于简单的列表,毕竟允许/拒绝指令是更好的解决方案.看下面的例子:
1 | # allow/deny: |
示例:
1 | # Map模块: |
Map 所有事物
使用地图管理大量重定向,并使用它们来自定义键/值对.
map 指令可映射字符串,因此可以表示例如 192.168.144.0/24 作为正则表达式,并继续使用 map 指令.
Map 模块提供了一种更优雅的解决方案,用于清晰地解析大量正则表达式,例如 用户代理,引荐来源.
您还可以对地图使用 include 指令,这样配置文件看起来会很漂亮.
示例:
1 | map $http_user_agent $device_redirect { |
为所有未匹配的路径设置根路径
为请求设置服务器指令内部的全局根路径. 它为未定义的位置指定根路径.
根据官方文档:
如果您在每个位置块中添加一个根路径,则不匹配的位置块将没有根路径.因此,重要的是,根指令必须在您的位置块之前发生,然后根目录指令可以在需要时覆盖该指令.
示例:
1 | server { |
使用 return 指令进行 URL 重定向(301、302)
这是一个简单的规则. 您应该使用服务器块和 return 语句,因为它们比评估 RegEx 更快.
因为 Nginx 停止处理请求(而不必处理正则表达式),所以它更加简单快捷.
示例
1 | server { |
配置日志轮换策略
日志文件为您提供有关服务器活动和性能以及可能出现的任何问题的反馈. 它们记录了有关请求和 Nginx 内部的详细信息. 不幸的是,日志使用了更多的磁盘空间.
您应该定义一个过程,该过程将定期存档当前日志文件并启动一个新日志文件,重命名并有选择地压缩当前日志文件,删除旧日志文件,并强制日志记录系统开始使用新日志文件.
我认为最好的工具是 logrotate. 如果我想自动管理日志,也想睡个好觉,那么我会在任何地方使用它. 这是一个旋转日志的简单程序,使用 crontab 可以工作. 它是计划的工作,而不是守护程序,因此无需重新加载其配置.
示例:
手动旋转
1
2
3
4# 手动检查(所有日志文件):
logrotate -dv /etc/logrotate.conf
# 手动检查并强制轮转(特定的日志文件):
logrotate -dv --force /etc/logrotate.d/nginx自动旋转
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67cat > /etc/logrotate.d/nginx << __EOF__
# 配置nginx日志轮转
/var/log/nginx/*.log {
daily # 每天执行日志轮转
missingok # 如果日志丢失也不报错
rotate 14 # 保存14份旧日志
compress # 对旧日志进行压缩
delaycompress # 延迟压缩
notifempty # 如果日志为空,则不轮转
create 0640 nginx nginx # 轮转后创建新日志文件,指定权限和所有者
sharedscripts # 脚本在所有日志轮转后运行一次
prerotate # 轮转前脚本
if [ -d /etc/logrotate.d/httpd-prerotate ]; then \
run-parts /etc/logrotate.d/httpd-prerotate; \
fi \
endscript
postrotate # 轮转后脚本
# 以下为注释的命令,用于发送信号给nginx主进程,但实际使用下面的命令来平滑重载nginx
# test ! -f /var/run/nginx.pid || kill -USR1 `cat /var/run/nginx.pid`
invoke-rc.d nginx reload >/dev/null 2>&1 # 重载nginx配置,输出重定向到/dev/null
endscript
}
# 针对localhost的日志文件同上配置
/var/log/nginx/localhost/*.log {
daily
missingok
rotate 14
compress
delaycompress
notifempty
create 0640 nginx nginx
sharedscripts
prerotate
if [ -d /etc/logrotate.d/httpd-prerotate ]; then \
run-parts /etc/logrotate.d/httpd-prerotate; \
fi \
endscript
postrotate
# 这是注释掉的命令,旨在向nginx主进程发送信号,但实际使用以下命令进行平滑重启
# test ! -f /var/run/nginx.pid || kill -USR1 `cat /var/run/nginx.pid`
invoke-rc.d nginx reload >/dev/null 2>&1 # 重载nginx配置,输出重定向到/dev/null
endscript
}
# 配置针对特定域名example.com的日志文件轮转规则,与前面的配置基本一致
/var/log/nginx/domains/example.com/*.log {
daily
missingok
rotate 14
compress
delaycompress
notifempty
create 0640 nginx nginx
sharedscripts
prerotate
if [ -d /etc/logrotate.d/httpd-prerotate ]; then \
run-parts /etc/logrotate.d/httpd-prerotate; \
fi \
endscript
postrotate
# 同上,注释掉的命令可以发送信号给nginx,但推荐使用下面的命令
# test ! -f /var/run/nginx.pid || kill -USR1 `cat /var/run/nginx.pid`
invoke-rc.d nginx reload >/dev/null 2>&1 # 重载nginx配置,输出重定向到/dev/null
endscript
}
__EOF__
不要重复索引指令,只能在 http 块中使用
一次使用 index 指令. 它只需要在您的 http 上下文中发生,并将在下面继承.
我认为我们在复制相同规则时应格外小心. 但是,当然,规则的重复有时是可以的,或者不一定是大麻烦.
示例:
❌ 错误配置
1 | http { |
⭕ 正确配置
1 | http { |
Debugging
使用自定义日志格式
您可以在 Nginx 配置中作为变量访问的任何内容都可以记录,包括非标准的 HTTP 标头等.因此,这是一种针对特定情况创建自己的日志格式的简单方法.
这对于调试特定的
location
指令非常有帮助.
示例:
1 | # 默认的主日志格式来自Nginx官方仓库: |
使用调试模式来跟踪意外行为
通常,
error_log
指令是在main
中指定的,但是也可以在server
或location
块中指定,全局设置将被覆盖,并且这个error_log
指令将设置其自己的日志文件路径和日志记录级别.如果要记录
ngx_http_rewrite_module
(at the notice level) ,应该在http
、server
或location
块中开启rewrite_log on;
.注意:
- 永远不要将调试日志记录留在生产环境中的文件上
- 不要忘记在流量非常高的站点上恢复
error_log
的调试级别- 必须使用日志回滚政策
示例:
- 将 debug 信息写入文件
1 | # 在特定上下文中开启,例如: |
- 将 debug 信息写入内存
1 | error_log memory:32m debug; |
- IP 地址/范围的调试日志:
1 | events { |
- 为不同服务器设置不同 Debug 配置
1 | error_log /var/log/nginx/debug.log debug; |
核心转储
核心转储基本上是程序崩溃时内存的快照.
Nginx 是一个非常稳定的守护程序,但是有时可能会发生正在运行的 Nginx 进程独特终止的情况.
如果要保存内存转储,它可以确保应启用两个重要的指令,但是,为了正确处理内存转储,需要做一些事情. 有关它的完整信息,请参见转储进程的内存(来自本手册).
当您的 Nginx 实例收到意外错误或崩溃时,应始终启用核心转储.
示例:
1 | worker_rlimit_core 500m; |
性能
工作进程数
worker_processes
- 用于设置 Nginx 的工作进程数.
- worker_processes 的默认值为 1.
- 设置 worker_processes 的安全做法是将其设为 auto,则启动 Nginx 时会自动分配工作进程数.当然,也可以显示的设置一个工作进程数值.
- 一般一个进程足够了,你可以把连接数设得很大.(worker_processes: 1,worker_connections: 10,000)如果有 SSL、gzip 这些比较消耗 CPU 的工作,而且是多核 CPU 的话,可以设为和 CPU 的数量一样.或者要处理很多很多的小文件,而且文件总大小比内存大很多的时候,也可以把进程数增加,以充分利用 IO 带宽(主要似乎是 IO 操作有 block)
示例:
1 | ## The safest way: |
最大连接数
worker_connections
- 单个 Nginx 工作进程允许同时建立的外部连接的数量.数字越大,能同时处理的连接越多.
worker_connections
不是随便设置的,而是与两个指标有重要关联:
- 内存
- 每个连接数分别对应一个 read_event、一个 write_event 事件,一个连接数大概占用 232 字节,2 个事件总占用 96 字节,那么一个连接总共占用 328 字节,通过数学公式可以算出 100000 个连接数大概会占用 31M = 100000 * 328 / 1024 / 1024,当然这只是 nginx 启动时,worker_connections 连接数所占用的 nginx.
- 操作系统级别”进程最大可打开文件数”.
- 进程最大可打开文件数受限于操作系统,可通过
ulimit -n
命令查询,以前是 1024,现在是 65535. - nginx 提供了 worker_rlimit_nofile 指令,这是除了 ulimit 的一种设置可用的描述符的方式. 该指令与使用 ulimit 对用户的设置是同样的效果.此指令的值将覆盖 ulimit 的值,如:worker_rlimit_nofile 20960; 设置 ulimits:ulimit -SHn 65535
- 进程最大可打开文件数受限于操作系统,可通过
使用 HTTP/2
HTTP / 2 将使我们的应用程序更快,更简单且更可靠. HTTP / 2 的主要目标是通过启用完整的请求和响应多路复用来减少延迟,通过有效压缩 HTTP 标头字段来最小化协议开销,并增加对请求优先级和服务器推送的支持.
HTTP / 2 与 HTTP / 1.1 向后兼容,因此有可能完全忽略它,并且一切都会像以前一样继续工作,因为如果不支持 HTTP / 2 的客户端永远不会向服务器请求 HTTP / 2 通讯升级:它们之间的通讯将完全是 HTTP1 / 1.
请注意,HTTP / 2 在单个 TCP 连接中多路复用许多请求. 通常,当使用 HTTP / 2 时,将与服务器建立单个 TCP 连接.
您还应该包括 ssl 参数,这是必需的,因为浏览器不支持未经加密的 HTTP / 2.
HTTP / 2 对旧的和不安全的密码有一个非常大的黑名单,因此您应该避免使用它们.
示例:
1 | server { |
维护 SSL 会话
客户端每次发出请求时都进行新的 SSL 握手的需求.默认情况下,内置会话缓存并不是最佳选择,因为它只能由一个工作进程使用,并且可能导致内存碎片,最好使用共享缓存.
使用
ssl_session_cache
时,通过 SSL 保持连接的性能可能会大大提高.10M 的值是一个很好的起点(1MB 共享缓存可以容纳大约 4,000 个会话).通过共享,所有工作进程之间共享一个缓存(可以在多个虚拟服务器中使用相同名称的缓存).但是,大多数服务器不清除会话或票证密钥,因此增加了服务器受到损害将泄漏先前(和将来)连接中的数据的风险.
示例:
1 | ssl_session_cache shared:NGX_SSL_CACHE:10m; |
尽可能在 server_name 指令中使用确切名称
确切名称,以星号开头的通配符名称和以星号结尾的通配符名称存储在绑定到侦听端口的三个哈希表中.
首先搜索确切名称哈希表. 如果未找到名称,则搜索具有以星号开头的通配符名称的哈希表. 如果未在此处找到名称,则搜索带有通配符名称以星号结尾的哈希表. 搜索通配符名称哈希表比搜索精确名称哈希表要慢,因为名称是按域部分搜索的.
正则表达式是按顺序测试的,因此是最慢的方法,并且不可缩放.由于这些原因,最好在可能的地方使用确切的名称.
示例:
1 | # 显式地定义它们更为高效: |
避免使用 if
检查 server_name
当 Nginx 收到请求时,无论请求的是哪个子域,无论是 www.example.com 还是普通的 example.com,如果始终对 if 指令进行评估. 由于您是在请求 Nginx 检查每个请求的 Host 标头. 效率极低.
而是使用两个服务器指令,如下面的示例. 这种方法降低了 Nginx 的处理要求.
示例:
❌ 错误配置
1 | server { |
⭕ 正确配置
1 | server { |
使用 $request_uri
来避免使用正则表达式
使用内置变量
$request_uri
,我们可以完全避免进行任何捕获或匹配.默认情况下,正则表达式的代价较高,并且会降低性能.此规则用于解决将 URL 不变地传递到新主机,确保仅通过现有 URI 进行返回的效率更高.
示例:
❌ 错误配置
1 | ## 1) |
⭕ 正确配置
1 | return 301 https://example.com$request_uri; |
使用 try_files
指令确认文件是否存在
try_files
绝对是一个非常有用的指令.你可以使用try_files
指令按照指定的顺序检查文件是否存在.
你应该使用try_files
而不是if
指令.使用try_files
完成这个操作绝对比使用if
更好,因为if
指令非常低效,它会在每个请求时都被评估.
使用try_files
的优势在于,只需一个命令行为就会立即切换.我认为代码的可读性也更强.try_files
允许你:
- 从预定义列表中检查文件是否存在
- 从指定目录检查文件是否存在
- 如果没有找到任何文件,使用内部重定向
示例:
❌ 错误配置
1 | ... |
⭕ 正确配置
1 | ... |
使用 return 代替 rewrite 来做重定向
您应该使用服务器块和 return 语句,因为它们比通过位置块评估 RegEx 更简单,更快捷. 该指令停止处理,并将指定的代码返回给客户端.
示例:
❌ 错误配置
1 | server { |
⭕ 正确配置
1 | server { |
开启 PCRE JIT 来加速正则表达式处理
允许使用 JIT 的正则表达式来加速他们的处理.
通过与 PCRE 库编译 Nginx 的,你可以用你的 location 块进行复杂的操作和使用功能强大的 return 和 rewrite.
PCRE JIT 可以显著加快正则表达式的处理. Nginx 的与 pcre_jit 比没有更快的幅度.
如果你试图在使用 pcre_jit;没有可用的 JIT,或者 Nginx 的与现有 JIT,但当前加载 PCRE 库编译不支持 JIT,将配置解析时发出警告.
当您编译使用 NGNIX 配置 PCRE 库时,才需要–with-PCRE-JIT 时(./configure –with-PCRE =).当使用系统 PCRE 库 JIT 是否被支持依赖于库是如何被编译.
从 Nginx 的文档:
JIT 正在从与–enable-JIT 配置参数内置 8.20 版本开始 PCRE 库提供.当 PCRE 库与 nginx 的内置(–with-PCRE =)时,JIT 支持经由–with-PCRE-JIT 配置参数使能.
示例:
1 | ## In global context: |
进行精确的位置匹配以加快选择过程
精确的位置匹配通常用于通过立即结束算法的执行来加快选择过程.
示例:
1 | # 仅匹配查询 / 并停止搜索: |
使用 limit_conn
改善对下载速度的限制
Nginx provides two directives to limiting download speed:
Nginx 提供了两个指令来限制下载速度:
limit_rate_after
- 设置 limit_rate 指令生效之前传输的数据量limit_rate
- 允许您限制单个客户端连接的传输速率
此解决方案限制了每个连接的 Nginx 下载速度,因此,如果一个用户打开多个(例如) 视频文件,则可以下载
X * 连接到视频文件的次数
.
示例:
1 | # 创建连接数限制区域: |
反向代理
使用与后端协议兼容的 pass 指令
所有
proxy_*
指令都与使用特定后端协议的后端服务器有关.
您应该仅对在后端层工作的 HTTP 服务器使用proxy_pass
(在引用 HTTP 后端前设置http://
协议),
对于非 HTTP 后端服务器(如 uWSGI 或 FastCGI)则使用其他的*_pass
指令.uwsgi_pass
、fastcgi_pass
或scgi_pass
等指令专为非 HTTP 应用程序设计,
您应该使用它们而不是proxy_pass
(非 HTTP 通信).
例如:uwsgi_pass
使用 uwsgi 协议与服务器通信.proxy_pass
使用普通 HTTP 与 uWSGI 服务器通信.
uWSGI 文档声称 uwsgi 协议更好、更快,并且可以利用 uWSGI 的所有特殊功能.
您可以发送信息到 uWSGI,告知您正在发送何种类型的数据以及应调用哪个 uWSGI 插件来生成响应.
使用 http(proxy_pass
)就无法做到这一点.
示例:
❌ 错误配置
1 | server { |
⭕ 正确配置
1 | server { |
小心 proxy_pass
指令中的斜杠
注意尾随斜杠,因为 Nginx 会逐字替换部分,并且您可能会得到一些奇怪的 URL.
如果 proxy_pass 不带 URI 使用(即 server:port 之后没有路径),Nginx 会将原始请求中的 URI 与所有双斜杠
../
完全一样.
proxy_pass
中的 URI 就像别名指令一样,意味着 Nginx 将用proxy_pass
指令中的 URI 替换与位置前缀匹配的部分(我故意将其与位置前缀相同),因此 URI 将与请求的相同,但被规范化(没有小写斜杠和其他所有内容) 员工).
示例:
1 | location = /a { |
仅使用 $host
变量设置和传递 Host 头
几乎应该始终将
$host
用作传入的主机变量,因为无论用户代理如何行为,它都是保证具有某种意义的唯一变量,除非您特别需要其他变量之一的语义.变量
$host
是请求行或http头中的主机名. 变量$server_name
是我们当前所在的服务器块的名称.
区别:
$host
包含”按此优先顺序:请求行中的主机名,或”主机”请求标头字段中的主机名,或与请求匹配的服务器名”- 如果请求中包含HTTP主机标头字段,则
$http_host
包含该内容(始终等于HTTP_HOST请求标头)$server_name
包含了处理请求的虚拟主机的server_name
,正如它在 Nginx 配置中定义的那样.如果一个服务器有多个服务器名,这个变量中只会有第一个.
$http_host
比$host:$server_port
更好,因为它使用的是 URL 中出现的端口号,而不是 Nginx 监听的端口号,后者是$server_port
使用的端口.
示例:
1 | proxy_set_header Host $host; |
正确设置 X-Forwarded-For
头的值
Rationale
鉴于最新的httpoxy漏洞,确实需要一个完整的示例来正确使用
HTTP_X_FORWARDED_FOR
. 简而言之,负载均衡器设置头信息中的’最新’部分.我认为,出于安全原因,管理员必须手动指定代理服务器.
X-Forwarded-For
是一个自定义的HTTP头部,它携带了客户端的原始IP地址,这样端点的应用程序就能知道这个地址是什么. 否则,它只会看到代理的IP地址,这可能会导致一些应用程序出现问题
X-Forwarded-For
取决于代理服务器,它应该传递连接到它的客户端的IP地址. 当连接通过一系列代理服务器时,
X-Forwarded-For
可以提供一个以逗号分隔的IP地址列表,其中第一个地址是最远的下游(即用户). 正因为如此,位于代理服务器后面的服务器需要知道哪些代理服务器是可信的.
使用的代理可以将此头设置为其希望的任何值,因此您不能信任它的值.尽管大多数代理确实设置了正确的值.
这个头部主要被缓存代理使用,在这些情况下,您控制着代理,因此可以验证它提供给您的信息是否正确.
在所有其他情况下,它的值应被视为不可信的.
一些系统也使用
X-Forwarded-For
来执行访问控制.许多应用程序依赖于知道客户端的实际IP地址来帮助防止欺诈和启用访问权限.
X-Forwarded-For
头部字段的值可以在客户端设置 - 这也可以被称为X-Forwarded-For
伪造.然而,当通过代理服务器发出web请求时,代理服务器通过附加客户端(用户)的IP地址修改X-Forwarded-For
字段.这将在X-Forwarded-For
字段中产生2个逗号分隔的IP地址. 反向代理不是源IP地址透明的.当你需要后端服务器的日志中的客户端源IP地址是正确的时,这是一个问题.我认为解决这个问题的最佳方案是配置负载均衡器添加/修改带有客户端源IP的
X-Forwarded-For
头,并以正确的形式转发给后端. 不幸的是,在代理端我们无法解决这个问题(所有解决方案都可能被伪造),重要的是这个头被应用服务器正确解释.这样做确保了应用程序或下游服务拥有准确的信息来进行决策,包括那些关于访问和授权的决策.
为了防止这种情况,我们必须默认不信任那个头部,并从我们的服务器向后追踪IP地址:
首先我们需要确保
REMOTE_ADDR
是我们信任的,有能力在X-Forwarded-For
的末尾追加了一个正确的值.如果是这样,那么我们需要确保我们信任X-Forwarded-For
中的IP,信任它在自己之前追加了正确的IP,依此类推.直到最后,我们得到一个我们不信任的IP,在那一点上我们必须假设那是我们用户的IP.- 这个观点来自Xiao Yu在代理和IP欺骗中的观点.
Example
1 | # 它存在的整个目的是为了执行追加行为: |
不要在反向代理后面使用带有 $scheme
的 X-Forwarded-Proto
反向代理可以设置
X-Forwarded-Proto
,以告知应用程序它是HTTPS还是HTTP甚至是无效名称.schema 变量仅在需要的时候才会被评估(仅用于当前请求).如果设置了 $schema 变量且沿途遇上多个代理,则会导致变形.例如:如果客户端转到https://example.com,则代理将方案值存储为HTTPS. 如果代理与下一级代理之间的通信是通过HTTP进行的,则后端会将方案视为HTTP.
示例:
1 | # 1) 客户端 <-> 代理 <-> 后端 |
始终将 Host,X-Real-IP 和 X-Forwarded 标头传递给后端
Rationale
在使用Nginx作为反向代理时,你可能希望将远程客户端的一些信息传递给你的后端Web服务器.我认为这是一个好做法,因为它能让你更好地控制转发的头部信息.
这对于位于代理后面的服务器来说非常重要,因为它允许正确地解释客户端.代理是这些服务器的”眼睛”,它们不应允许对现实的扭曲感知.如果并非所有请求都通过代理,那么直接从客户端收到的请求可能包含例如不准确的头部中的IP地址.
X-Forwarded
头部对于统计或过滤也很重要.另一个例子可能是你的应用中的访问控制规则,因为如果没有这些头部,过滤机制可能无法正常工作. 如果你使用像Apache这样的前端服务作为你的API的前端,你将需要这些头部来理解连接API时使用的IP或主机名.
如果你使用https协议(现在已经成为标准),转发这些头部也很重要.
然而,我不会完全依赖于所有
X-Forwarded
头部的存在,或者他们数据的有效性.
Example
1 | location / { |
prefix 使用不带 X-
前缀的自定义头
Rationale
互联网工程任务组(IETF)发布了一个新的RFC(RFC-6648),建议弃用
X-
前缀.
X-
前缀在头部名称前通常表示它是实验性的、非标准的或特定于供应商的.一旦它成为HTTP的标准部分,就会失去这个前缀. 如果有可能将新的自定义头部标准化,请使用一个未被使用且有意义的头部名称.
使用带有
X-
前缀的自定义头部并不禁止,但是不鼓励.换句话说,你可以继续使用带有X-
前缀的头部,但不推荐这样做,并且你不应该将它们记录为公共标准.
Example
不推荐的配置:
1 | add_header X-Backend-Server $hostname; |
推荐的配置:
1 | add_header Backend-Server $hostname; |
负载均衡
负载平衡是一种有用的机制,可将传入的流量分布在几个有能力的服务器之间.
健康检查
健康监控对于所有类型的负载平衡都非常重要,主要是为了业务连续性. 被动检查会按照客户端的请求监视通过 Nginx 的连接失败或超时.
默认情况下启用此功能,但是此处提到的参数允许您调整其行为. 默认值为:
max_fails = 1
和fail_timeout = 10s
.
示例:
1 | upstream backend { |
down 参数
有时我们需要关闭后端,例如 在维护时. 我认为良好的解决方案是使用 down 参数将服务器标记为永久不可用,即使停机时间很短也是如此.
如果您使用 IP 哈希负载平衡技术,那也很重要. 如果其中一台服务器需要临时删除,则应使用此参数进行标记,以保留客户端 IP 地址的当前哈希值.
注释对于真正永久禁用服务器或要出于历史目的而保留信息非常有用.
Nginx 还提供了一个备份参数,将该服务器标记为备份服务器. 当主服务器不可用时,将传递请求. 仅当我确定后端将在维护时正常工作时,我才很少将此选项用于上述目的.
示例
1 | upstream backend { |
安全
防盗链
1 | location ~* \.(gif|jpg|png)$ { |