如何将Nginx响应时间从500ms降至50ms

500ms 的响应时间对用户来说已经是明显可感知的延迟------页面加载转圈、按钮点击后要等半秒才有反馈、API 超时风险随时触发。如果是核心接口,这个延迟会直接影响用户体验和业务转化率。

但 500ms 到 50ms 的优化不是靠运气,不是靠玄学,是靠一层层排查、一项项配置调出来的结果。Nginx 作为反向代理和前端服务器,是大多数 Web 服务的入口,它对响应时间的影响是全链路的:从连接建立、请求转发、后端响应、内容压缩、静态资源缓存到 keepalive 复用,每一层都有优化空间。

本文用一个真实的优化案例,从问题定位→分层排查→配置调优→验证复盘的完整闭环出发,展示如何将 Nginx 响应时间从 500ms 压到 50ms。每一步都有可执行的命令和可测量的结果。

前提说明:本文的优化路径基于真实场景,但 Nginx 版本差异、操作系统配置、后端业务特性都会影响优化效果。有些参数不是越大越好,也不是所有优化都适用于所有场景。优化前先做基线测量,优化后对比效果,有效果则保留,无效果则回滚。


第一阶段:建立基线------先测量,再优化

问题背景

运维团队收到反馈:用户访问商品详情页感觉很慢,开发排查后怀疑是 Nginx 这一层的问题,丢给运维处理。这是一个典型的"页面加载慢"的投诉,500ms 响应时间是 SLA 告警阈值。

现象:用户点击商品详情页,浏览器转圈约 0.5 秒才能看到内容。

排查前的准备工作

第一步:确认测试环境

不要直接在生产机做测试。先用测试环境或 staging 环境做验证,确认优化有效后再灰度到生产。

复制代码
# 确认 Nginx 版本(不同版本配置项有差异)
nginx -v
# nginx version: nginx/1.24.0

# 确认 Nginx 编译参数(了解已加载的模块)
nginx -V
# 重点关注:--with-http_gzip_static_module、--with-http_ssl_module、
# --with-http_stub_status_module、--with-http_v2_module

# 确认操作系统版本
cat /etc/os-release
# NAME="Ubuntu 22.04.3 LTS"
# VERSION_ID="22.04"

# 确认 CPU 核心数(影响 worker 进程数配置)
nproc
# 8

# 确认内存大小(影响缓存和连接数配置)
free -h
# Mem: 31Gi

第二步:找到慢请求的日志

Nginx 的 access log 默认记录了每个请求的响应时间:

复制代码
# 查看 access log 格式(确认响应时间字段)
grep -E "log_format|access_log" /etc/nginx/nginx.conf

# access_log 示例格式:
# log_format main '$remote_addr - $remote_user [$time_local] "$request" '
#                 '$status $body_bytes_sent "$http_referer" '
#                 '"$http_user_agent" "$http_x_forwarded_for" '
#                 'rt=$request_time uct="$upstream_connect_time" '
#                 'uht="$upstream_header_time" urt="$upstream_response_time"';

# rt 是总响应时间(request_time)
# uct 是与后端建立连接的时间
# uht 是后端返回 header 的时间
# urt 是后端返回完整响应的时间

找到 rt(总响应时间)超过 500ms 的请求:

复制代码
# 提取 access log 中响应时间超过 500ms 的请求
awk '{if ($NF > 0.5) print $0}' /var/log/nginx/access.log | tail -50

# 统计响应时间分布
awk '{print $NF}' /var/log/nginx/access.log | \
  awk -F. '{print $1}' | sort | uniq -c | sort -rn | head -20
# 输出示例:
# 8500 0    (0ms以下)
# 1200  0   (0-0.1s)
# 400   1   (1s)
# 150   2   (2s)
# 80    5   (5s+)

第三步:用 curl 模拟请求,获取详细时间分解

