面试题手册

梳理高频技术问题,帮助你按主题复习和查漏补缺。

服务端阅读 02026年5月28日 07:28

Nginx 如何配置虚拟主机?有哪些配置方式?

Nginx 如何配置虚拟主机?有哪些配置方式?虚拟主机(Virtual Host)是 Nginx 最核心的能力之一——一台服务器、一个 Nginx 进程,就能同时服务几十个甚至上百个网站。面试中这道题考察的不只是"怎么配",更是你对其背后路由机制的理解深度。Nginx 虚拟主机的本质就是 server 块。每个 server 块是一个独立的虚拟主机,Nginx 根据请求的域名、端口或 IP 地址,将请求路由到匹配的 server 块处理。三个关键指令决定了路由规则:listen:监听的地址和端口,如 listen 80 或 listen 443 sslserver_name:匹配请求头中的 Host 字段,支持精确匹配、通配符和正则root:该虚拟主机的网站根目录基于域名的虚拟主机这是生产环境最主流的方式。 多个域名共享同一个 IP,Nginx 根据 HTTP 请求头中的 Host 字段决定将请求交给哪个 server 块处理。这也叫 Name-Based Virtual Host:server { listen 80; server_name example.com www.example.com; root /var/www/example.com; index index.html; access_log /var/log/nginx/example.com.access.log; error_log /var/log/nginx/example.com.error.log; location / { try_files $uri $uri/ =404; }}server { listen 80; server_name test.com www.test.com; root /var/www/test.com; index index.html; access_log /var/log/nginx/test.com.access.log; error_log /var/log/nginx/test.com.error.log; location / { try_files $uri $uri/ =404; }}两个 server 块都监听 80 端口,Nginx 收到请求后先匹配 server_name,命中哪个就走哪个配置。server_name 用空格分隔可以写多个域名,www.example.com 和 example.com 都会命中第一个块。面试追问:如果两个 server 块的 servername 都匹配同一个域名会怎样? Nginx 有明确的匹配优先级:精确匹配 > 最长通配符前缀 > 最长通配符后缀 > 第一个正则匹配 > defaultserver。优先级相同则按配置文件加载顺序,先加载的优先。基于端口的虚拟主机通过不同端口区分服务,适合将管理后台、API 服务与主站做物理隔离:# 主站server { listen 80; server_name example.com; root /var/www/example.com; index index.html; location / { try_files $uri $uri/ =404; }}# 管理后台server { listen 8080; server_name example.com; root /var/www/example.com/admin; index index.html; location / { try_files $uri $uri/ =404; }}# HTTPS 安全服务server { listen 8443 ssl; server_name example.com; ssl_certificate /etc/nginx/ssl/example.com.crt; ssl_certificate_key /etc/nginx/ssl/example.com.key; root /var/www/example.com/secure; index index.html; location / { try_files $uri $uri/ =404; }}端口方式的缺点是用户需要显式指定端口号(如 example.com:8080),80 和 443 之外的端口对用户不友好,因此一般只用于内部服务或管理入口。基于 IP 地址的虚拟主机服务器绑定多个 IP 时,按 IP 地址区分站点。这种方式在早期互联网常用,但在现代云环境下已很少使用——公网 IP 资源有限且费用高,基于域名的方式更经济:server { listen 192.168.1.100:80; server_name example.com; root /var/www/example.com; index index.html; location / { try_files $uri $uri/ =404; }}server { listen 192.168.1.101:80; server_name example.com; root /var/www/example.com/mirror; index index.html; location / { try_files $uri $uri/ =404; }}通配符与正则域名匹配子域名多且动态变化时,逐一配置 server_name 不现实。Nginx 提供通配符和正则两种高级匹配:通配符匹配——用 * 匹配子域名:server { listen 80; server_name *.example.com; location / { set $subdomain $host; if ($subdomain ~* ^(.*)\.example\.com$) { set $subdomain $1; } root /var/www/subdomains/$subdomain; index index.html; }}# 默认虚拟主机:兜底处理未匹配的请求server { listen 80 default_server; server_name _; root /var/www/default; return 444; # 直接关闭连接,比 404 更安全}default_server 是安全防线——任何未匹配到 server_name 的请求都会走这个块。建议返回 444(Nginx 特有状态码,直接关闭连接),比返回 404 更能防止信息泄露。正则表达式匹配——用命名捕获组提取子域名:server { listen 80; server_name ~^(?<subdomain>.+)\.example\.com$; root /var/www/example.com/$subdomain; index index.html; location / { try_files $uri $uri/ =404; }}server { listen 80; server_name ~^(?<user>.+)\.users\.example\.com$; root /var/www/users/$user; index index.html; location / { try_files $uri $uri/ =404; }}?<name> 是命名捕获组,提取的值以 $name 变量在配置中引用。通配符只能做简单的 * 匹配,正则则能处理复杂的域名模式——实际项目中正则用得更多。HTTPS 虚拟主机生产环境必须启用 HTTPS,标准做法是 HTTP 301 永久跳转到 HTTPS:# HTTP -> HTTPS 301 跳转server { listen 80; server_name example.com; return 301 https://$server_name$request_uri;}# HTTPS 服务server { listen 443 ssl http2; server_name example.com; ssl_certificate /etc/nginx/ssl/example.com.crt; ssl_certificate_key /etc/nginx/ssl/example.com.key; ssl_protocols TLSv1.2 TLSv1.3; ssl_ciphers HIGH:!aNULL:!MD5; ssl_session_cache shared:SSL:10m; ssl_session_timeout 10m; root /var/www/example.com; index index.html; location / { try_files $uri $uri/ =404; }}http2 参数直接在 listen 指令中启用 HTTP/2,无需额外编译模块。ssl_session_cache 将 SSL 会话缓存 10 分钟,客户端重连时可以复用,避免完整的 TLS 握手,显著提升 HTTPS 性能。SSL 协议只保留 TLSv1.2 和 TLSv1.3,禁用所有不安全的旧版本。反向代理虚拟主机虚拟主机在生产中最常见的用途是反向代理——不同域名转发到不同的后端服务,Nginx 作为网关统一入口:upstream backend1 { server 192.168.1.100:8080; server 192.168.1.101:8080;}upstream backend2 { server 192.168.1.200:8080; server 192.168.1.201:8080;}server { listen 80; server_name api.example.com; location / { proxy_pass http://backend1; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; }}server { listen 80; server_name admin.example.com; location / { proxy_pass http://backend2; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; }}proxy_set_header 三件套必须配置:Host 让后端知道原始域名,X-Real-IP 传递客户端真实 IP,X-Forwarded-For 追加代理链路。不加这些头,后端拿到的全是 Nginx 的内网 IP,日志和鉴权都会出问题。多域名共享配置多个域名配置相似、仅 root 路径不同时,用 map 指令消除重复:map $host $root_path { example.com /var/www/example.com; test.com /var/www/test.com; default /var/www/default;}server { listen 80; server_name example.com test.com; root $root_path; index index.html; location / { try_files $uri $uri/ =404; }}map 在请求处理阶段根据 $host 变量的值映射到对应目录,一个 server 块就能服务多个域名。当域名数量超过 5 个且配置差异仅在路径时,这种方式比逐个写 server 块更易维护。配置文件分离与站点管理生产环境中,千万不要把所有虚拟主机堆在 nginx.conf 一个文件里。用 include 拆分:# /etc/nginx/nginx.confhttp { include /etc/nginx/conf.d/*.conf; include /etc/nginx/sites-enabled/*;}每个虚拟主机一个配置文件,放在 sites-available 目录,通过符号链接启用:# 启用站点ln -s /etc/nginx/sites-available/example.com /etc/nginx/sites-enabled/# 禁用站点rm /etc/nginx/sites-enabled/example.com# 测试配置语法(修改后必做)nginx -t# 重新加载配置(不影响正在处理的请求)nginx -s reloadnginx -t 是生产操作的铁律——修改配置后先检测语法,通过后再 reload。reload 是平滑重载,Nginx 会等旧请求处理完再切换到新配置,不会中断服务。而 restart 会直接杀掉工作进程,正在处理的请求会断开。PHP 应用虚拟主机WordPress、Laravel 等 PHP 应用需要配置 FastCGI 传递给 PHP-FPM:server { listen 80; server_name example.com; root /var/www/example.com; index index.php index.html; location / { try_files $uri $uri/ /index.php?$query_string; } location ~ \.php$ { fastcgi_pass unix:/var/run/php/php8.0-fpm.sock; fastcgi_index index.php; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; include fastcgi_params; } location ~* \.(css|js|jpg|jpeg|png|gif|ico|svg|woff|woff2)$ { expires 1y; add_header Cache-Control "public, immutable"; }}try_files 中的 /index.php?$query_string 是 PHP 框架的标配写法——先尝试找静态文件,找不到就转发给 index.php 处理,这是 Laravel 等框架 URL 重写的基础。SCRIPT_FILENAME 必须用 $document_root 拼接,否则 PHP-FPM 找不到脚本文件。静态站点虚拟主机纯静态站点可以做更激进的优化——关日志、开压缩、长缓存:server { listen 80; server_name static.example.com; root /var/www/static; index index.html; gzip on; gzip_vary on; gzip_min_length 1024; gzip_types text/plain text/css text/xml text/javascript application/json application/javascript; location ~* \.(css|js|jpg|jpeg|png|gif|ico|svg|woff|woff2|ttf|eot)$ { expires 1y; add_header Cache-Control "public, immutable"; access_log off; } location / { try_files $uri $uri/ =404; }}静态资源关闭 access_log 能显著减少磁盘 IO。高流量站点中,日志写入是主要瓶颈之一,对不需要统计的静态资源关闭日志是常规优化手段。生产环境完整配置模板综合安全、性能和可维护性的完整配置,可作为新项目的起点:server { listen 80; server_name example.com www.example.com; return 301 https://$server_name$request_uri;}server { listen 443 ssl http2; server_name example.com www.example.com; # SSL ssl_certificate /etc/nginx/ssl/example.com.crt; ssl_certificate_key /etc/nginx/ssl/example.com.key; ssl_protocols TLSv1.2 TLSv1.3; ssl_ciphers HIGH:!aNULL:!MD5; ssl_session_cache shared:SSL:10m; ssl_session_timeout 10m; # 站点根目录 root /var/www/example.com; index index.php index.html; # 日志 access_log /var/log/nginx/example.com.access.log; error_log /var/log/nginx/example.com.error.log; # 安全头 add_header X-Frame-Options "SAMEORIGIN" always; add_header X-Content-Type-Options "nosniff" always; add_header X-XSS-Protection "1; mode=block" always; # Gzip 压缩 gzip on; gzip_vary on; gzip_min_length 1024; gzip_types text/plain text/css text/xml text/javascript application/json application/javascript; # 静态资源长缓存 location ~* \.(css|js|jpg|jpeg|png|gif|ico|svg|woff|woff2)$ { expires 1y; add_header Cache-Control "public, immutable"; access_log off; } # PHP-FPM location ~ \.php$ { fastcgi_pass unix:/var/run/php/php8.0-fpm.sock; fastcgi_index index.php; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; include fastcgi_params; } # 主路由 location / { try_files $uri $uri/ /index.php?$query_string; } # 禁止访问隐藏文件(.git、.env 等) location ~ /\. { deny all; access_log off; log_not_found off; }}配置要点总结三种类型的选用原则:基于域名是首选方案,一个 IP 托管所有站点,配置简单且用户无感知;基于端口适合内部服务隔离,但需要用户感知端口;基于 IP 在公网环境下已基本淘汰。server_name 匹配优先级(面试必考):精确匹配 > 通配符前缀(*.example.com)> 通配符后缀(example.*)> 正则表达式 > default_server。优先级相同的,按配置文件加载顺序决定。配置排错四步法:nginx -t 检查语法 -> nginx -s reload 确认重载 -> 检查 server_name 是否匹配请求域名 -> 查看 error.log 中的具体报错。大部分"配置不生效"的问题,要么是忘记 reload,要么是 server_name 写错了。
服务端阅读 02026年5月28日 07:26

