Skip to content

Nginx:编译、反代、HTTPS 与限流 ​

包管理器装的 Nginx 往往缺模块、目录散在各处。生产建议源码编译 + 自定义前缀,配置、日志、二进制都在自己手里。

一、编译安装 ​

bash
yum install -y gcc make pcre-devel zlib-devel openssl-devel
# Ubuntu: apt install -y build-essential libpcre3-dev zlib1g-dev libssl-dev

cd /usr/local/src
wget https://nginx.org/download/nginx-1.27.2.tar.gz
tar zxf nginx-1.27.2.tar.gz && cd nginx-1.27.2

./configure \
  --prefix=/usr/local/nginx-1.27.2 \
  --with-http_ssl_module \
  --with-http_v2_module \
  --with-http_realip_module \
  --with-http_stub_status_module \
  --with-http_gzip_static_module \
  --with-http_sub_module \
  --with-stream \
  --with-stream_ssl_module \
  --with-threads \
  --with-pcre

make -j"$(nproc)" && make install
ln -sfn /usr/local/nginx-1.27.2 /usr/local/nginx
bash
/usr/local/nginx/sbin/nginx -V
text
nginx version: nginx/1.27.2
built by gcc 11.4.1 20231218 (Red Hat 11.4.1-3)
configure arguments: --prefix=/usr/local/nginx-1.27.2 --with-http_ssl_module --with-http_v2_module ...

-V 必须确认这几个模块在:

模块用途
http_ssl_moduleHTTPS
http_v2_moduleHTTP/2
http_realip_module取真实客户端 IP(过 CDN/LB 时必需)
http_stub_status_module状态页
streamTCP/UDP 四层代理

后期加模块要重新编译

Nginx 不支持动态加载官方模块(除少数第三方)。要加模块必须重新 ./configure && make,然后用新二进制重启。保留 nginx -V 的输出,下次照着抄参数。

二、systemd ​

ini
# /etc/systemd/system/nginx.service
[Unit]
Description=nginx
After=network-online.target
Wants=network-online.target

[Service]
Type=forking
PIDFile=/run/nginx.pid
ExecStartPre=/usr/local/nginx/sbin/nginx -t -q
ExecStart=/usr/local/nginx/sbin/nginx
ExecReload=/usr/local/nginx/sbin/nginx -s reload
ExecStop=/bin/kill -s QUIT $MAINPID
Restart=on-failure
RestartSec=3
LimitNOFILE=65535

[Install]
WantedBy=multi-user.target
ini
# nginx.conf 顶部要有
pid /run/nginx.pid;
bash
systemctl daemon-reload
systemctl enable --now nginx

三、配置结构 ​

ini
# /usr/local/nginx/conf/nginx.conf
user  nginx;
worker_processes auto;
worker_rlimit_nofile 65535;
pid /run/nginx.pid;
error_log /var/log/nginx/error.log warn;

events {
    worker_connections 10240;
    use epoll;
    multi_accept on;
}