复制代码
# -w 参数输出各阶段耗时,-o /dev/null 不输出响应体
curl -o /dev/null -s -w "\
  DNS Lookup: %{time_namelookup}s\n\
  TCP Connect: %{time_connect}s\n\
  SSL Handshake: %{time_appconnect}s\n\
  TTFB (First Byte): %{time_starttransfer}s\n\
  Total Time: %{time_total}s\n\
  " https://api.example.com/product/detail?id=12345

# 输出示例:
# DNS Lookup: 0.005s
# TCP Connect: 0.012s
# SSL Handshake: 0.045s
# TTFB (First Byte): 0.487s    ← 主要时间消耗在这里
# Total Time: 0.512s

关键发现:TTFB(Time To First Byte,首字节时间)高达 487ms,占总时间的 95%。这说明瓶颈在后端响应或 Nginx 与后端之间的网络延迟,而不是 Nginx 本身。

第四步:区分瓶颈在哪一层

复制代码
# 测试 Nginx 本身处理静态文件的速度(排除后端影响)
time curl -o /dev/null -s http://127.0.0.1/static/product.js

# 输出:直接返回静态文件,总时间应该在 5ms 以内
# 如果超过 10ms,说明 Nginx 配置有问题或磁盘 I/O 是瓶颈

# 测试本地回环网络延迟(排除公网影响)
ping 127.0.0.1
# 正常应该在 0.01ms 以内

# 测试后端服务响应时间(直接 curl 后端,不走 Nginx)
time curl -o /dev/null -s http://127.0.0.1:8080/api/product/detail?id=12345
# 如果直接调后端只需要 50ms,说明问题在 Nginx 层
# 如果直接调后端也需要 400ms,说明问题在后端服务本身

基线测量总结

指标 测量值 目标值
Nginx 总响应时间 512ms <50ms
TTFB(首字节时间) 487ms <30ms
Nginx 处理静态文件 3ms <5ms
后端 API 响应时间 485ms <30ms

结论:瓶颈在后端 API 响应时间,Nginx 作为反向代理需要从减少转发延迟、启用缓存、优化连接复用等方面入手。同时后端服务本身也需要优化。


第二阶段:Nginx 层优化------连接、缓存、压缩

优化1:启用 upstream keepalive,减少连接建立开销

Nginx 与后端之间每次请求都新建 TCP 连接,这个过程在 Linux 内核里需要三次握手,Nginx 层面还需要 upstream connect time。如果后端在异地机房,一次连接建立可能就是几十毫秒。

问题现象$upstream_connect_time 居高不下

复制代码
# 查看当前 upstream 连接时间分布
awk -F'"' '{print $NF}' /var/log/nginx/access.log | \
  awk '{print $NF}' | awk -F. '{print $1}' | sort | uniq -c | sort -rn | head -10

优化前配置/etc/nginx/conf.d/upstream.conf):

复制代码
upstream backend {
    server 127.0.0.1:8080;
    server 127.0.0.1:8081;
}

优化后配置

复制代码
upstream backend {
    server 127.0.0.1:8080;
    server 127.0.0.1:8081;

    keepalive 32;          # 保持的空闲连接数
    keepalive_requests 1000;  # 每个连接最多处理多少请求后关闭
    keepalive_timeout 60s;    # 空闲连接保持多久
}

然后在对应的 server 块里配置 proxy_http_version 1.1proxy_set_header Connection ""

复制代码
server {
    listen 443 ssl http2;
    server_name api.example.com;

    location /api/ {
        proxy_pass http://backend;
        proxy_http_version 1.1;                          # 启用 HTTP/1.1
        proxy_set_header Connection "";                    # 清空 Connection 头,开启 keepalive
        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_connect_timeout 5s;
        proxy_send_timeout 30s;
        proxy_read_timeout 30s;

        # Buffer 配置(减少后端压力)
        proxy_buffering on;
        proxy_buffer_size 4k;
        proxy_buffers 8 16k;
        proxy_busy_buffers_size 32k;
    }
}

验证方式

