基本规则

管理 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:
  2. listen 10.240.20.2:443 ssl;
  3. ssl_certificate /etc/nginx/master/_server/example.com/certs/nginx_example.com_bundle.crt;
  4. ssl_certificate_key /etc/nginx/master/_server/example.com/certs/example.com.key;
  5. ## Include this file to the server section:
  6. server {
  7. include /etc/nginx/master/_listen/10.240.20.2/https.conf;
  8. ## And other:
  9. include /etc/nginx/master/_static/errors.conf;
  10. include /etc/nginx/master/_server/_helpers/global.conf;
  11. ...
  12. server_name domain.com www.domain.com;
  13. ...

重加载 Nginx 配置

示例:

  1. ## 1)
  2. systemctl reload nginx
  3. ## 2)
  4. service nginx reload
  5. ## 3)
  6. /etc/init.d/nginx reload
  7. ## 4)
  8. /usr/sbin/nginx -s reload
  9. ## 5)
  10. kill -HUP $(cat /var/run/nginx.pid)
  11. ## or
  12. kill -HUP $(pgrep -f "nginx: master")
  13. ## 6)
  14. /usr/sbin/nginx -g 'daemon on; master_process on;' -s reload

监听 80 和 443 端口

如果您使用完全相同的配置为 HTTP 和 HTTPS 提供服务(单个服务器同时处理 HTTP 和 HTTPS 请求),Nginx 足够智能,可以忽略通过端口 80 加载的 SSL 指令。

Nginx 的最佳实践是使用单独的服务器进行这样的重定向(不与您的主要配置的服务器共享),对所有内容进行硬编码,并且完全不使用正则表达式。

我不喜欢复制规则,但是单独的监听指令无疑可以帮助您维护和修改配置。

如果将多个域固定到一个 IP 地址,则很有用。 这使您可以将一个侦听指令(例如,如果将其保留在配置文件中)附加到多个域配置。

如果您使用的是 HTTPS,则可能还需要对域进行硬编码,因为您必须预先知道要提供的证书。

示例:

  1. ## For HTTP:
  2. server {
  3. listen 10.240.20.2:80;
  4. ...
  5. }
  6. ## For HTTPS:
  7. server {
  8. listen 10.240.20.2:443 ssl;
  9. ...
  10. }

显示指定监听的地址和端口

Nginx 的 listen 指令用于监听指定的 IP 地址和端口号,配置形式为:listen <address>:<port>。若 IP 地址或端口缺失,Nginx 会以默认值来替换。

而且,仅当需要区分与 listen 指令中的同一级别匹配的服务器块时,才会评估 server_name 指令。

示例:

  1. server {
  2. ## This block will be processed:
  3. listen 192.168.252.10; ## --> 192.168.252.10:80
  4. ...
  5. }
  6. server {
  7. listen 80; ## --> *:80 --> 0.0.0.0:80
  8. server_name api.random.com;
  9. ...
  10. }

防止使用未定义的服务器名称处理请求

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. ## Place it at the beginning of the configuration file to prevent mistakes:
  2. server {
  3. ## For ssl option remember about SSL parameters (private key, certs, cipher suites, etc.);
  4. ## add default_server to your listen directive in the server that you want to act as the default:
  5. listen 10.240.20.2:443 default_server ssl;
  6. ## We catch:
  7. ## - invalid domain names
  8. ## - requests without the "Host" header
  9. ## - and all others (also due to the above setting)
  10. ## - default_server in server_name directive is not required - I add this for a better understanding and I think it's an unwritten standard
  11. ## ...but you should know that it's irrelevant, really, you can put in everything there.
  12. server_name _ "" default_server;
  13. ...
  14. return 444;
  15. ## We can also serve:
  16. ## location / {
  17. ## static file (error page):
  18. ## root /etc/nginx/error-pages/404;
  19. ## or redirect:
  20. ## return 301 https://badssl.com;
  21. ## return 444;
  22. ## }
  23. }
  24. server {
  25. listen 10.240.20.2:443 ssl;
  26. server_name domain.com;
  27. ...
  28. }
  29. server {
  30. listen 10.240.20.2:443 ssl;
  31. server_name domain.org;
  32. ...
  33. }

不要在 listen 或 upstream 中使用 hostname

通常,在 listen 或上游指令中使用主机名是一种不好的做法。

在最坏的情况下,Nginx 将无法绑定到所需的 TCP 套接字,这将完全阻止 Nginx 启动。

最好和更安全的方法是知道需要绑定的 IP 地址,并使用该地址代替主机名。 这也可以防止 Nginx 查找地址并消除对外部和内部解析器的依赖。

在 server_name 指令中使用\$ hostname(计算机的主机名)变量也是不当行为的示例(类似于使用主机名标签)。

我认为也有必要设置 IP 地址和端口号对,以防止可能难以调试的软错误。

示例:

❌ 错误配置

  1. upstream {
  2. server http://x-9s-web01-prod:8080;
  3. }
  4. server {
  5. listen rev-proxy-prod:80;
  6. ...
  7. }

⭕ 正确配置

  1. upstream {
  2. server http://192.168.252.200:8080;
  3. }
  4. server {
  5. listen 10.10.100.20:80;
  6. ...
  7. }

指令中只配置一个 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. ## Store this configuration in e.g. https.conf:
  2. listen 192.168.252.10:443 default_server ssl http2;
  3. ssl_protocols TLSv1.2;
  4. ssl_ciphers "ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384";
  5. ssl_prefer_server_ciphers on;
  6. ssl_ecdh_curve secp521r1:secp384r1;
  7. ...
  8. ## Include this file to the server context (attach domain-a.com for specific listen directive):
  9. server {
  10. include /etc/nginx/https.conf;
  11. server_name domain-a.com;
  12. ssl_certificate domain-a.com.crt;
  13. ssl_certificate_key domain-a.com.key;
  14. ...
  15. }
  16. ## Include this file to the server context (attach domain-b.com for specific listen directive):
  17. server {
  18. include /etc/nginx/https.conf;
  19. server_name domain-b.com;
  20. ssl_certificate domain-b.com.crt;
  21. ssl_certificate_key domain-b.com.key;
  22. ...
  23. }

使用 geo/map 模块替代 allow/deny

使用地图或地理模块(其中之一)可以防止用户滥用您的服务器。这样就可以创建变量,其值取决于客户端 IP 地址。

由于仅在使用变量时才对其进行求值,因此甚至仅存在大量已声明的变量。地理位置变量不会为请求处理带来任何额外费用。

这些指令提供了阻止无效访问者的完美方法,例如使用 ngx_http_geoip_module。例如,geo 模块非常适合有条件地允许或拒绝 IP。

geo 模块(注意:不要将此模块误认为是 GeoIP)在加载配置时会构建内存基数树。这与路由中使用的数据结构相同,并且查找速度非常快。如果每个网络有许多唯一值,那么较长的加载时间是由在数组中搜索数据重复项引起的。否则,可能是由于插入基数树引起的。

我将两个模块都用于大型列表。您应该考虑一下,因为此规则要求使用多个 if 条件。我认为,对于简单的列表,毕竟允许/拒绝指令是更好的解决方案。看下面的例子:

  1. ## Allow/deny:
  2. location /internal {
  3. include acls/internal.conf;
  4. allow 192.168.240.0/24;
  5. deny all;
  6. ...
  7. ## vs geo/map:
  8. location /internal {
  9. if ($globals_internal_map_acl) {
  10. set $pass 1;
  11. }
  12. if ($pass = 1) {
  13. proxy_pass http://localhost:80;
  14. }
  15. if ($pass != 1) {
  16. return 403;
  17. }
  18. ...
  19. }

示例:

  1. ## Map module:
  2. map $remote_addr $globals_internal_map_acl {
  3. ## Status code:
  4. ## - 0 = false
  5. ## - 1 = true
  6. default 0;
  7. ### INTERNAL ###
  8. 10.255.10.0/24 1;
  9. 10.255.20.0/24 1;
  10. 10.255.30.0/24 1;
  11. 192.168.0.0/16 1;
  12. }
  13. ## Geo module:
  14. geo $globals_internal_geo_acl {
  15. ## Status code:
  16. ## - 0 = false
  17. ## - 1 = true
  18. default 0;
  19. ### INTERNAL ###
  20. 10.255.10.0/24 1;
  21. 10.255.20.0/24 1;
  22. 10.255.30.0/24 1;
  23. 192.168.0.0/16 1;
  24. }

Map 所有事物

使用地图管理大量重定向,并使用它们来自定义键/值对。

map 指令可映射字符串,因此可以表示例如 192.168.144.0/24 作为正则表达式,并继续使用 map 指令。

Map 模块提供了一种更优雅的解决方案,用于清晰地解析大量正则表达式,例如 用户代理,引荐来源。

您还可以对地图使用 include 指令,这样配置文件看起来会很漂亮。

示例:

  1. map $http_user_agent $device_redirect {
  2. default "desktop";
  3. ~(?i)ip(hone|od) "mobile";
  4. ~(?i)android.*(mobile|mini) "mobile";
  5. ~Mobile.+Firefox "mobile";
  6. ~^HTC "mobile";
  7. ~Fennec "mobile";
  8. ~IEMobile "mobile";
  9. ~BB10 "mobile";
  10. ~SymbianOS.*AppleWebKit "mobile";
  11. ~Opera\sMobi "mobile";
  12. }
  13. ## Turn on in a specific context (e.g. location):
  14. if ($device_redirect = "mobile") {
  15. return 301 https://m.domain.com$request_uri;
  16. }

为所有未匹配的路径设置根路径

为请求设置服务器指令内部的全局根路径。 它为未定义的位置指定根路径。

根据官方文档:

如果您在每个位置块中添加一个根路径,则不匹配的位置块将没有根路径。因此,重要的是,根指令必须在您的位置块之前发生,然后根目录指令可以在需要时覆盖该指令。

示例:

  1. server {
  2. server_name domain.com;
  3. root /var/www/domain.com/public;
  4. location / {
  5. ...
  6. }
  7. location /api {
  8. ...
  9. }
  10. location /static {
  11. root /var/www/domain.com/static;
  12. ...
  13. }
  14. }

使用 return 指令进行 URL 重定向(301、302)

这是一个简单的规则。 您应该使用服务器块和 return 语句,因为它们比评估 RegEx 更快。

因为 Nginx 停止处理请求(而不必处理正则表达式),所以它更加简单快捷。

示例

  1. server {
  2. server_name www.example.com;
  3. ## return 301 https://$host$request_uri;
  4. return 301 $scheme://www.example.com$request_uri;
  5. }

配置日志轮换策略

日志文件为您提供有关服务器活动和性能以及可能出现的任何问题的反馈。 它们记录了有关请求和 Nginx 内部的详细信息。 不幸的是,日志使用了更多的磁盘空间。

您应该定义一个过程,该过程将定期存档当前日志文件并启动一个新日志文件,重命名并有选择地压缩当前日志文件,删除旧日志文件,并强制日志记录系统开始使用新日志文件。

我认为最好的工具是 logrotate。 如果我想自动管理日志,也想睡个好觉,那么我会在任何地方使用它。 这是一个旋转日志的简单程序,使用 crontab 可以工作。 它是计划的工作,而不是守护程序,因此无需重新加载其配置。

示例:

  • 手动旋转

    1. ## Check manually (all log files):
    2. logrotate -dv /etc/logrotate.conf
    3. ## Check manually with force rotation (specific log file):
    4. logrotate -dv --force /etc/logrotate.d/nginx
  • 自动旋转

    1. cat > /etc/logrotate.d/nginx << __EOF__
    2. /var/log/nginx/*.log {
    3. daily
    4. missingok
    5. rotate 14
    6. compress
    7. delaycompress
    8. notifempty
    9. create 0640 nginx nginx
    10. sharedscripts
    11. prerotate
    12. if [ -d /etc/logrotate.d/httpd-prerotate ]; then \
    13. run-parts /etc/logrotate.d/httpd-prerotate; \
    14. fi \
    15. endscript
    16. postrotate
    17. ## test ! -f /var/run/nginx.pid || kill -USR1 `cat /var/run/nginx.pid`
    18. invoke-rc.d nginx reload >/dev/null 2>&1
    19. endscript
    20. }
    21. /var/log/nginx/localhost/*.log {
    22. daily
    23. missingok
    24. rotate 14
    25. compress
    26. delaycompress
    27. notifempty
    28. create 0640 nginx nginx
    29. sharedscripts
    30. prerotate
    31. if [ -d /etc/logrotate.d/httpd-prerotate ]; then \
    32. run-parts /etc/logrotate.d/httpd-prerotate; \
    33. fi \
    34. endscript
    35. postrotate
    36. ## test ! -f /var/run/nginx.pid || kill -USR1 `cat /var/run/nginx.pid`
    37. invoke-rc.d nginx reload >/dev/null 2>&1
    38. endscript
    39. }
    40. /var/log/nginx/domains/example.com/*.log {
    41. daily
    42. missingok
    43. rotate 14
    44. compress
    45. delaycompress
    46. notifempty
    47. create 0640 nginx nginx
    48. sharedscripts
    49. prerotate
    50. if [ -d /etc/logrotate.d/httpd-prerotate ]; then \
    51. run-parts /etc/logrotate.d/httpd-prerotate; \
    52. fi \
    53. endscript
    54. postrotate
    55. ## test ! -f /var/run/nginx.pid || kill -USR1 `cat /var/run/nginx.pid`
    56. invoke-rc.d nginx reload >/dev/null 2>&1
    57. endscript
    58. }
    59. __EOF__

不要重复索引指令,只能在 http 块中使用

一次使用 index 指令。 它只需要在您的 http 上下文中发生,并将在下面继承。

我认为我们在复制相同规则时应格外小心。 但是,当然,规则的重复有时是可以的,或者不一定是大麻烦。

示例:

❌ 错误配置

  1. http {
  2. ...
  3. index index.php index.htm index.html;
  4. server {
  5. server_name www.example.com;
  6. location / {
  7. index index.php index.html index.$geo.html;
  8. ...
  9. }
  10. }
  11. server {
  12. server_name www.example.com;
  13. location / {
  14. index index.php index.htm index.html;
  15. ...
  16. }
  17. location /data {
  18. index index.php;
  19. ...
  20. }
  21. ...
  22. }

⭕ 正确配置

  1. http {
  2. ...
  3. index index.php index.htm index.html index.$geo.html;
  4. server {
  5. server_name www.example.com;
  6. location / {
  7. ...
  8. }
  9. }
  10. server {
  11. server_name www.example.com;
  12. location / {
  13. ...
  14. }
  15. location /data {
  16. ...
  17. }
  18. ...
  19. }