外观
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/nginxbash
/usr/local/nginx/sbin/nginx -Vtext
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_module | HTTPS |
http_v2_module | HTTP/2 |
http_realip_module | 取真实客户端 IP(过 CDN/LB 时必需) |
http_stub_status_module | 状态页 |
stream | TCP/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.targetini
# 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:10m | 10MB 约存 4 万个会话,显著减少握手 |
ssl_session_tickets off | 多机部署时关闭(ticket 密钥不同步会导致失败) |
| HSTS | 告诉浏览器以后只走 HTTPS,上线前确认所有子域都支持 HTTPS |
证书检查:
bash
openssl x509 -in /etc/nginx/certs/app.crt -noout -subject -datestext
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:10m | 10MB 共享内存,约存 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/pingtext
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 nginxreload 不会中断现有连接,但配置有错会静默用旧配置
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_statustext
Active connections: 291
server accepts handled requests
1823411 1823411 9842134
Reading: 2 Writing: 18 Waiting: 271| 字段 | 含义 |
|---|---|
Active | 当前连接数 |
accepts / handled | 应接受 / 实际接受(不等说明触发了连接限制) |
requests | 总请求数 |
Reading | 正在读请求头 |
Writing | 正在写响应 |
Waiting | keepalive 空闲连接 |
十、常见错误码
| 码 | 含义 | 排查 |
|---|---|---|
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 的错。
十一、踩过的坑
| 现象 | 根因 | 处置 |
|---|---|---|
| 上传大文件 413 | client_max_body_size 默认 1m | 调到需要的大小 |
| 502 但后端正常 | upstream 端口/地址错,或 SELinux | 查 error.log,curl 直连后端 |
| 504 偶发 | proxy_read_timeout 太短 | 调大,或优化后端 |
| 拿不到真实 IP | 没配 realip 或 X-Forwarded-For | set_real_ip_from + real_ip_header |
| reload 后配置没变 | 配置有语法错,reload 失败 | 先 nginx -t |
| 日志切割后不写了 | 只 mv 没发 USR1 | logrotate 里加 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 确认,重新编译 |