复制代码
# 重新加载 Nginx 配置
sudo nginx -t && sudo nginx -s reload

# 再次用 curl 测量
curl -o /dev/null -s -w "\
  Upstream Connect: %{time_connect}s\n\
  TTFB: %{time_starttransfer}s\n\
  Total: %{time_total}s\n\
  " https://api.example.com/product/detail?id=12345

# 对比优化前后的 upstream_connect_time
# 预期:从 10-30ms 降低到 1-5ms

效果预估:减少连接建立开销,响应时间可降低 20-50ms。

优化2:开启 gzip 压缩,减少传输体积

传输体积大是 TTFB 高的另一个原因------即使后端响应快,内容从后端传到 Nginx、从 Nginx 传到用户,中间传输耗时跟体积成正比。

复制代码
http {
    gzip on;
    gzip_vary on;                    # 对代理请求也启用压缩
    gzip_proxied any;                # 对代理请求也启用压缩
    gzip_comp_level 4;                # 压缩级别 1-9,默认 4,平衡速度与压缩率
    gzip_min_length 256;             # 小于 256 字节不压缩(压缩头部开销不划算)
    gzip_types
        text/plain
        text/css
        text/xml
        text/javascript
        application/json
        application/javascript
        application/xml
        application/xml+rss
        application/x-javascript
        image/svg+xml;
    gzip_disable "msie6";            # 禁用 IE6 的 gzip(现代浏览器已不需要)
}

验证方式

复制代码
# 测试是否有 gzip 压缩(看响应头)
curl -I -H "Accept-Encoding: gzip" https://api.example.com/product/detail?id=12345

# 输出中应该有:
# Content-Encoding: gzip
# Content-Length: 1234   ← 压缩后体积

# 对比压缩前后的 Content-Length
curl -I https://api.example.com/product/detail?id=12345
# Content-Length: 5678   ← 原始体积

效果预估:JSON 响应压缩率通常在 60-80%,传输时间可降低 30-60%。

优化3:启用 proxy_cache,将频繁访问的响应缓存住

后端响应慢,但相同请求的响应内容可能是一样的(比如商品详情、分类列表)。把这些缓存到 Nginx 本地,后续相同请求直接返回缓存,不用每次都打到后端。

复制代码
http {
    # 定义缓存路径和参数
    proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=api_cache:10m
                     max_size=1g inactive=60m use_temp_path=off;

    # 缓存响应码 200 和 304,保留 10 分钟
    proxy_cache_valid 200 304 10m;
    proxy_cache_valid 404 1m;    # 404 也缓存 1 分钟,避免频繁回源

    # 缓存 Key:按 host + uri + query string
    proxy_cache_key "$scheme$request_method$host$request_uri";

    # 响应头添加缓存状态(方便调试)
    add_header X-Cache-Status $upstream_cache_status;
}

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

        # 启用缓存(默认 off)
        proxy_cache api_cache;

        # 遇到这些请求头时,不走缓存(强制回源)
        proxy_cache_bypass $cookie_nocache $arg_nocache $arg_comment;

        # 同样的,多久算旧缓存需要重新验证
        proxy_cache_revalidate on;

        # 允许在后台更新缓存(避免缓存过期瞬间大量请求打到后端)
        proxy_cache_background_update on;
        proxy_cache_lock on;

        # 缓存 Key 其他部分保持不变...
        proxy_http_version 1.1;
        proxy_set_header Connection "";
        proxy_set_header Host $host;
    }
}

验证方式

复制代码
# 第一次请求(MISS,表示缓存未命中,回源)
curl -I https://api.example.com/product/detail?id=12345
# X-Cache-Status: MISS

# 第二次请求(HIT,表示命中缓存)
curl -I https://api.example.com/product/detail?id=12345
# X-Cache-Status: HIT

# 等 10 分钟后再次请求(EXPIRED,缓存过期)
curl -I https://api.example.com/product/detail?id=12345
# X-Cache-Status: EXPIRED

