服务器只要暴露在公网上,就会有爬虫扫描、暴力破解、CC 攻击、恶意爬虫。Nginx 默认配置是"能跑就行",完全没有防护。这篇就来聊安全加固,核心三板斧:限速防刷、封禁恶意 IP、防 DDoS 基础。
💡 提示: 安全加固的原则是"最小暴露面"——能不暴露的端口就不暴露,能不给的信息就不给,能不开放的路径就不开放。每减少一个暴露面,攻击面就少一个。
一、隐藏敏感信息
1.1 为什么要隐藏信息
攻击者在攻击之前会先"踩点"——探测服务器版本、模块、操作系统。信息越少,攻击成本越高。Nginx 默认会在错误页面和响应头里暴露版本号:
# 默认的响应头
Server: nginx/1.24.0
# 默认的 404 页面
404 Not Found
nginx/1.24.0
攻击者看到 nginx/1.24.0,就能精准查找这个版本的已知漏洞。
1.2 隐藏版本号
# /etc/nginx/nginx.conf 的 http 块里
http {
server_tokens off;
}
改完后效果:
| | |
|---|
| Server: nginx/1.24.0 | Server: nginx |
| | |
| | |
⚠️ 注意: server_tokens off 只是隐藏版本号,不是真正的安全措施。它增加的是攻击成本,不是安全性本身。真正的安全是及时更新 Nginx 版本和系统补丁。
1.3 完全隐藏 Server 头
server_tokens off 还会暴露 Server: nginx,如果想完全去掉:
# 需要安装 headers-more 模块
sudo apt install nginx-extras
# 包含了 headers-more 模块,或从源码编译时添加 --add-module 参数。
# 在 nginx.conf 的 http 块里
http {
more_clear_headers "Server";
more_clear_headers "X-Powered-By";
more_clear_headers "X-Runtime";
}
| | |
|---|
server_tokens off | | |
more_clear_headers | | |
编译时修改源码 src/http/ngx_http_header_filter_module.c | | |
1.4 添加安全响应头
# /etc/nginx/conf.d/example.conf 的 server 块里
server {
# 防止 MIME 类型嗅探
add_header X-Content-Type-Options "nosniff" always;
# 防止点击劫持(页面不允许被 iframe 嵌套)
add_header X-Frame-Options "SAMEORIGIN" always;
# 浏览器 XSS 过滤(虽然现代浏览器已内置,加上更保险)
add_header X-XSS-Protection "1; mode=block" always;
# 内容安全策略(CSP)——限制资源加载来源
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:;" always;
# HSTS——强制浏览器以后都用 HTTPS 访问
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
# Referrer 策略——控制 Referer 头泄露
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
# 权限策略——控制浏览器功能(摄像头/麦克风/地理位置等)
add_header Permissions-Policy "camera=(), microphone=(), geolocation=()" always;
}
| | |
|---|
| | nosniff |
| | SAMEORIGIN |
| | 1; mode=block |
| | |
| Strict-Transport-Security | | max-age=31536000 |
| | strict-origin-when-cross-origin |
| | |
💡 提示: always 参数很重要。不加 always 时,这些头只在 2xx/3xx 响应中添加;加了 always 后,4xx/5xx 错误页面也会带上安全头。攻击者经常故意请求错误页面来探测信息。
1.5 隐藏不必要的错误信息
# 不要把后端错误暴露给用户
location /api/ {
proxy_pass http://127.0.0.1:3000;
# 后端返回 500 时,显示自定义错误页而不是堆栈信息
proxy_intercept_errors on;
error_page 500 502 503 504 /50x.html;
}
# 自定义错误页面
location = /50x.html {
root /var/www/errors;
internal;
}
二、限速——limit_req 模块
2.1 为什么限速
不限速时,一个 IP 一秒发 1000 个请求,Nginx 全部转发到后端,后端直接被打满。限速就是给每个 IP 设一个请求频率上限,超过的请求直接拒绝。
2.2 limit_req_zone 定义区域
# /etc/nginx/nginx.conf 的 http 块里
http {
# 定义限速区域
# $binary_remote_addr 以二进制格式存储客户端 IP(比文本省内存)
# zone=req_limit:10m 共享内存区域名和大小,10MB 约可存储十万级 IP 的状态(取决于实际请求模式)
# rate=10r/s 每个 IP 每秒最多 10 个请求
limit_req_zone $binary_remote_addr zone=req_limit:10m rate=10r/s;
}
💡 提示: $binary_remote_addr 比 $remote_addr 省内存。文本格式的 IPv4 地址占 15 字节(如 192.168.1.100),二进制格式只占 4 字节。10MB 内存约可存 16 万个 IP 的状态。
2.3 limit_req 启用限速
# /etc/nginx/conf.d/example.conf
server {
# 全局限速(所有 location 共享)
limit_req zone=req_limit burst=20 nodelay;
# 只对 API 限速
location /api/ {
limit_req zone=req_limit burst=20 nodelay;
proxy_pass http://127.0.0.1:3000;
}
# 登录接口更严格
location /api/login {
limit_req zone=req_limit burst=5 nodelay;
proxy_pass http://127.0.0.1:3000;
}
}
2.4 burst 和 nodelay 详解
这是限速配置最容易搞混的部分:
各种组合的效果:
| |
|---|
rate=10r/s | |
rate=10r/s burst=20 | 突发 30 个(10+20)能进入队列,但队列中的请求要等,超出 503 |
rate=10r/s burst=20 nodelay | 突发 30 个立即处理不延迟,超出 503(推荐) |
rate=10r/s burst=20 delay=10 | 前 10 个立即处理,后 10 个延迟处理,超出 503 |
# 图解:rate=10r/s burst=20 nodelay 的效果
#
# 时间轴(秒)
# 0s: 30个请求同时来 → 前30个通过(10常规+20突发),第31个503
# 1s: 又来5个 → 全部通过(桶里还有10个额度)
# 2s: 又来15个 → 全部通过(用完突发桶)
# 3s: 又来15个 → 前10个通过,后5个503(突发桶空了)
⚠️ 注意: burst 设太大会让限速形同虚设。一般 API 接口 burst 设 5-20,普通页面设 20-50。登录等敏感接口 burst 设 3-5。简单记忆:burst 是‘突发窗口’,nodelay 是‘不要排队’。两者结合就是‘允许你偶尔超一点,但不让你的请求等待。
2.5 自定义限速响应
默认被限速时返回 503 Service Unavailable,可以改成 429 Too Many Requests(更符合 HTTP 语义):
http {
limit_req_zone $binary_remote_addr zone=req_limit:10m rate=10r/s;
limit_req_status 429;
}
server {
limit_req zone=req_limit burst=20 nodelay;
# 自定义 429 响应
error_page 429 = @rate_limited;
location @rate_limited {
default_type application/json;
return 429 '{"error": "Too Many Requests", "retry_after": 60}';
}
}
2.6 多维度限速
不同接口限速不同:
http {
# 全局限速:每秒 10 个请求
limit_req_zone $binary_remote_addr zone=general:10m rate=10r/s;
# API 限速:每秒 5 个请求
limit_req_zone $binary_remote_addr zone=api:10m rate=5r/s;
# 登录限速:每秒 1 个请求
limit_req_zone $binary_remote_addr zone=login:10m rate=1r/s;
# 下载限速:每秒 2 个请求
limit_req_zone $binary_remote_addr zone=download:10m rate=2r/s;
}
server {
# 全局限速
limit_req zone=general burst=20 nodelay;
location /api/ {
limit_req zone=api burst=10 nodelay;
proxy_pass http://127.0.0.1:3000;
}
location /api/login {
limit_req zone=login burst=3 nodelay;
proxy_pass http://127.0.0.1:3000;
}
location /download/ {
limit_req zone=download burst=5 nodelay;
proxy_pass http://127.0.0.1:3000;
}
}
三、限制并发连接数——limit_conn 模块
3.1 limit_req vs limit_conn
limit_req 限制"每秒发多少个请求",limit_conn 限制"同时保持多少个连接"。两者互补,都要开。
3.2 配置 limit_conn
# http 块
http {
# 定义连接数限制区域
# 每个IP最多同时保持 10 个连接
limit_conn_zone $binary_remote_addr zone=conn_limit:10m;
}
# server 块
server {
# 每个IP最多 10 个并发连接
limit_conn conn_limit 10;
# 整个虚拟主机最多 1000 个并发连接(防止单站占满所有连接)
limit_conn_zone $server_name zone=perserver:10m;
limit_conn perserver 1000;
limit_conn_status 429;
}
3.3 慢速攻击防护
慢速攻击(Slowloris)的原理是:攻击者建立大量连接,但每个请求都发得极慢(比如每分钟发一个字节),占满服务器的连接数,正常用户连不上。
# http 块——超时配置
http {
# 客户端请求头超时(60秒没发完请求头就断开)
client_header_timeout 60s;
# 客户端请求体超时(60秒没发完 body 就断开)
client_body_timeout 60s;
# 客户端响应超时(发送响应后60秒客户端没读完就断开)
send_timeout 60s;
# keepalive 超时(空闲连接65秒后断开)
keepalive_timeout 65s;
# 限制请求体大小(防止超大 POST 攻击)
client_max_body_size 10m;
# 限制请求头大小
large_client_header_buffers 4 8k;
}
四、IP 封禁——allow / deny
4.1 基本用法
# 允许特定 IP,拒绝其他所有
location /admin {
allow 192.168.1.0/24; # 允许内网
allow 10.0.0.0/8; # 允许 VPN
allow 123.45.67.89; # 允许特定公网 IP
deny all; # 拒绝其他所有
proxy_pass http://127.0.0.1:3000;
}
# 拒绝特定 IP,允许其他所有
location / {
deny 1.2.3.4; # 拒绝单个 IP
deny 5.6.0.0/16; # 拒绝一个网段
allow all; # 允许其他所有
proxy_pass http://127.0.0.1:3000;
}
⚠️ 注意: allow 和 deny 按从上到下顺序匹配,匹配到就停止。所以 deny all 一定要放在最后,allow all 一定要放在最后(取决于策略)。
4.2 适用场景
| |
|---|
| |
| |
| |
| allow 国家IP段,deny all(需要 GeoIP) |
4.3 全局 IP 封禁
# /etc/nginx/conf.d/blocklist.conf
# 封禁列表(可以单独维护一个文件,方便更新)
deny 1.2.3.4; # 扫描器
deny 5.6.7.8; # 暴力破解
deny 10.20.30.0/24; # 某机房段
# 在 server 块里引用
server {
include /etc/nginx/conf.d/blocklist.conf;
# ... 其他配置
}
五、Geo 模块——条件封禁
5.1 为什么用 geo 模块
allow/deny 适合封少数 IP。如果要根据 IP 段做不同的策略(比如国内放行、国外限制),手动写 allow/deny 列表会非常长。geo 模块可以给 IP 段打标签,然后在配置里根据标签做不同的处理。
5.2 geo 基本用法
# http 块
http {
# 定义 IP 分类
geo $allowed_ip {
default 0; # 默认不允许
192.168.0.0/16 1; # 内网允许
10.0.0.0/8 1; # VPN允许
123.45.67.89 1; # 特定IP允许
}
}
# server 块
server {
location /admin {
if ($allowed_ip = 0) {
return 403;
}
proxy_pass http://127.0.0.1:3000;
}
}
5.3 封禁恶意 IP 段
http {
geo $blocked_ip {
default 0; # 默认不封禁
# 已知攻击源
1.2.3.0/24 1;
5.6.0.0/16 1;
10.20.30.0/24 1;
# 某云扫描器段
100.200.0.0/16 1;
}
}
server {
if ($blocked_ip = 1) {
return 403;
}
}
5.4 使用 map 实现灵活控制
http {
# 定义封禁列表
geo $is_blocked {
default 0;
1.2.3.0/24 1;
5.6.0.0/16 1;
}
# 用 map 定义不同 location 的封禁行为
map $is_blocked$uri $block_action {
default 0; # 不封禁
1/api/login 1; # 封禁访问登录接口
1/admin 1; # 封禁访问后台
}
}
server {
location / {
if ($block_action = 1) {
return 403;
}
proxy_pass http://127.0.0.1:3000;
}
}
💡 提示: geo 模块的查找是 O(1) 的(基于 radix tree),性能远好于正则匹配。大量 IP 段的封禁建议用 geo,不要用 if + 正则。
六、限制 HTTP 方法
6.1 只允许必要的方法
server {
# 只允许 GET、POST、HEAD
if ($request_method !~ ^(GET|POST|HEAD)$ ) {
return 405;
}
# API 允许 GET、POST、PUT、DELETE、HEAD
location /api/ {
if ($request_method !~ ^(GET|POST|PUT|DELETE|HEAD)$ ) {
return 405;
}
proxy_pass http://127.0.0.1:3000;
}
}
⚠️ 注意: TRACE 和 CONNECT 方法在生产环境必须禁止。TRACE 会导致跨站追踪(XST)攻击,CONNECT 可能被用来做代理跳板。
七、防止目录遍历和敏感文件泄露
7.1 禁止访问隐藏文件和敏感路径
server {
# 禁止访问以 . 开头的隐藏文件(.git、.env、.htaccess 等)
location ~ /\. {
deny all;
access_log off;
log_not_found off;
}
# 禁止访问备份文件
location ~ ~$ {
deny all;
access_log off;
log_not_found off;
}
# 禁止访问 .git 目录(源码泄露)
location ~ /\.git {
deny all;
access_log off;
log_not_found off;
}
# 禁止访问 .env 文件(数据库密码等)
location ~ /\.env {
deny all;
access_log off;
log_not_found off;
}
# 禁止访问配置文件
location ~ \.(sql|bak|backup|old|swp|tmp|log)$ {
deny all;
access_log off;
log_not_found off;
}
}
7.2 常见泄露路径
| | |
|---|
/.git/ | | |
/.env | | |
/.htaccess | | |
/.htpasswd | | |
/wp-admin/ | | |
/phpmyadmin/ | | |
/*.sql | | |
/*.bak | | |
/*.log | | |
🚨 警告: .git 目录泄露是常见的安全事故之一。攻击者通过 /.git/config 可以还原整个源代码仓库,包括数据库密码、API 密钥等。务必确保 /.git 被封禁。
7.3 关闭目录列表
server {
# 关闭目录列表(如果 index 文件不存在,不显示目录内容)
autoindex off;
}
默认就是 off,但确认一下没被意外打开。
八、防止图片盗链
8.1 什么是盗链
别人网站直接引用服务器上的图片 URL,用户访问他的网站,图片是从服务器加载的——带宽是你出的,流量是你付的。
8.2 配置防盗链
server {
# 图片防盗链
location ~ \.(jpg|jpeg|png|gif|webp|svg|mp4|mp3|woff|woff2)$ {
# valid_referers:合法的来源
# none:直接访问(浏览器地址栏输入URL)
# blocked:Referer 被代理删除了
# server_names:本站域名
valid_referers none blocked server_names
*.example.com example.com
*.google.com; # 允许搜索引擎
if ($invalid_referer) {
# 返回一张替代图片(或直接 403)
return 403;
# 或者返回一张"禁止盗链"的图片
# rewrite ^/ /hotlink-denied.png last;
}
root /var/www/example;
}
}
| |
|---|
none | |
blocked | |
server_names | 允许 Referer 中的域名匹配 server_name |
*.example.com | |
example.com | |
💡 提示: 如果图片需要被第三方 CDN 加载(比如又拍云、七牛云),把 CDN 域名加到 valid_referers 里。valid_referers 是基础防护,不能完全防止盗链,对高价值资源考虑用签名 URL。
九、防 DDoS 基础
9.1 DDoS 攻击类型
⚠️ 注意: Nginx 是应用层(L7)服务器,只能防应用层 DDoS(CC/连接耗尽/慢速攻击)。网络层 DDoS(SYN Flood/UDP 洪泛)需要内核参数调优 + 云服务商高防 IP。Nginx 做不到的事情不要勉强。
9.2 Nginx 层面能做的
第一层:限速 + 限连接
http {
# 限速
limit_req_zone $binary_remote_addr zone=req_limit:10m rate=10r/s;
# 限连接
limit_conn_zone $binary_remote_addr zone=conn_limit:10m;
# 限制每 IP 请求体大小
client_body_buffer_size 8k;
client_max_body_size 2m;
# 超时
client_header_timeout 30s;
client_body_timeout 30s;
send_timeout 30s;
}
server {
limit_req zone=req_limit burst=20 nodelay;
limit_conn conn_limit 10;
limit_req_status 429;
limit_conn_status 429;
}
第二层:限制请求头大小和数量
http {
# 限制请求头缓冲区(防止超大请求头攻击)
client_header_buffer_size 1k;
large_client_header_buffers 4 8k;
# 限制请求体缓冲区
client_body_buffer_size 8k;
}
第三层:内核参数调优
# /etc/sysctl.d/99-ddos.conf
# SYN Flood 防护
net.ipv4.tcp_syncookies = 1 # 开启 SYN Cookie
net.ipv4.tcp_max_syn_backlog = 65535 # SYN 队列长度
net.ipv4.tcp_synack_retries = 2 # SYN-ACK 重试次数(默认5,减少可防 SYN Flood)
# 连接复用
net.ipv4.tcp_tw_reuse = 1 # 复用 TIME_WAIT
net.ipv4.tcp_fin_timeout = 15 # FIN-WAIT-2 超时
# 连接追踪
net.netfilter.nf_conntrack_max = 1000000 # 连接追踪表大小
net.netfilter.nf_conntrack_tcp_timeout_established = 1800 # 已建立连接超时
# ICMP 防护
net.ipv4.icmp_echo_ignore_broadcasts = 1 # 忽略广播 ICMP
net.ipv4.icmp_ignore_bogus_error_responses = 1
# 反向路径过滤
net.ipv4.conf.all.rp_filter = 1
net.ipv4.conf.default.rp_filter = 1
# 应用配置
sudo sysctl -p /etc/sysctl.d/99-ddos.conf
9.3 集成 fail2ban 自动封禁
Nginx 限速能挡住超速请求,但恶意 IP 还是在不断尝试。fail2ban 可以监控 Nginx 日志,自动把超过阈值的 IP 用 iptables 封掉。如果不想手动维护 IP 黑名单,用 fail2ban 自动封禁是最省心的方案。
创建对应的 filter 文件,或使用 fail2ban 自带的 nginx-limit-req 过滤规则(如果存在)。
# 安装 fail2ban
sudo apt install fail2ban
# 创建 Nginx 限速 jail 配置
sudo tee /etc/fail2ban/jail.d/nginx-limit.conf << 'EOF'
[nginx-limit-req]
enabled = true
filter = nginx-limit-req
action = iptables-multiport[name=ReqLimit, port="http,https", protocol=tcp]
logpath = /var/log/nginx/error.log
findtime = 600
bantime = 7200
maxretry = 10
[nginx-botsearch]
enabled = true
filter = nginx-botsearch
action = iptables-multiport[name=BotSearch, port="http,https", protocol=tcp]
logpath = /var/log/nginx/access.log
findtime = 600
bantime = 7200
maxretry = 2
EOF
# 启动 fail2ban
sudo systemctl enable fail2ban
sudo systemctl restart fail2ban
# 查看 fail2ban 状态
sudo fail2ban-client status nginx-limit-req
# 输出示例:
# Status for the jail: nginx-limit-req
# |- Filter
# | |- Currently failed: 3
# | |- Total failed: 156
# | `- File list: /var/log/nginx/error.log
# `- Actions
# |- Currently banned: 5
# |- Total banned: 23
# `- Banned IP list: 1.2.3.4 5.6.7.8 9.10.11.12 13.14.15.16 17.18.19.20
# 手动解封某个 IP
sudo fail2ban-client set nginx-limit-req unbanip 1.2.3.4
💡 提示: fail2ban 和 Nginx 限速是互补关系:Nginx 限速挡住超速请求(返回 429),fail2ban 监控到某个 IP 频繁触发限速,直接用 iptables 封掉它的所有连接。两层防护,效果倍增。
十、SSL/TLS 安全加固
10.1 禁用不安全的协议和加密套件
server {
listen 443 ssl http2;
# 只允许 TLS 1.2 和 1.3
ssl_protocols TLSv1.2 TLSv1.3;
# 安全的加密套件
ssl_ciphers ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256;
# 服务器优先选择加密套件
ssl_prefer_server_ciphers on;
# SSL 会话缓存
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 10m;
ssl_session_tickets off;
# OCSP Stapling(让客户端不需要单独查询证书状态)
ssl_stapling on;
ssl_stapling_verify on;
resolver 223.5.5.5 223.6.6.6 valid=300s;
resolver_timeout 5s;
}
10.2 HSTS 强制 HTTPS
server {
# HSTS:告诉浏览器一年内只走 HTTPS
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
}
⚠️ 注意: preload 参数意味着域名会被加入浏览器的 HSTS 预加载列表。一旦加入,所有用户访问域名都强制 HTTPS,无法回退到 HTTP。只有在确认所有子域名都支持 HTTPS 后才加 preload。
十一、完整安全加固配置模板
把以上所有安全措施整合到一份配置里:
# /etc/nginx/nginx.conf
user www-data;
worker_processes auto;
worker_rlimit_nofile 65535;
pid /run/nginx.pid;
error_log /var/log/nginx/error.log warn;
events {
worker_connections 4096;
multi_accept on;
use epoll;
}
http {
# ===== 基础安全 =====
server_tokens off;
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 30s;
# ===== 限制请求大小 =====
client_header_buffer_size 1k;
large_client_header_buffers 4 8k;
client_body_buffer_size 8k;
client_max_body_size 2m;
# ===== 超时配置 =====
client_header_timeout 30s;
client_body_timeout 30s;
send_timeout 30s;
# ===== 限速 =====
limit_req_zone $binary_remote_addr zone=req_limit:10m rate=10r/s;
limit_req_status 429;
# ===== 限连接 =====
limit_conn_zone $binary_remote_addr zone=conn_limit:10m;
limit_conn_status 429;
# ===== IP 封禁 =====
geo $blocked_ip {
default 0;
# 在这里添加要封禁的 IP 段
# 1.2.3.0/24 1;
# 5.6.0.0/16 1;
}
# ===== Gzip =====
gzip on;
gzip_comp_level 5;
gzip_min_length 1k;
gzip_types text/plain text/css text/javascript application/javascript application/json application/xml image/svg+xml;
gzip_vary on;
# ===== SSL 全局 =====
ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers on;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 10m;
ssl_session_tickets off;
include /etc/nginx/mime.types;
default_type application/octet-stream;
include /etc/nginx/conf.d/*.conf;
}
# /etc/nginx/conf.d/example.conf
server {
listen 443 ssl http2;
server_name example.com;
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
# ===== 安全响应头 =====
add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:;" always;
# ===== 限速 + 限连接 =====
limit_req zone=req_limit burst=20 nodelay;
limit_conn conn_limit 10;
# ===== IP 封禁 =====
if ($blocked_ip = 1) {
return 403;
}
# ===== 只允许必要 HTTP 方法 =====
if ($request_method !~ ^(GET|POST|HEAD|OPTIONS)$ ) {
return 405;
}
# ===== 隐藏敏感文件 =====
location ~ /\. {
deny all;
access_log off;
log_not_found off;
}
location ~ ~$ {
deny all;
access_log off;
log_not_found off;
}
location ~ \.(sql|bak|backup|old|swp|tmp|log|env)$ {
deny all;
access_log off;
log_not_found off;
}
# ===== 后台管理——IP 白名单 =====
location /admin {
allow 192.168.0.0/16;
allow 10.0.0.0/8;
allow 123.45.67.89;
deny all;
proxy_pass http://127.0.0.1:3000;
}
# ===== 登录接口——更严格限速 =====
location /api/login {
limit_req zone=req_limit burst=3 nodelay;
proxy_pass http://127.0.0.1:3000;
}
# ===== API =====
location /api/ {
limit_req zone=req_limit burst=10 nodelay;
proxy_pass http://127.0.0.1:3000;
}
# ===== 图片防盗链 =====
location ~ \.(jpg|jpeg|png|gif|webp|svg|mp4)$ {
valid_referers none blocked server_names *.example.com;
if ($invalid_referer) {
return 403;
}
root /var/www/example;
expires 30d;
}
# ===== 静态文件 =====
location / {
root /var/www/example;
index index.html;
try_files $uri $uri/ =404;
}
access_log /var/log/nginx/example.access.log;
error_log /var/log/nginx/example.error.log warn;
}
# HTTP → HTTPS 重定向
server {
listen 80;
server_name example.com;
return 301 https://$host$request_uri;
}
十二、安全验证清单
上线前对照这张清单逐项检查:
| | |
|---|
| curl -I https://example.com | Server: nginx |
| curl -I https://example.com | |
| ab -n 100 -c 20 https://example.com/ | |
| curl -I https://example.com/.git/config | |
| curl -I https://example.com/.env | |
| curl -X TRACE https://example.com | |
| curl -I https://example.com | 包含 Strict-Transport-Security |
| curl -I http://example.com | |
| | |
| sudo fail2ban-client status | |
部署完成后用这些命令跑一遍,确认防护措施都生效了。
十三、常见问题排查
13.1 限速配置不生效
| |
|---|
| limit_req_zone 写在 server 块里 | |
| |
| sudo nginx -s reload |
| 检查 limit_req 的 zone 名和 zone 定义一致 |
| |
| CDN 回源时 IP 是 CDN 的,需要用 $http_x_forwarded_for |
13.2 CDN 场景下限速失效
如果网站套了 CDN(如阿里云 CDN、腾讯云 CDN),Nginx 看到的 $remote_addr 是 CDN 节点 IP,不是用户真实 IP,限速会变成"对整个 CDN 节点限速"。
# 方案:用 real_ip 模块获取真实 IP
http {
# 信任的 CDN IP 段(以阿里云为例)
set_real_ip_from 47.0.0.0/8;
set_real_ip_from 39.0.0.0/8;
set_real_ip_from 120.0.0.0/8;
# 从哪个头取真实 IP
real_ip_header X-Forwarded-For;
# 递归去除信任的代理 IP
real_ip_recursive on;
}
⚠️ 注意: set_real_ip_from 必须只包含信任的 CDN/代理 IP。如果设成 0.0.0.0/0,任何人都可以伪造 X-Forwarded-For 头绕过限速。
13.3 allow/deny 不生效
| |
|---|
| |
| $remote_addr |
| 正则 location 优先级高于前缀 location |
13.4 fail2ban 不封禁
| |
|---|
| 确认 logpath 和 Nginx 实际日志路径一致 |
| sudo fail2ban-regex /var/log/nginx/error.log /etc/fail2ban/filter.d/nginx-limit-req.conf |
| |
| |
| sudo iptables -L -n |
十四、速查表
限速速查
| | |
|---|
limit_req_zone ... rate= | | |
limit_req zone= ... burst= | | |
nodelay | | |
limit_req_status | | |
封禁速查
| | |
|---|
allow/deny | | deny 1.2.3.4; |
geo | | geo $blocked { ... } |
fail2ban | | |
| | iptables -A INPUT -s 1.2.3.4 -j DROP |
安全头速查
| |
|---|
| nosniff |
| SAMEORIGIN |
| Strict-Transport-Security | max-age=31536000; includeSubDomains |
| strict-origin-when-cross-origin |
| |
超时速查
防护层级速查
小结
Nginx 安全加固的核心逻辑:
- 隐藏信息——
server_tokens off + 安全响应头,减少攻击者可获取的信息 - 限速防刷——
limit_req 控制请求频率,CC 攻击和 API 滥用的第一道防线 - 限连接防耗尽——
limit_conn 控制并发连接数,慢速攻击和连接耗尽的防线 - IP 封禁——
allow/deny 手动封禁,geo 模块批量分类,fail2ban 自动封禁 - 敏感路径保护——封禁
.git、.env、备份文件,防止源码和密码泄露 - SSL 加固——禁用旧协议,只留 TLS 1.2/1.3,启用 HSTS
💡 提示: 安全加固不是一次性的工作。新的攻击手法不断出现,定期检查 Nginx 版本更新、审查日志中的异常请求、更新 fail2ban 规则,是保持安全的必要工作。
一个原则:多层防护,纵深防御。不要只靠一层——限速 + 限连接 + IP 封禁 + fail2ban + 内核参数 + 高防 IP,每一层都挡一部分攻击,叠加起来才能扛住真实流量。
阅读原文:点击这里
该文章在 2026/7/19 14:02:42 编辑过