http {
    include       mime.types;
    default_type  application/octet-stream;
    charset utf-8;

    log_format main '$remote_addr - $remote_user [$time_local] "$request" '
                    '$status $body_bytes_sent "$http_referer" '
                    '"$http_user_agent" rt=$request_time ut=$upstream_response_time';

    access_log /var/log/nginx/access.log main;

    sendfile        on;
    tcp_nopush      on;
    tcp_nodelay     on;
    keepalive_timeout 65;
    server_tokens   off;

    client_max_body_size 20m;
    client_body_buffer_size 128k;

    gzip on;
    gzip_min_length 1k;
    gzip_comp_level 5;
    gzip_types text/plain text/css application/json application/javascript text/xml application/xml;
    gzip_vary on;

    include /usr/local/nginx/conf/conf.d/*.conf;
}
配置说明
worker_processes auto等于 CPU 核数
worker_rlimit_nofile单 worker 的 fd 上限,要 ≥ worker_connections × 2
multi_accept on一次接受多个新连接
sendfile on零拷贝发静态文件
server_tokens off隐藏版本号
client_max_body_size上传大小上限,默认 1m

nginx -t 只检查语法,不检查证书是否过期、上游是否可达。

四、反向代理 ​

ini
# /usr/local/nginx/conf/conf.d/app.conf
upstream order_api {
    least_conn;
    server 10.0.0.41:8080 max_fails=3 fail_timeout=10s;
    server 10.0.0.42:8080 max_fails=3 fail_timeout=10s;
    keepalive 32;
}

server {
    listen 80;
    server_name app.example.com;

    location / {
        proxy_pass http://order_api;
        proxy_http_version 1.1;
        proxy_set_header Connection "";
        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 X-Forwarded-Proto $scheme;

        proxy_connect_timeout 3s;
        proxy_send_timeout 30s;
        proxy_read_timeout 30s;
        proxy_buffering on;
        proxy_buffers 8 16k;
    }

    location /static/ {
        root /data/www;
        expires 30d;
        access_log off;
    }

    location = /health {
        access_log off;
        return 200 "ok\n";
    }
}

关键点:

  • proxy_http_version 1.1 + Connection "" 才能启用 upstream keepalive。少了这两行,每次请求都新建 TCP 连接。
  • max_fails=3 fail_timeout=10s:10 秒内失败 3 次,10 秒内不再往这台转发。
  • proxy_read_timeout 是两次读之间的间隔,不是总时长。长轮询要调大。

proxy_pass 末尾斜杠决定路径拼接

ini
location /api/ {
    proxy_pass http://backend;        # 转发为 /api/foo
}
location /api/ {
    proxy_pass http://backend/;       # 转发为 /foo(去掉 /api 前缀)
}

这一个斜杠的差异,是最常见的 404 原因。

五、HTTPS ​

ini
server {
    listen 80;
    server_name app.example.com;
    return 301 https://$host$request_uri;
}

server {
    listen 443 ssl;
    http2 on;
    server_name app.example.com;

    ssl_certificate     /etc/nginx/certs/app.crt;
    ssl_certificate_key /etc/nginx/certs/app.key;

    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384;
    ssl_prefer_server_ciphers off;

    ssl_session_cache shared:SSL:10m;
    ssl_session_timeout 1d;
    ssl_session_tickets off;

    add_header Strict-Transport-Security "max-age=31536000" always;

    location / {
        proxy_pass http://order_api;
        # 同上
    }
}
配置作用
ssl_session_cache shared:SSL:10m10MB 约存 4 万个会话,显著减少握手
ssl_session_tickets off多机部署时关闭(ticket 密钥不同步会导致失败)
HSTS告诉浏览器以后只走 HTTPS,上线前确认所有子域都支持 HTTPS

证书检查:

bash
openssl x509 -in /etc/nginx/certs/app.crt -noout -subject -dates
text
subject=CN = app.example.com
notBefore=Sep  1 00:00:00 2026 GMT
notAfter=Nov 30 23:59:59 2026 GMT

六、限流 ​

ini
# http 段
limit_req_zone $binary_remote_addr zone=api:10m rate=20r/s;
limit_conn_zone $binary_remote_addr zone=conn:10m;

# server 段
location /api/ {
    limit_req zone=api burst=40 nodelay;
    limit_conn conn 20;
    proxy_pass http://order_api;
}
参数含义
zone=api:10m10MB 共享内存,约存 16 万个 IP
rate=20r/s每 IP 每秒 20 个请求
burst=40允许突发 40 个排队
nodelay突发部分立即处理,不延迟
limit_conn conn 20每 IP 最多 20 个并发连接

不加 nodelay 时,超过 rate 的请求会被排队延迟处理,表现为"越限越慢"。

超限返回:

bash
curl -i http://app.example.com/api/ping
text
HTTP/1.1 503 Service Temporarily Unavailable

限流默认返回 503,不是 429。要改:

ini
limit_req_status 429;
limit_conn_status 429;

七、日志切割 ​

ini
# /etc/logrotate.d/nginx
/var/log/nginx/*.log {
    daily
    rotate 14
    missingok
    notifempty
    compress
    delaycompress
    sharedscripts
    postrotate
        /bin/kill -USR1 $(cat /run/nginx.pid) 2>/dev/null || true
    endscript
}

kill -USR1 让 Nginx 重新打开日志文件。不能只 mv 不 signal,否则 Nginx 会继续写旧 inode。

bash
logrotate -d /etc/logrotate.d/nginx     # 干跑
logrotate -f /etc/logrotate.d/nginx     # 立即执行

八、常用命令 ​

bash
/usr/local/nginx/sbin/nginx -t              # 检查配置
/usr/local/nginx/sbin/nginx -s reload       # 热加载
/usr/local/nginx/sbin/nginx -s reopen       # 重开日志
/usr/local/nginx/sbin/nginx -s quit         # 优雅停止
systemctl reload nginx

reload 不会中断现有连接,但配置有错会静默用旧配置

nginx -s reload 在配置校验失败时不会替换进程,你会以为生效了其实没有。所以必须先 nginx -t 再 reload,并看 error.log 是否有 [emerg]。

九、状态页 ​

ini
server {
    listen 127.0.0.1:8090;
    location /nginx_status {
        stub_status;
        allow 127.0.0.1;
        deny all;
    }
}
bash
curl -s http://127.0.0.1:8090/nginx_status
text
Active connections: 291
server accepts handled requests
 1823411 1823411 9842134
Reading: 2 Writing: 18 Waiting: 271
字段含义
Active当前连接数
accepts / handled应接受 / 实际接受(不等说明触发了连接限制)
requests总请求数
Reading正在读请求头
Writing正在写响应
Waitingkeepalive 空闲连接

十、常见错误码 ​

码含义排查
400请求格式错客户端问题,或 Host 头不对
403权限/目录索引root 目录权限,autoindex
404文件不存在root/alias 路径,proxy_pass 斜杠
413请求体太大client_max_body_size
499客户端主动断开后端太慢,客户端等不及
500后端返回 500查后端日志
502连不上后端后端没起、端口错、防火墙
504后端超时proxy_read_timeout、后端慢

499 是 Nginx 特有的:它记录"客户端在响应前关闭了连接"。大量 499 = 后端处理太慢,不是 Nginx 的错。

十一、踩过的坑 ​

现象根因处置
上传大文件 413client_max_body_size 默认 1m调到需要的大小
502 但后端正常upstream 端口/地址错,或 SELinux查 error.log,curl 直连后端
504 偶发proxy_read_timeout 太短调大,或优化后端
拿不到真实 IP没配 realip 或 X-Forwarded-Forset_real_ip_from + real_ip_header
reload 后配置没变配置有语法错,reload 失败先 nginx -t
日志切割后不写了只 mv 没发 USR1logrotate 里加 postrotate
upstream keepalive 不生效缺 proxy_http_version 1.1 和 Connection ""补上两行
worker_connections are not enough连接数和 fd 不够调 worker_connections 和 worker_rlimit_nofile
nginx -t 报 unknown directive模块没编译进去nginx -V 确认,重新编译