# 查看缓存目录大小
du -sh /var/cache/nginx

# 查看缓存命中率
awk '{print $NF}' /var/log/nginx/access.log | \
  grep -E "HIT|MISS|EXPIRED" | sort | uniq -c

效果预估:缓存命中时,Nginx 直接返回缓存,响应时间可从 500ms 降到 5-10ms。

优化4:调整 worker 进程数和连接数上限

Nginx 默认的 worker 进程数可能不是最优的。在多核服务器上,如果 worker 数太少,无法充分利用 CPU;如果 worker 数太多,上下文切换开销又会成为瓶颈。

复制代码
# 当前 Nginx worker 进程数配置
grep -E "worker_processes|worker_connections" /etc/nginx/nginx.conf

优化配置

复制代码
# /etc/nginx/nginx.conf
worker_processes auto;    # 自动等于 CPU 核心数(推荐),也可以手动指定数字

# 每个 worker 进程允许的最大连接数(不要超过 1024 的太多倍)
worker_rlimit_nofile 65535;

events {
    worker_connections 4096;   # 单个 worker 最大连接数
    multi_accept on;            # 一次接受多个新连接(提高并发能力)
    use epoll;                  # Linux 下使用 epoll(高效事件驱动模型)
}

同时修改系统的文件描述符上限:

复制代码
# 查看当前 limit
ulimit -n

# 临时生效(当前 session)
ulimit -n 65535

# 永久生效(/etc/security/limits.conf)
# 添加:
# * soft nofile 65535
# * hard nofile 65535

验证方式

复制代码
# 确认 worker 进程数等于 CPU 核心数
ps aux | grep nginx | grep worker
# 应该看到 8 个 worker 进程(假设 8 核)

# 确认 worker_connections 生效(需要 reload 后)
sudo nginx -t && sudo nginx -s reload

# 用 ab 或 wrk 做压力测试,观察延迟和吞吐量
# ab(Apache Benchmark)
ab -n 1000 -c 100 https://api.example.com/product/detail?id=12345

# wrk(更现代的压测工具)
wrk -t4 -c100 -d30s https://api.example.com/product/detail?id=12345

优化5:开启 HTTP/2,减少请求排队

HTTP/1.1 下,浏览器对同一个域名的并发请求数有限制(通常 6-8 个),超过的部分需要排队。HTTP/2 的多路复用(Multiplexing)允许在同一个 TCP 连接上并行传输多个请求,彻底消除队头阻塞。

复制代码
server {
    listen 443 ssl http2;    # 加上 http2 参数即可启用 HTTP/2
    ssl_certificate /etc/nginx/ssl/example.com.crt;
    ssl_certificate_key /etc/nginx/ssl/example.com.key;

    # HTTP/2 相关优化
    http2_idle_timeout 60s;
    http2_max_concurrent_streams 128;   # 单连接最大并发流数
}

验证方式

复制代码
# 用 curl 验证是否启用 HTTP/2
curl -I --http2 https://api.example.com/
# 输出应该包含:
# HTTP/2 200

# 如果返回 HTTP/1.1,说明 HTTP/2 没有启用

# 用 Chrome DevTools 查看请求的协议版本
# Network 面板 → Protocol 列会显示 h2(HTTP/2)

第三阶段:后端服务优化------不只是 Nginx 的事

优化6:后端连接池大小调优

即使 Nginx 这边用了 keepalive,如果后端服务的连接池配置不合理,还是会瓶颈。Java 服务的连接池(HikariCP、Druid)、Python 的 DBPool、Go 的 sql.DB------每个后端服务的连接池都有最大连接数限制。

复制代码
# 查看后端服务的连接池状态(假设后端是 Java,用 JDBC)
# 通过 JMX 或 actuator 端点查看活跃连接数
curl -s http://127.0.0.1:8080/actuator/metrics/hikaricp.connections.active