Nginx 的 location 指令如何匹配?优先级是什么?

Nginx 的 location 指令如何匹配?优先级是什么?location 是 Nginx 中最核心的指令之一,它决定了一个请求由哪个配置块来处理。理解它的匹配规则和优先级,不仅是面试高频考点,更是排查 Nginx 配置问题的基本功。location 的四种匹配方式location 指令的语法:location [=|~|~*|^~] uri { ... }修饰符不同,匹配行为完全不同:| 修饰符 | 匹配方式 | 匹配后是否继续搜索 ||--------|---------|-------------------|| = | 精确匹配 | 否,立即停止 || ^~ | 前缀匹配 | 否,跳过正则检查 || ~ | 正则匹配(区分大小写) | 否,按配置顺序首个命中即停止 || ~* | 正则匹配(不区分大小写) | 否,按配置顺序首个命中即停止 || 无 | 前缀匹配 | 是,继续检查正则 |精确匹配(=)只有请求 URI 与指定路径完全一致时才命中,一旦匹配立即停止搜索,性能最优:location = / { # 仅匹配 /,不匹配 /index.html}前缀匹配(无修饰符)按 URI 前缀匹配,匹配成功后不会立即使用,而是先记住这个最长前缀匹配,继续检查正则表达式。如果正则没有命中,才会回退使用这个前缀匹配:location /docs/ { # 匹配以 /docs/ 开头的所有 URI # 但如果有正则也命中了,正则优先}正则匹配(~ 和 ~*)~:区分大小写~*:不区分大小写正则匹配按配置文件中出现的顺序依次检查,首个命中即停止:location ~ \.php$ { # 区分大小写,匹配 .php 结尾的请求}location ~* \.(jpg|png|gif|css|js)$ { # 不区分大小写,匹配常见静态资源}此外还有 !~ 和 !~*,表示正则不匹配,但它们不能用于 location 指令,只能用在 if 条件判断中。前缀匹配(^~)行为与无修饰符的前缀匹配类似,但关键区别是:如果 ^~ 前缀匹配成功,会跳过后续所有正则检查,直接使用该 location。这在对性能敏感的场景下非常有用:location ^~ /static/ { # 匹配 /static/ 开头的请求 # 即使有正则也匹配,也不检查,直接用这个}Nginx 完整匹配算法面试中光背优先级顺序不够,必须理解 Nginx 的完整匹配流程:Nginx 首先检查所有前缀匹配(包括 =、^~ 和无修饰符),找到最长前缀匹配如果最长前缀是精确匹配(=),立即使用,匹配结束如果最长前缀是 ^~ 匹配,立即使用,匹配结束按配置文件顺序依次检查所有正则表达式(~ 和 ~*)如果正则命中,使用该正则 location,匹配结束如果所有正则都未命中,回退使用步骤 1 中找到的最长前缀匹配这个流程说明一个关键点:正则匹配的优先级高于普通前缀匹配,但低于 = 和 ^~。优先级总结(从高到低)精确匹配 = — 最高优先级,匹配即停前缀匹配 ^~ — 匹配后跳过正则检查正则匹配 ~ / ~* — 按配置顺序,先到先得普通前缀匹配 — 优先级最低,作为兜底注意:正则匹配之间没有优先级之分,完全取决于配置文件中的书写顺序,写在前面的先匹配。配置示例与匹配结果server { listen 80; server_name example.com; location = / { return 200 "1: exact /"; } location / { return 200 "5: prefix /"; } location ^~ /images/ { return 200 "2: ^~ /images/"; } location ~ \.php$ { return 200 "3: regex .php"; } location ~* \.(jpg|png|gif)$ { return 200 "4: regex image"; } location /docs/ { return 200 "6: prefix /docs/"; }}匹配结果验证:| 请求 URI | 命中 location | 原因 ||----------|--------------|------|| / | = / | 精确匹配,优先级最高 || /images/logo.jpg | ^~ /images/ | ^~ 命中后跳过正则 || /api/test.php | ~ \.php$ | 前缀匹配 / 记住后,正则命中 || /photo.JPG | ~* \.(jpg\|png\|gif)$ | 不区分大小写正则命中 || /docs/readme.html | /docs/ | 最长前缀 /docs/ 优先于 /,且无正则命中 || /about | / | 仅前缀 / 命中,无正则匹配 |常见面试陷阱陷阱一:前缀匹配的长度优先多个普通前缀同时匹配时,Nginx 选择最长前缀,而非配置顺序:location /api/ { ... } # 前缀长度 5location /api/v1/ { ... } # 前缀长度 9,优先请求 /api/v1/users 会匹配 /api/v1/,因为前缀更长。陷阱二:正则覆盖普通前缀location /images/ { ... }location ~* \.(jpg|png)$ { ... }请求 /images/logo.jpg 会命中正则 ~*,而非前缀 /images/,因为正则优先级高于普通前缀。如果希望 /images/ 下的请求不被正则抢走,必须使用 ^~。陷阱三:= 只匹配精确路径location = /api { ... }它只匹配 /api,不匹配 /api/、/api/v1。如果需要同时匹配,应该用前缀匹配。实际配置建议高频路径用精确匹配 =,性能最优且语义清晰静态资源目录用 ^~,避免被正则拦截正则匹配控制数量,按命中频率从高到低排列避免在正则中使用复杂回溯表达式,防止 ReDoS 攻击通用兜底 location / 放在最后# 精确匹配首页location = / { proxy_pass http://frontend;}# 静态资源,跳过正则检查location ^~ /static/ { alias /var/www/static/; expires 30d;}# PHP 请求转发location ~ \.php$ { fastcgi_pass unix:/run/php/php-fpm.sock; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; include fastcgi_params;}# 管理后台,跳过正则检查location ^~ /admin/ { auth_basic "Restricted"; auth_basic_user_file /etc/nginx/.htpasswd; proxy_pass http://admin_backend;}# API 代理location /api/ { proxy_pass http://api_backend;}# 兜底location / { try_files $uri $uri/ /index.html;}追问:location 嵌套怎么用?location 支持嵌套,但只有普通前缀匹配可以嵌套在普通前缀内,正则和精确匹配不能嵌套:location /api/ { proxy_pass http://backend; location /api/internal/ { # 更具体的路径,覆盖外层配置 deny all; }}内层 location 会完全覆盖外层的处理逻辑,而不是继承。如果内层也需要 proxy_pass,必须显式重新声明。追问:location 和 rewrite 的执行顺序?Nginx 处理请求时,rewrite 阶段在 location 匹配之前执行(server 级别的 rewrite)。如果 rewrite 修改了 URI,location 会基于修改后的 URI 重新匹配。但 location 内部的 rewrite 可能触发重新匹配,需要注意避免循环重写。
服务端阅读 02026年5月28日 07:26

Nginx 如何实现缓存?缓存策略怎么配才能防击穿?

Nginx 如何实现缓存?如何配置缓存策略?Nginx 的缓存能力是后端服务性能优化的关键手段。面试中常从"Nginx 有哪几种缓存""proxycache 和 fastcgicache 怎么选""如何防止缓存击穿"这几个角度考察,理解原理比背配置更重要。Nginx 缓存的三大层次Nginx 缓存并不是单一机制,而是分布在请求链路的不同位置:浏览器缓存:通过响应头(Cache-Control、Expires)让客户端自行缓存,Nginx 只负责下发头信息代理缓存(proxy_cache):Nginx 作为反向代理时,缓存后端上游的响应,适用于反向代理场景FastCGI 缓存(fastcgi_cache):缓存 FastCGI 协议上游(如 PHP-FPM)的响应,适用于 PHP 直连场景面试时先说清楚这三层,再深入其中一层的配置细节,逻辑比直接贴配置更清晰。代理缓存配置详解http { proxy_cache_path /var/cache/nginx/proxy levels=1:2 keys_zone=proxy_cache:10m max_size=1g inactive=60m use_temp_path=off; server { listen 80; server_name example.com; location / { proxy_cache proxy_cache; proxy_cache_valid 200 302 10m; proxy_cache_valid 404 1m; proxy_cache_key "$scheme$request_method$host$request_uri"; proxy_cache_bypass $http_cache_control; add_header X-Cache-Status $upstream_cache_status; proxy_pass http://backend; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } }}核心参数解读| 参数 | 作用 | 注意点 ||---|---|---|| levels=1:2 | 缓存目录分两级,避免单目录文件过多 | 值越大层级越深,1:2 是常用配置 || keys_zone=proxy_cache:10m | 共享内存区域,存储缓存键和元数据 | 10m 约可存 8 万条键,按需调大 || max_size=1g | 缓存磁盘上限 | 超出后 Nginx 自动淘汰最久未访问的缓存 || inactive=60m | 60 分钟无访问则淘汰 | 与 proxy_cache_valid 无关,是另一条淘汰链 || use_temp_path=off | 临时文件写入缓存目录而非系统临时目录 | 减少跨磁盘拷贝,生产环境建议开启 || proxy_cache_valid 200 302 10m | 200/302 响应缓存 10 分钟 | 必须显式配置,否则不缓存 || proxy_cache_key | 缓存键的计算方式 | 默认含 scheme + method + host + uri,带参请求需考虑是否加入 $args |FastCGI 缓存配置FastCGI 缓存适用于 Nginx 直连 PHP-FPM 的场景,参数与 proxy_cache 对称:http { fastcgi_cache_path /var/cache/nginx/fastcgi levels=1:2 keys_zone=fastcgi_cache:10m max_size=1g inactive=60m; server { location ~ \.php$ { fastcgi_cache fastcgi_cache; fastcgi_cache_valid 200 60m; fastcgi_cache_methods GET HEAD; fastcgi_cache_bypass $skip_cache; fastcgi_no_cache $skip_cache; add_header X-Cache-Status $upstream_cache_status; fastcgi_pass unix:/var/run/php/php8.0-fpm.sock; include fastcgi_params; } }}proxycache 与 fastcgicache 怎么选?| 对比维度 | proxycache | fastcgicache ||---|---|---|| 适用场景 | 反向代理后端(任何 HTTP 服务) | 直连 PHP-FPM 等 FastCGI 进程 || 协议 | HTTP | FastCGI || 灵活性 | 更通用,后端不限语言 | 仅限 FastCGI 协议 || 生产推荐 | 微服务、多语言后端 | 纯 PHP 架构 |两者不能同时作用于同一 location。如果用了 proxy_pass 就用 proxycache,用了 fastcgi_pass 就用 fastcgicache。缓存风暴与缓存锁定这是面试高频追问点。当缓存过期瞬间,大量并发请求同时穿透到后端,这就是缓存风暴(Cache Stampede)。# 缓存锁定:只放一个请求去后端取数据,其余等待proxy_cache_lock on;proxy_cache_lock_timeout 5s;# 过期缓存兜底:后端异常时返回旧缓存proxy_cache_use_stale error timeout updating http_500 http_502 http_503 http_504;# 后台异步更新:命中过期缓存时异步刷新,不阻塞请求proxy_cache_background_update on;三者配合的执行逻辑:缓存过期 → proxy_cache_lock 放行一个请求 → 其余请求读 proxy_cache_use_stale 返回旧数据 → 新数据写入后所有请求命中。这就是完整的防击穿方案。动态缓存控制不是所有请求都该缓存。用 map 指令按条件跳过缓存:# 按 URI 跳过缓存map $request_uri $skip_cache { default 0; ~*/admin/ 1; ~*/api/ 1; ~*/user/ 1;}# 按后端响应头跳过缓存map $upstream_http_cache_control $skip_cache_by_header { ~*no-cache 1; ~*private 1; default 0;}# 组合条件map $skip_cache$skip_cache_by_header $combined_skip { default 0; ~1 1;}然后在 location 中使用:proxy_cache_bypass $combined_skip;proxy_no_cache $combined_skip;proxy_cache_bypass 和 proxy_no_cache 的区别:bypass 是跳过缓存直接请求后端但可能将响应写入缓存;no_cache 则完全不写缓存。生产环境通常两者配合使用,确保该跳过的请求既不读缓存也不写缓存。静态文件与浏览器缓存静态资源的缓存走另一套逻辑,不经过 proxy_cache,直接由 Nginx 返回文件并设置浏览器缓存头:location ~* \.(css|js|jpg|jpeg|png|gif|ico|svg|woff|woff2|ttf|eot)$ { expires 1y; add_header Cache-Control "public, immutable"; access_log off;}immutable 告诉浏览器:资源不会变,不需要发条件请求验证。搭配文件名加 hash(如 app.3a7b2c.js)效果最佳,更新时换文件名即可让浏览器重新请求。缓存清除方案Nginx 开源版不支持主动清除缓存,三种替代方案:自然过期:通过 proxy_cache_valid 设定 TTL,到期自动淘汰第三方模块 ngxcachepurge:支持按 URL 主动清除,需编译安装删除缓存文件:根据 proxy_cache_key 的 MD5 值定位文件路径并删除,rm -rf /var/cache/nginx/proxy 可全量清除生产环境中,最稳妥的方式是修改 proxy_cache_key 加入版本号参数,发布时更新版本号让旧缓存自然失效。缓存命中率监控通过 X-Cache-Status 响应头可观察缓存命中情况:add_header X-Cache-Status $upstream_cache_status;状态值含义:| 状态 | 含义 ||---|---|| MISS | 未命中,请求穿透到后端 || BYPASS | 命中跳过条件,不走缓存 || EXPIRED | 缓存已过期,需重新获取 || STALE | 后端异常,返回过期缓存 || UPDATING | 缓存正在更新,返回旧内容 || HIT | 命中缓存,直接返回 |监控思路:统计 HIT / (HIT + MISS + EXPIRED) 的比值,低于 80% 就需要调优缓存键或 TTL。缓存配置的常见踩坑1. 后端响应头导致缓存不生效后端返回 Cache-Control: no-cache 或 Set-Cookie 时,Nginx 默认不缓存。需要忽略这些头:proxy_ignore_headers X-Accel-Expires Expires Cache-Control Set-Cookie;2. 缓存键缺少关键参数默认 proxy_cache_key 不含 $args,但 API 请求 ?page=1 和 ?page=2 应该返回不同内容,需要加入查询参数:proxy_cache_key "$scheme$request_method$host$request_uri$is_args$args";3. 缓存最小请求次数导致首次不缓存proxy_cache_min_uses 2 表示请求出现 2 次才缓存,低流量接口可能永远不缓存。生产环境建议设为 1。4. keys_zone 过小导致缓存元数据丢失keys_zone 只存键和元数据,不存响应体。10m 约存 8 万条键,key 较长时需适当增大。生产环境完整配置参考http { proxy_cache_path /var/cache/nginx/proxy levels=1:2 keys_zone=proxy_cache:100m max_size=10g inactive=60m use_temp_path=off; fastcgi_cache_path /var/cache/nginx/fastcgi levels=1:2 keys_zone=fastcgi_cache:100m max_size=10g inactive=60m; map $request_uri $skip_cache { default 0; ~*/admin/ 1; ~*/api/ 1; ~*/user/ 1; } server { listen 80; server_name example.com; location ~* \.(css|js|jpg|jpeg|png|gif|ico|svg|woff|woff2)$ { expires 1y; add_header Cache-Control "public, immutable"; } location / { proxy_cache proxy_cache; proxy_cache_valid 200 302 10m; proxy_cache_valid 404 1m; proxy_cache_key "$scheme$request_method$host$request_uri"; proxy_cache_bypass $skip_cache; proxy_no_cache $skip_cache; proxy_cache_lock on; proxy_cache_lock_timeout 5s; proxy_cache_use_stale error timeout updating http_500 http_502 http_503 http_504; proxy_cache_background_update on; add_header X-Cache-Status $upstream_cache_status; proxy_pass http://backend; } location ~ \.php$ { fastcgi_cache fastcgi_cache; fastcgi_cache_valid 200 60m; fastcgi_cache_bypass $skip_cache; fastcgi_no_cache $skip_cache; add_header X-Cache-Status $upstream_cache_status; fastcgi_pass unix:/var/run/php/php8.0-fpm.sock; } }}
服务端阅读 02026年5月28日 07:24

Nginx 如何实现访问控制?有哪些访问控制方法?

Nginx 如何实现访问控制?有哪些访问控制方法?Nginx 的访问控制是后端面试高频考点,核心思路是"在反向代理层拦截非法请求,减轻后端压力"。主要方法有五种:IP 黑白名单、HTTP 基本认证、请求方法限制、基于请求头的鉴权、地理/时间条件控制,实战中往往组合使用。下面逐一拆解原理和配置要点。一、IP 黑白名单:最基础的网络层控制Nginx 通过 allow / deny 指令按 IP 或 CIDR 段做访问控制,规则从上到下依次匹配,命中即生效:location /admin { allow 192.168.1.0/24; # 内网放行 allow 10.0.0.0/8; # VPN 段放行 deny all; # 其余全部拒绝 proxy_pass http://backend;}注意事项:当客户端经过代理时,$remote_addr 拿到的是代理 IP 而非真实客户端 IP,需要配合 $http_x_forwarded_for 或 realip 模块获取真实地址白名单优先于黑名单是安全最佳实践——默认拒绝,显式放行二、HTTP 基本认证:用户名密码验证使用 auth_basic + auth_basic_user_file 实现,密码文件通过 htpasswd 工具生成:location /admin { auth_basic "Admin Area"; auth_basic_user_file /etc/nginx/.htpasswd; proxy_pass http://backend;}生成密码文件:htpasswd -c /etc/nginx/.htpasswd admin_user关键点: Basic Auth 的凭据是 Base64 编码而非加密,生产环境务必搭配 HTTPS 使用,否则密码可被中间人截获。三、请求方法限制:只允许特定 HTTP 方法用 limit_except 指令比 if ($request_method) 更规范,它在 location 级别做方法白名单:location /api { limit_except GET POST { deny all; # 只允许 GET 和 POST,其他方法返回 403 } proxy_pass http://api_backend;}与 if 写法的区别: limit_except 是 Nginx 官方推荐的方式,不会触发 "if is evil" 问题,且与 satisfy 指令配合更好。四、基于请求头的鉴权:API Key、Referer、User-AgentAPI Key 校验:map $http_x_api_key $api_valid { default 0; "sk_prod_abc123" 1; "sk_prod_def456" 1;}location /api { if ($api_valid = 0) { return 401; } proxy_pass http://api_backend;}用 map 比 if 直接比较更灵活,支持多 key 映射且可集中管理。防盗链(Referer 校验):location /images/ { valid_referers none blocked server_names *.example.com; if ($invalid_referer) { return 403; } root /var/www/images;}UA 过滤: 屏蔽恶意爬虫if ($http_user_agent ~* (bot|crawler|spider|scraper)) { return 403;}五、地理与时间条件控制地理位置限制(基于 geo 模块):geo $allowed_country { default no; CN yes; US yes;}server { location / { if ($allowed_country = no) { return 403; } proxy_pass http://backend; }}如需精确到城市级,可用 geoip 模块配合 MaxMind 数据库。时间条件限制:map $time_iso8601 $business_hours { default 0; ~^(\d{4}-\d{2}-\d{2}T(09|1[0-9]|2[0-1])) 1;}location /admin { if ($business_hours = 0) { return 403; } proxy_pass http://backend;}适合管理后台只在工作时段开放的场景。六、组合策略:satisfy 指令与多层防护satisfy any 表示满足任一条件即可访问,satisfy all 表示必须全部满足:location /admin { satisfy any; # IP 白名单 或 密码认证,满足其一即可 allow 192.168.1.0/24; deny all; auth_basic "Admin Area"; auth_basic_user_file /etc/nginx/.htpasswd; proxy_pass http://backend;}实战建议: 管理后台常用 satisfy any——内网 IP 免密码,外网需要认证;API 接口常用 satisfy all——IP + Key 双重验证。七、安全加固:敏感文件与目录防护# 禁止访问隐藏文件(如 .git、.env)location ~ /\. { deny all; access_log off; log_not_found off;}# 禁止访问敏感后缀文件location ~* \.(htaccess|htpasswd|ini|log|sh|sql|bak|swp)$ { deny all; access_log off;}# 禁止目录遍历autoindex off;这些规则应作为 Nginx 配置的基线安全策略,防止信息泄露。面试追问与核心要点Q:Nginx 访问控制的执行顺序是什么?allow/deny 按配置顺序从上到下匹配,先命中先生效。location 内的规则优先于 server 级别,server 级别优先于 http 级别。Q:satisfy any 和 satisfy all 的区别?any 是"或"逻辑——IP 白名单和认证满足其一即可;all 是"与"逻辑——两者都必须通过。默认是 all。Q:代理场景下 IP 限制为什么不生效?因为 $remote_addr 拿到的是上一层代理的 IP。解决方案:使用 ngx_http_realip_module 设置 set_real_ip_from 和 real_ip_header X-Forwarded-For 还原真实客户端 IP。Q:if 指令在 location 中有什么陷阱?Nginx 的 if 在 location 中属于 rewrite 阶段,可能导致非预期行为("if is evil")。能用 map、limit_except、allow/deny 替代的就避免用 if。
服务端阅读 02026年5月28日 07:23

Nginx 如何实现限流?有哪些限流策略?

Nginx 如何实现限流?有哪些限流策略?Nginx 限流的核心思路是控制单位时间内的请求量或并发连接数,防止后端服务被流量打垮。面试中这道题主要考察三个层面:你知道哪些限流模块、你理解底层算法吗、你在生产环境怎么用。限流的两种基本方式Nginx 提供两大限流模块:limit_req:限制请求速率,控制单位时间内允许的请求数limit_conn:限制并发连接数,控制同一时刻的 TCP 连接数两者的区别在于粒度——limit_req 关注的是请求频率(每秒多少个请求),limit_conn 关注的是连接数(同时存在多少个连接)。一个长连接可以承载多个请求,所以实际防护中通常两者配合使用。limit_req:请求速率限制http { limit_req_zone $binary_remote_addr zone=api_limit:10m rate=10r/s; server { location /api/ { limit_req zone=api_limit burst=20 nodelay; limit_req_status 429; proxy_pass http://backend; } }}关键参数解读:$binary_remote_addr:以客户端 IP 作为限流键,二进制格式比字符串格式节省内存,10MB 共享内存大约能记录 16 万个 IPzone=api_limit:10m:定义共享内存区域名称和大小rate=10r/s:每秒允许 10 个请求,也可以用 r/m 表示每分钟burst=20:允许 20 个突发请求排队等待nodelay:突发请求不延迟处理,超出 burst 容量则直接拒绝limit_req_status 429:被限流时返回 429 而非默认的 503burst 和 nodelay 到底怎么配合?这是面试的高频追问,很多人配置过但说不清楚原理。只用 limit_req zone=api_limit:严格按 rate 执行,超出的请求直接 503,体验差。加 burst=20:允许 20 个请求排队,Nginx 按 rate 速率逐个处理队列中的请求,多余请求延迟响应。好处是不误杀,坏处是用户感知延迟。再加 nodelay:队列中的请求立即处理,不延迟响应,但队列满了还是拒绝。实际效果是「短时间内允许突发,超出就拒绝」,适合大多数 API 场景。简单记:burst 控制能容忍多少突发,nodelay 决定突发请求是延迟还是立即处理。limit_conn:并发连接数限制http { limit_conn_zone $binary_remote_addr zone=conn_limit:10m; server { limit_conn conn_limit 10; proxy_pass http://backend; }}这里 limit_conn conn_limit 10 表示同一个 IP 最多同时保持 10 个连接。注意这和 limit_req 不同——limit_req 限制的是请求的到达速率,limit_conn 限制的是连接的并发数量。典型场景:防止单个客户端通过大量并发连接耗尽服务器资源(如慢速攻击 Slowloris)。底层算法:漏桶与令牌桶面试中问限流,必然会追问算法原理。漏桶算法(Leaky Bucket)请求像水一样倒入桶中,桶以固定速率漏水。如果桶满了,新请求被丢弃。特点是输出速率恒定,不管输入多猛烈,处理速度始终平稳。limit_req 不加 burst 参数时就是典型的漏桶行为——严格按 rate 处理,超出直接拒绝。令牌桶算法(Token Bucket)系统以固定速率往桶里放令牌,每个请求需要取走一个令牌。桶满了令牌不再增加。与漏桶的区别在于:令牌桶允许突发——桶里攒够了令牌时,可以一次性处理一批请求。limit_req 加上 burst 参数就实现了类似令牌桶的效果,允许一定程度的流量突发。核心区别:漏桶强制匀速输出,令牌桶允许有限突发。Nginx 的 limit_req 实际上是两者的结合——基础速率是漏桶,burst 提供了令牌桶式的突发能力。带宽限制除了请求和连接层面的限流,Nginx 还能限制响应传输速率:location /download/ { limit_rate 1m; limit_rate_after 10m; root /var/www/files;}limit_rate 1m:限速 1MB/slimit_rate_after 10m:前 10MB 不限速,之后才限速适用于大文件下载场景,防止少数大流量用户占满带宽。白名单与动态限流生产环境中通常需要对内部 IP 或特定请求方法豁免限流。基于 geo 的白名单:geo $limit_key { default $binary_remote_addr; 192.168.1.0/24 ""; 10.0.0.0/8 "";}limit_req_zone $limit_key zone=whitelist:10m rate=10r/s;白名单内的 IP 对应空字符串,不参与限流计算。基于请求方法的动态限流:map $request_method $limit_key { default $binary_remote_addr; GET ""; HEAD "";}GET 和 HEAD 请求不限流,其他方法(POST、PUT 等)参与限流,适合写操作需要更严格控制的场景。多层限流实战配置http { # 全局限流 limit_req_zone $binary_remote_addr zone=global:10m rate=50r/s; # API 接口限流 limit_req_zone $binary_remote_addr zone=api:10m rate=10r/s; # 登录接口限流 limit_req_zone $binary_remote_addr zone=login:10m rate=5r/m; # 连接数限制 limit_conn_zone $binary_remote_addr zone=conn:10m; limit_req_status 429; limit_conn_status 429; server { limit_conn conn 20; location / { limit_req zone=global burst=50 nodelay; proxy_pass http://backend; } location /api/ { limit_req zone=global burst=50 nodelay; limit_req zone=api burst=10 nodelay; proxy_pass http://api_backend; } location /login { limit_req zone=login burst=2 nodelay; proxy_pass http://auth_backend; } }}这套配置的思路是分层防护:全局兜底防 DDoS,API 层控制接口频率,登录接口单独严控防暴力破解。同一个 location 可以叠加多个 limit_req,任一规则触发都会拒绝请求。限流日志与监控log_format limit '$remote_addr - [$time_local] "$request" ' '$status limit=$limit_req_status';limit_req_log_level warn;$limit_req_status 变量记录限流状态,limit_req_log_level 控制限流日志级别。生产环境建议用 warn 级别,避免日志量过大。配合 ELK 或 Prometheus 可以做限流趋势分析和告警。生产环境的几个经验阈值不是拍脑袋定的——先压测后端服务的极限 QPS,限流值设在其 70%-80% 作为安全水位429 响应要友好——返回 JSON 格式的错误提示,带上 Retry-After 头告诉客户端多久后重试zone 内存别省——10MB 约存 16 万 IP,如果用户量大要相应调大,内存耗尽后新请求直接 503burst 要结合业务——API 类场景 burst 可以小一些(5-10),页面访问场景可以大一些(20-50)限流不是万能的——在 Nginx 层限流只能防住从外到内的流量冲击,内部服务间的调用保护需要 Sentinel 或熔断器关注误杀——公司出口 IP 共享场景下,单 IP 限流会误伤同一 NAT 后的多个用户,可考虑基于 token 或租户维度的限流键追问:limitreq 和 limitconn 该选哪个?都要用。limit_req 防高频请求冲击,limit_conn 防连接数耗尽,两者解决不同问题。面试中如果只答一个,会被认为理解不全面。追问:Nginx 限流有什么局限?单机维度的限流,分布式环境下需要 Redis + Lua 或专门的限流服务限流键有限,复杂业务逻辑(如按用户等级限流)需要结合 OpenResty 或网关层limit_req 基于共享内存,重启后状态丢失不支持滑动窗口计数,只有固定时间窗口的速率计算
服务端阅读 05月27日 22:13

Nginx 如何进行安全配置?有哪些安全最佳实践?

Nginx 如何进行安全配置?有哪些安全最佳实践?核心思路是:隐藏信息、限制访问、加密传输、防御攻击,四层递进。一、隐藏服务器指纹攻击者第一步是信息收集,版本号是最直接的突破口。server_tokens off;关掉之后,响应头和错误页不再暴露 Nginx 版本。面试追问:还能怎么隐藏? 可以用 more_set_headers 模块改掉 Server 字段本身,或者在前端反代层抹掉这个头。二、访问控制与限流IP 限制和速率限制是防暴力攻击的第一道门。# IP 白名单location /admin { allow 192.168.1.0/24; deny all;}# 限流limit_req_zone $binary_remote_addr zone=req:10m rate=10r/s;limit_req zone=req burst=20 nodelay;limit_conn_zone $binary_remote_addr zone=conn:10m;limit_conn conn 10;面试追问:burst 和 nodelay 区别是什么? burst 允许突发排队,nodelay 让排队的请求立即处理而不延时等待,否则超出的请求会被延迟处理。三、SSL/TLS 与安全头HTTPS 是底线配置,安全头加固浏览器端防护。ssl_protocols TLSv1.2 TLSv1.3;ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384;ssl_prefer_server_ciphers on;ssl_session_cache shared:SSL:50m;ssl_session_tickets off;add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;add_header X-Frame-Options "SAMEORIGIN" always;add_header X-Content-Type-Options "nosniff" always;add_header Content-Security-Policy "default-src 'self'" always;面试追问:为什么关 sslsessiontickets? 默认的 session ticket key 是明文保存在 worker 共享内存中的,多台 Nginx 之间无法复用,且存在前向安全性问题。集群部署时应手动轮换 key 或直接关闭。四、文件与目录防护禁止访问隐藏文件和敏感后缀,堵住信息泄露的口子。location ~ /\. { deny all; }location ~* \.(htaccess|ini|log|sql|bak|swp)$ { deny all; }autoindex off;五、超时与缓冲区防止慢速攻击和缓冲区溢出。client_body_timeout 10;client_header_timeout 10;keepalive_timeout 5;send_timeout 10;client_max_body_size 10m;client_header_buffer_size 1k;面试追问:如果只改一个配置,改哪个? 先关 server_tokens,零成本高风险。然后上 HTTPS,这是生产环境硬性要求。限流和访问控制按业务场景逐步加。常见误区用 if 正则匹配 SQL 注入或 XSS 关键词来拦截攻击,这是错误的。Nginx 的 if 在 location 中行为不稳定,且正则容易被编码绕过。防注入应交给 WAF(如 ModSecurity)或应用层参数校验,Nginx 只做流量层防护。
服务端阅读 05月27日 22:13

Nginx 性能调优需要关注哪些关键参数?

Nginx 性能调优需要关注哪些关键参数?Nginx 调优的核心思路是:减少不必要的系统调用、充分利用内核零拷贝能力、压缩传输体积、避免连接浪费。下面按影响程度从大到小逐项说明。Worker 进程与连接worker_processes 设为 auto,让 Nginx 自动匹配 CPU 核心数。worker_rlimit_nofile 调到 100000,避免文件描述符耗尽。每个 worker 的 worker_connections 可设 65535,理论最大并发 = worker 数 × 65535。multi_accept on 让 worker 一次性接收所有就绪连接,accept_mutex off 在高并发下减少锁争用。worker_processes auto;worker_rlimit_nofile 100000;events { worker_connections 65535; use epoll; multi_accept on; accept_mutex off;}零拷贝与传输优化sendfile on 跳过用户态拷贝,数据从内核直接到 socket。tcp_nopush on 让数据攒满一个包再发,配合 sendfile 减少系统调用次数。tcp_nodelay on 禁用 Nagle 算法,避免小包延迟。三者同时开启并不矛盾:tcpnopush 在 sendfile 阶段生效,最后一个包由 tcpnodelay 立即发出。sendfile on;tcp_nopush on;tcp_nodelay on;Gzip 压缩压缩能将文本响应体积缩减 60%-80%,直接降低带宽和传输耗时。gzip_comp_level 建议设 4-6,再高收益递减但 CPU 开销上升。gzip_min_length 设 1024,避免压缩小响应反而变大。别忘了通过 gzip_types 覆盖 JSON、SVG 等常见 MIME 类型。gzip on;gzip_vary on;gzip_min_length 1024;gzip_comp_level 5;gzip_types text/plain text/css application/json application/javascript application/xml image/svg+xml;连接复用与超时keepalive_timeout 控制客户端长连接保持时间,默认 75s,可视业务缩短到 30-60s。keepalive_requests 限制单连接最大请求数,防止连接泄漏。对上游服务器也要开长连接:upstream 块中 keepalive 32 保持 32 条空闲连接,减少反复握手开销。keepalive_timeout 60s;keepalive_requests 1000;upstream backend { server 10.0.0.1:8080; keepalive 32;}缓冲区与文件缓存client_body_buffer_size 和 client_header_buffer_size 按业务调整,过小会触发临时文件写入拖慢请求。open_file_cache 缓存频繁访问的文件描述符,减少磁盘 stat 调用,对静态资源场景效果显著。client_header_buffer_size 2k;client_body_buffer_size 128k;open_file_cache max=10000 inactive=20s;open_file_cache_valid 30s;open_file_cache_min_uses 2;日志降耗写日志是高并发下的隐性瓶颈。静态资源 access_log off 直接关掉;动态请求的日志加 buffer=32k flush=5s,写操作先进内存再批量落盘。追问:调了参数怎么验证效果?用 wrk 或 ab 做基准测试,对比调优前后的 QPS、P99 延迟和错误率。生产环境通过 stub_status 模块持续监控活跃连接数和请求处理量。记住一个原则:每次只改一个参数,观察效果后再改下一个,否则无法定位哪个改动真正有效。
服务端阅读 05月27日 22:13

Nginx 日志怎么配置?有哪些格式和优化方法?

Nginx 如何配置日志?有哪些日志格式和优化方法?Nginx 提供了访问日志(accesslog)和错误日志(errorlog)两种日志类型。面试中常考的是日志格式自定义、条件记录和性能优化三条主线。访问日志与自定义格式访问日志通过 log_format 定义格式,再用 access_log 引用。常用三种格式:main 格式:记录 IP、请求行、状态码、Referer、UA、耗时等基础信息detailed 格式:在 main 基础上增加 upstream 地址、状态、X-Forwarded-For、request_idJSON 格式:用 escape=json 转义,方便 ELK/Grafana Loki 等工具直接解析log_format main "$remote_addr - $remote_user [$time_local] " ""$request" $status $body_bytes_sent " ""$http_referer" "$http_user_agent" " "$request_time $upstream_response_time";log_format json_combined escape=json "{" ""time_local":"$time_local", ""remote_addr":"$remote_addr", ""request":"$request", ""status":"$status", ""request_time":"$request_time"" "}";access_log /var/log/nginx/access.log main;追问:accesslog 和 errorlog 有什么区别? accesslog 记录每次请求的详细信息,可自定义格式;errorlog 记录服务器错误,不支持自定义格式,只能设级别(debug/info/notice/warn/error/crit/alert/emerg)。日志性能优化高并发场景下日志写入会成为瓶颈,核心优化手段:关闭不必要的日志:静态资源、健康检查路径用 access_log off缓冲写入:access_log ... buffer=32k flush=5s,减少磁盘 I/O 次数gzip 压缩:access_log ... gzip=9,节省磁盘空间条件记录:用 map + if= 只记录特定状态码或慢请求map $status $loggable { ~^[23] 0; default 1;}access_log /var/log/nginx/access.log main if=$loggable;location ~* \.(css|js|jpg|png|gif|ico)$ { access_log off;}access_log /var/log/nginx/access.log main buffer=32k flush=5s;追问:buffer 和 flush 参数分别控制什么? buffer 控制缓冲区大小,写满才刷盘;flush 控制最大等待时间,超时强制刷盘。两者配合保证日志既不丢又不多写。日志轮转与分离单文件日志会无限增长,必须配合 logrotate 轮转:# /etc/logrotate.d/nginx/var/log/nginx/*.log { daily rotate 14 compress delaycompress missingok sharedscripts postrotate [ -f /var/run/nginx.pid ] && kill -USR1 $(cat /var/run/nginx.pid) endscript}kill -USR1 让 Nginx 重新打开日志文件,不会中断服务。按业务分离日志同样重要:API 请求写独立文件,不同 server 块各写各的,便于精准排查问题。常用变量速查| 变量 | 说明 ||------|------|| $remoteaddr | 客户端 IP || $status | 响应状态码 || $requesttime | 请求总耗时 || $upstreamresponsetime | 上游响应时间 || $httpxforwarded_for | 真实客户端 IP |生产环境建议用 JSON 格式 + 缓冲写入 + logrotate 轮转 + 条件过滤,四板斧组合基本够用。
服务端阅读 05月27日 22:12

Nginx 监控运维怎么做?stub_status、Prometheus、ELK 怎么选?

答案前置:Nginx 监控运维的核心思路Nginx 监控围绕三条线展开:指标采集(stub_status / 日志)→ 存储与展示(Prometheus+Grafana / ELK / Zabbix)→ 告警与响应(阈值告警 + 自动化脚本)。面试中常考的不是你背了多少工具名,而是能不能说清楚每一步为什么这么做、不同规模场景怎么选型。内置指标:stub_status 能拿到什么?stub_status 是 Nginx 自带的状态模块,开启后在指定路径暴露一组关键指标:location /nginx_status { stub_status on; access_log off; allow 127.0.0.1; deny all;}访问后返回:Active connections(当前活跃连接数)、accepts/handled/requests(累计连接与请求数)、Reading/Writing/Waiting(读请求头、写响应、空闲等待的连接数)。追问:Waiting 数量大说明什么? 如果 Active connections ≈ Waiting,说明大量连接是 keep-alive 空闲态,连接复用率高是好事;但如果同时 Writing 偏低而请求排队,就要检查上游响应是否过慢。日志监控:比 stub_status 更细的粒度stubstatus 只有粗粒度指标,真正定位问题靠日志。关键是自定义 logformat 加入上游响应时间:log_format detailed '$remote_addr - [$time_local] "$request" $status ' 'rt=$request_time uct=$upstream_connect_time ' 'uht=$upstream_header_time urt=$upstream_response_time';access_log /var/log/nginx/detailed.log detailed;request_time 是总耗时,upstream_response_time 是上游处理耗时,两者差值就是 Nginx 自身开销。如果差值大,排查 Nginx 层面的缓冲、压缩或 DNS 解析。对于 ELK 场景,直接输出 JSON 格式日志省掉 Logstash 的 grok 解析:log_format json_log escape=json '{"time":"$time_local","ip":"$remote_addr",' '"status":$status,"rt":$request_time,"urt":"$upstream_response_time"}';access_log /var/log/nginx/json.log json_log;Prometheus + Grafana:云原生场景首选nginx-prometheus-exporter 把 stub_status 的指标转为 Prometheus 格式,Grafana 做可视化:# prometheus.ymlscrape_configs: - job_name: 'nginx' static_configs: - targets: ['localhost:9113']选型逻辑: 容器化/K8s 环境下 Prometheus 是事实标准,开箱即用 Service Discovery,Grafana 社区模板丰富。Nginx Plus 用户还可以用官方 nginx-plus-exporter 拿到更细的 upstream 指标。ELK Stack:日志深度分析场景当需求不只是看指标曲线,而是要按 IP、URL、状态码做聚合分析和历史追溯,ELK 更合适。Filebeat 采集 → Logstash 清洗 → Elasticsearch 存储 → Kibana 可视化,链路长但灵活度高。选型对比: Prometheus 适合指标型监控(数字曲线),ELK 适合日志型分析(文本检索+聚合)。小团队二选一推荐 Prometheus,监控告警闭环更短。Zabbix:传统企业环境的选择Zabbix 通过 Agent 调用 stub_status 页面,用正则提取指标配置监控项。适合已有 Zabbix 基建的企业,不推荐新项目为 Nginx 单独搭 Zabbix。告警:监控闭环的关键有监控没告警等于没监控。核心告警规则:5xx 比率超阈值(如 > 1%)触发 criticalActive connections 突增超过 2 倍标准差触发 warningupstreamresponsetime P99 > 2s 触发 warningPrometheus 用 Alertmanager 配路由和静默,ELK 用 Watcher 或 ElastAlert,Zabbix 自带触发器机制。运维常用命令速查nginx -t # 测试配置语法nginx -s reload # 平滑重载,不中断连接nginx -s quit # 优雅停止,处理完当前请求后退出nginx -s reopen # 重新打开日志文件(配合 logrotate)日志轮转用 logrotate,配置 postrotate 里发 USR1 信号让 Nginx 重新打开文件句柄,避免写入已轮转的老文件。面试追问方向stub_status 的 Waiting 和 keep-alive 是什么关系? Waiting 连接就是 keep-alive 空闲连接,keepalive_timeout 控制超时回收。Nginx 502 怎么排查? 先查 upstream 是否存活,再看 Nginx error log 里 connect() failed 的具体原因,最后检查 proxy_read_timeout 配置。如何不重启更新配置? nginx -t && nginx -s reload,reload 会 fork 新 worker 加载新配置,老 worker 处理完手头请求后退出。Prometheus 和 ELK 怎么选? 指标监控选 Prometheus(轻量、告警闭环好),日志分析选 ELK(全文检索、聚合灵活),大团队通常两套都搭。
服务端阅读 05月27日 22:12

Nginx 如何配置 WebSocket 代理?

Nginx 如何配置 WebSocket 代理?WebSocket 建立在 HTTP/1.1 之上,通过 Upgrade 机制将 HTTP 连接升级为全双工长连接。Nginx 默认会清除 Upgrade 头,所以必须手动配置才能正确代理 WebSocket。核心配置(三行必写)location /ws { proxy_pass http://backend; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade";}proxy_http_version 1.1:WebSocket 要求 HTTP/1.1,Nginx 默认用 1.0Upgrade $http_upgrade:转发客户端的协议升级请求Connection "upgrade":告知 Nginx 这不是普通 HTTP,需要保持升级状态推荐用 map 管理 Connection 头多 location 场景下,硬编码 "upgrade" 会导致非 WebSocket 请求也被标记。用 map 按需切换更安全:map $http_upgrade $connection_upgrade { default upgrade; '' close;}server { location /ws { proxy_pass http://backend; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection $connection_upgrade; }}没有 Upgrade 头时走 close,普通请求不受影响。超时:为什么连着连着就断了?Nginx 的 proxy_read_timeout 默认 60 秒。WebSocket 是长连接,如果 60 秒内没有数据传输,Nginx 会主动断开。解决办法:proxy_read_timeout 3600s; # 1小时proxy_send_timeout 3600s;proxy_connect_timeout 60s; # 建连超时保持短即可按业务调,不是越大越好。过长超时意味着僵死连接不会被回收。WSS(WebSocket over TLS)在 SSL server 块里照加那三行即可,Nginx 负责 TLS 卸载,后端仍用 ws://:server { listen 443 ssl; ssl_certificate /etc/nginx/ssl/cert.pem; ssl_certificate_key /etc/nginx/ssl/key.pem; location /ws { proxy_pass http://backend; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection $connection_upgrade; }}负载均衡要注意会话保持WebSocket 是有状态长连接,轮询策略会导致重连时切到不同后端。必须用 ip_hash:upstream ws_backend { ip_hash; server 10.0.0.1:8080; server 10.0.0.2:8080;}如果后端是无状态的(如用 Redis Pub/Sub 做消息同步),也可以用轮询。追问:连接断开怎么排查?查 Nginx error log,确认是否超时断开检查 Upgrade / Connection 头是否正确转发确认 proxy_buffering off 已设置,避免数据被缓冲检查防火墙或 CDN 是否拦截长连接用 curl -H "Upgrade: websocket" 手动测试握手是否返回 101