# 如果活跃连接数接近最大连接数,说明连接池是瓶颈
# 需要增加最大连接数或优化慢查询

# 查看 MySQL 的连接数
mysql -e "SHOW STATUS LIKE 'Threads_connected'; SHOW VARIABLES LIKE 'max_connections';"

优化7:后端启用 Redis 缓存,减少数据库查询

商品详情的响应如果有 400ms,其中可能有 300ms 花在数据库查询上。后端缓存(Redis/Memcached)是最有效的优化手段。

复制代码
# 查看慢查询日志(MySQL)
mysql -e "SET GLOBAL slow_query_log = 'ON'; SET GLOBAL long_query_time = 0.5;"
mysql -e "SHOW VARIABLES LIKE 'slow_query_log%';"
tail -f /var/log/mysql/slow-query.log

# 典型的慢查询:缺少索引、SELECT *、大结果集
# 例如:
# SELECT * FROM products WHERE id = 12345;   ← 没有索引
# SELECT * FROM products WHERE category_id IN (1,2,3,4,5);  ← 大结果集

第四阶段:系统层优化------别让 OS 成为瓶颈

优化8:调整内核参数,减少网络延迟

Linux 内核的网络参数默认值是为通用场景设计的,在高并发短连接场景下,这些默认值可能导致性能问题。

复制代码
# /etc/sysctl.conf 添加以下配置

# 减少 TIME_WAIT 状态的连接占用(高并发短连接场景)
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_fin_timeout = 15

# 增加本地端口范围(高并发场景避免端口耗尽)
net.ipv4.ip_local_port_range = 32768 60999

# 增加最大文件描述符(每个连接占用一个 fd)
fs.file-max = 2097152

# 增大 TCP 缓冲区(提升大文件传输和长肥管道性能)
net.core.rmem_max = 16777216
net.core.wmem_max = 16777216
net.ipv4.tcp_rmem = 4096 87380 16777216
net.ipv4.tcp_wmem = 4096 65536 16777216

# 启用 TCP 快速打开(减少三次握手延迟)
net.ipv4.tcp_fastopen = 3

# 启用 SYN Cookie(防止 SYN Flood 攻击同时不影响正常连接)
net.ipv4.tcp_syncookies = 1

# 应用变更
sudo sysctl -p

验证方式

复制代码
# 确认参数生效
sysctl net.ipv4.tcp_tw_reuse
sysctl net.ipv4.ip_local_port_range

# 用 curl 测量 TCP Fast Open 效果(需要服务器和客户端都支持)
curl -o /dev/null -s -w "TCP Fast Open: %{time_connect}s\n" \
  https://api.example.com/product/detail?id=12345

优化9:网卡多队列和 RSS,提升网络吞吐

如果服务器网卡支持多队列(RSS),可以让网卡中断分散到多个 CPU 核心处理,减少单核瓶颈。

复制代码
# 查看网卡队列数
cat /proc/interrupts | grep eth0 | wc -l

# 查看每个队列的中断分布
cat /proc/interrupts | grep eth0

# 开启网卡多队列(需要 ethtool)
ethtool -l eth0
# 输出示例:
# Channel parameters for eth0:
# Pre-set maximums:
# RX:  8
# TX:  8
# Current hardware settings:
# RX:  1   ← 只有 1 个队列,没有充分利用
# TX:  1

# 开启 8 队列
ethtool -L eth0 combined 8

第五阶段:全链路压测与验证

灰度方案

不要一次性全量上线优化,先灰度到 10% 的流量,观察错误率和延迟变化。

复制代码
# 灰度配置示例:按 IP 段灰度
geo $backend_pool {
    default         backend;
    10.0.0.0/8     backend_optimized;   # 内部网络走优化后的后端
}

upstream backend {
    server 127.0.0.1:8080;
}
upstream backend_optimized {
    server 127.0.0.1:8081;  # 优化后的后端实例
}

监控指标

优化上线后,持续监控以下指标:

复制代码
# 1. Nginx 响应时间分布(P50/P90/P99)
awk '{print $NF}' /var/log/nginx/access.log | \
  awk '{sum+=$1; arr[NR]=$1} END {
    asort(arr);
    print "P50:" arr[int(NR*0.5)];
    print "P90:" arr[int(NR*0.9)];
    print "P99:" arr[int(NR*0.99)];
  }'

# 2. Nginx 缓存命中率
awk '/api\// {print $NF}' /var/log/nginx/access.log | \
  grep -E "HIT|MISS" | sort | uniq -c

# 3. upstream_connect_time 分布
awk -F'"''{print $(NF-3)}' /var/log/nginx/access.log | \
  awk -F'= ''{print $2}' | awk -F. '{print $1}' | \
  sort | uniq -c | sort -rn | head -10

# 4. 错误率(5xx)
awk '{if ($9 >= 500) print}' /var/log/nginx/access.log | wc -l

最终效果对比

指标 优化前 优化后 降低幅度
TTFB(首字节时间) 487ms 32ms -93%
Total Response Time 512ms 48ms -91%
upstream_connect_time 25ms 2ms -92%
缓存命中率(热门商品) 0% 95%+ -
gzip 压缩率 - 72% -
P99 响应时间 2000ms 85ms -96%

补充一:Upstream 健康检查与故障转移

问题背景

如果后端有多个实例(多台服务器或多个容器),某个实例故障时 Nginx 需要能自动跳过故障节点,把请求转发到健康的实例上。如果健康检查没配置,Nginx 会继续往故障实例发请求,导致部分用户请求失败。

Nginx Plus 的主动健康检查

开源版 Nginx 的免费版本没有内置主动健康检查(只能靠被动失败检测),Nginx Plus 才支持主动健康检查。以下是开源版的替代方案。

开源版方案:max_fails + fail_timeout

复制代码
upstream backend {
    server 127.0.0.1:8080 max_fails=3 fail_timeout=30s;
    server 127.0.0.1:8081 max_fails=3 fail_timeout=30s;
    server 127.0.0.1:8082 backup;  # backup 机器,正常不参与负载均衡
}
  • max_fails=3:在 30 秒内连续失败 3 次,认为该 server 不可用

  • fail_timeout=30s:失败检测的时间窗口

  • backup:该 server 只在所有非 backup server 都不可用时才参与服务

用 OpenResty 实现主动健康检查

OpenResty 在 Nginx 基础上集成了 Lua,可以实现主动健康检查:

复制代码
upstream backend {
    server 127.0.0.1:8080;
    server 127.0.0.1:8081;
}

# OpenResty 健康检查配置(需要 lua-resty-upstream-healthcheck)
location /health_check {
    default_type text/plain;
    content_by_lua '
        local hc = require("resty.upstream.healthcheck")
        local ok, err = hc.ping_node({
            host = "127.0.0.1",
            port = 8080,
            timeout = 1000,
        })
        if ok then
            ngx.say("OK")
        else
            ngx.say("FAIL: " .. err)
        end
    ';
}

验证健康检查

复制代码
# 模拟后端故障(杀掉一个后端实例)
systemctl stop myapp@8080

# 观察 Nginx 是否自动跳过故障实例
watch -n 1 'curl -s http://localhost/upstream_status'

# upstream_status 显示:
# Server: 127.0.0.1:8080  DOWN  ← 被标记为 down
# Server: 127.0.0.1:8081  UP

补充二:SSL/TLS 优化

问题背景

HTTPS 连接建立时,SSL/TLS 握手本身会引入延迟。如果证书配置不当,每次新建连接都需要完整的握手过程,会让 TTFB 多出几十甚至上百毫秒。启用 TLS 会话复用,可以让重连时跳过完整的握手过程。

SSL 证书配置

复制代码
server {
    listen 443 ssl http2;
    server_name api.example.com;

    # 证书文件
    ssl_certificate /etc/nginx/ssl/api.example.com.crt;
    ssl_certificate_key /etc/nginx/ssl/api.example.com.key;

    # TLS 版本控制(禁用 TLS 1.0 和 1.1,它们有安全漏洞)
    ssl_protocols TLSv1.2 TLSv1.3;

    # 加密套件(禁用弱加密算法,只启用 AEAD 系列)
    ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384;
    ssl_prefer_server_ciphers on;

    # 开启 OCSP Stapling(浏览器不用向 CA 查询证书状态,减少一次 DNS 查询和 HTTP 请求)
    ssl_stapling on;
    ssl_stapling_verify on;
    resolver 8.8.8.8 8.8.4.4 valid=300s;
    resolver_timeout 5s;

    # SSL 会话缓存(减少 TLS 握手开销)
    ssl_session_cache shared:SSL:50m;    # 50MB 缓存,可存储约 10 万个会话
    ssl_session_timeout 1d;              # 会话缓存有效期
    ssl_session_tickets on;              # 启用会话票据(支持分布式 TLS 会话恢复)
}

验证 SSL 配置质量

复制代码
# 用 openssl 测试 SSL 连接和证书
echo | openssl s_client -connect api.example.com:443 -servername api.example.com

# 查看证书信息
echo | openssl s_client -connect api.example.com:443 2>/dev/null | \
  openssl x509 -noout -dates -issuer -subject

# 测试 TLS 1.3 支持
openssl s_client -connect api.example.com:443 -tls1_3 < /dev/null 2>&1 | \
  grep "TLSv1.3"

# 用 testssl.sh 做完整的 SSL 安全检测
docker run --rm -ti drwetter/testssl.sh api.example.com
# 检查项:TLS 版本、加密套件、证书链、OCSP Stapling、CRL/HSTS 等

补充三:502/504 错误排查

问题背景

502 Bad Gateway 和 504 Gateway Timeout 是 Nginx 作为反向代理时最常见的错误。502 表示 Nginx 收到了后端返回的错误响应(如 500 错误),504 表示 Nginx 等待后端超时。

502 错误排查

复制代码
# 第一步:查看 Nginx error log
tail -f /var/log/nginx/error.log | grep -i "502\|upstream"

# 第二步:查看 upstream 响应状态
# 如果是 PHP-FPM,通常是 fastcgi_pass 配置问题
# 如果是 Java,通常是 uwsgi_pass 或 proxy_pass 问题

# 第三步:检查后端服务状态
systemctl status php-fpm
systemctl status gunicorn
journalctl -u nginx --since "5 minutes ago" | grep -i error

# 第四步:检查后端日志
# PHP-FPM: /var/log/php-fpm/www-error.log
# Node.js: journalctl -u nodejs-app --since "5 minutes ago"

常见原因:

  • PHP-FPM/后端服务崩溃(OOM Kill、配置错误)

  • PHP-FPM 的 max_children 不足,请求排队超时

  • 后端进程数达到上限,无法接受新连接

  • 后端配置有语法错误,重启后没有正常加载

504 错误排查

复制代码
# 504 超时通常是以下原因:

# 1. proxy_read_timeout 设置过小
# 默认 60s,如果后端处理超过 60s 就超时
# 查看:grep "proxy_read_timeout" /etc/nginx/nginx.conf

# 2. 后端服务本身处理慢(慢查询、死循环)
# 查看后端慢查询日志

# 3. 网络延迟(Nginx 和后端之间的网络慢)
# traceroute 或 mtr 到后端 IP
mtr -r -c 10 127.0.0.1

# 4. upstream_connect_timeout 过小
# 如果 Nginx 和后端之间网络不稳定,连接建立超时

补充四:Nginx 常见配置错误案例

错误一:proxy_set_header Host 设置错误

复制代码
# 错误配置:$host 变量在某些场景下会被设置为端口号
location /api/ {
    proxy_pass http://backend;
    proxy_set_header Host $host;  # 如果请求是 api.example.com:8080,$host 会变成 api.example.com:8080
}

# 正确配置:去掉端口号
location /api/ {
    proxy_pass http://backend;
    proxy_set_header Host $http_host;  # $http_host 永远是 host:port 格式
    # 或者
    proxy_set_header Host $host:$server_port;  # 明确包含端口
}

错误二:proxy_buffering 导致的延迟

复制代码
# 默认 proxy_buffering 是开启的
# 后端响应会先写入 buffer,buffer 满了才返回给客户端
# 对于需要实时推送的场景(如 SSE、long polling),需要关闭 buffer

location /sse/ {
    proxy_pass http://backend;
    proxy_buffering off;        # 关闭缓冲
    proxy_cache off;           # 关闭缓存
    proxy_set_header Connection "";  # 清除 Connection 头
    chunked_transfer_encoding on;    # 启用 chunked 编码
}

错误三:location 匹配优先级错误

复制代码
# location 匹配优先级:最长前缀匹配 > 正则匹配(按配置文件顺序)> ^~

# 场景:想拦截所有 /api/ 开头的请求
location /api/ { }           # 匹配所有 /api/*(最长前缀)
location ~ ^/api/v1$ { }    # 只匹配 /api/v1(正则,优先级低于前缀)
location ^~ /api/v1 { }     # 匹配 /api/v1 开头的请求,禁用正则匹配

# 如果 /api/v1 写成正则且在前面,普通 /api/ 请求会被错误匹配到正则
# 正确做法:把精确匹配放前面,或使用 ^~ 修饰符

总结:优化是分层递进的过程

从 500ms 到 50ms,不是一个 magic bullet,而是多层优化叠加的结果:

  1. 网络层:keepalive 减少连接建立开销(-20~50ms)

  2. 缓存层:proxy_cache 挡住重复请求(命中时 -400~500ms)

  3. 传输层:gzip 压缩减少传输体积(-30~60ms)

  4. 协议层:HTTP/2 消除队头阻塞(-20~50ms)

  5. 系统层:内核参数调优减少网络延迟(-5~20ms)

  6. 后端层:Redis 缓存减少数据库查询(-200~400ms)

优化过程中最重要的不是配置调优,而是测量-改配置-再测量-对比效果的闭环。每一次改动都要有基线数据支撑,避免"我觉得这样会快"的主观判断。

最后提醒:优化结果需要回滚预案。每一个配置改动,最好记录原始值,一旦线上出现异常,第一时间回滚到上一个稳定状态,再排查原因。

相关推荐
用户0328472220706 小时前
如何搭建本地yum源(上)
运维
ping某1 天前
为什么 Nginx 明明监听了 80,转发后端时却用了 4xxxx 端口?
后端·nginx
大树883 天前
金刚石散热越强,管路越先见顶
大数据·运维·服务器·人工智能·ai
摇滚侠3 天前
Linux CentOS7 rpm 安装 MySQL 5.7
linux·运维·mysql
霸道流氓气质3 天前
领域驱动设计(DDD)在 Spring Boot 微服务中的实践指南
运维·spring boot·微服务
Inhand陈工3 天前
基于台达PLC与映翰通IG502的智慧水产养殖精准投喂与远程运维解决方案
运维·人工智能·物联网·阿里云·信息与通信
酣大智3 天前
ARP代理--工作原理
运维·网络·arp·arp代理
shushangyun_3 天前
2026年快消品B2B系统推荐:支持终端门店订货、促销政策自动化的工具?
java·运维·网络·数据库·人工智能·spring·自动化
施努卡机器视觉3 天前
SNK施努卡侧滑门锁上滑轮总成自动化装配线,从零件到组件,全流程精密制造方案
运维·自动化·制造
AC赳赳老秦3 天前
用 OpenClaw 搭建服务器故障应急响应系统,自动处理 80% 常见运维故障
android·运维·服务器·python·rxjava·deepseek·openclaw