服务器被DDoS打挂了?4层防线教你扛住流量洪峰
### 推荐标题
1. 服务器被DDoS打挂了?4层防线教你扛住流量洪峰
2. DDoS攻击来了怎么办?从免费到付费4层防御方案
3. 小公司也能防DDoS,这4招低成本高效果
今天把DDoS防御的4层防线整理出来,从免费到付费,从小公司到大规模,总有一层适合你。
第一层:内核参数调优(零成本)
在攻击流量还没到应用层之前,先让内核挡一波。这几个参数必须调:
# 开启SYN Cookie防SYN Flood sysctl -w net.ipv4.tcp_syncookies=1 # 增大SYN队列长度 sysctl -w net.ipv4.tcp_max_syn_backlog=8192 # 减少SYN重试次数 sysctl -w net.ipv4.tcp_synack_retries=2 # 加快TIME_WAIT回收 sysctl -w net.ipv4.tcp_tw_reuse=1 sysctl -w net.ipv4.tcp_max_tw_buckets=5000
同时用iptables做基础速率限制:
# 单IP每秒最多20个新连接 iptables -A INPUT -p tcp --syn -m connlimit --connlimit-above 20 -j DROP # 单IP每秒最多50个包 iptables -A INPUT -p tcp -m limit --limit 50/s --limit-burst 100 -j ACCEPT
这一层能挡住80%的脚本小子和小规模攻击,零成本,所有Linux服务器都应该配上。
第二层:Nginx限流与防刷(零成本)
一、隐藏敏感信息
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_tokens on(默认) |
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 |
完全去掉 Server 头 |
headers-more |
|
编译时修改源码 |
改掉默认 Server 字符串 |
不需要(但需要编译) |
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;
}
|
安全头 |
作用 |
推荐值 |
|---|---|---|
|
X-Content-Type-Options |
防止浏览器猜测文件类型 |
nosniff |
|
X-Frame-Options |
防止页面被 iframe 嵌套 |
SAMEORIGIN |
|
X-XSS-Protection |
启用浏览器 XSS 过滤 |
1; mode=block |
|
Content-Security-Policy |
限制资源加载来源 |
按需配置 |
|
Strict-Transport-Security |
强制 HTTPS(HSTS) |
max-age=31536000 |
|
Referrer-Policy |
控制 Referer 泄露 |
strict-origin-when-cross-origin |
|
Permissions-Policy |
禁用不需要的浏览器功能 |
按需配置 |
💡 提示:
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 设一个请求频率上限,超过的请求直接拒绝。
|
攻击类型 |
不限速时 |
限速后 |
|---|---|---|
|
CC 攻击 |
每秒 1000 请求打满后端 |
每秒只放 10 个,其余 503 |
|
暴力扫描目录 |
爬虫秒级扫完所有路径 |
每秒 2 个请求,扫描成本翻 500 倍 |
|
恶意爬虫 |
疯狂抓取内容 |
限制到人类正常速度 |
|
API 滥用 |
恶意刷接口 |
超出频率直接拒绝 |
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 详解
这是限速配置最容易搞混的部分:
|
参数 |
作用 |
不加的后果 |
|---|---|---|
burst=20 |
允许突发 20 个请求排队等待 |
超过 rate 的请求直接 503 |
nodelay |
排队的请求不延迟,立即处理 |
排队请求会延迟处理,用户体验差 |
各种组合的效果:
|
配置 |
行为 |
|---|---|
rate=10r/s
(无 burst) |
严格每秒 10 个,第 11 个立即 503 |
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;
}
}
|
接口类型 |
rate |
burst |
说明 |
|---|---|---|---|
|
普通页面 |
10r/s |
20 |
正常浏览 |
|
API 接口 |
5r/s |
10 |
前端轮询 |
|
登录接口 |
1r/s |
3 |
防暴力破解 |
|
下载接口 |
2r/s |
5 |
防带宽滥用 |
|
搜索接口 |
3r/s |
5 |
防恶意搜索 |
三、限制并发连接数——limit_conn 模块
3.1 limit_req vs limit_conn
|
模块 |
限制维度 |
适用场景 |
|---|---|---|
|
limit_req |
请求频率(每秒几次) |
CC 攻击、API 滥用 |
|
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)的原理是:攻击者建立大量连接,但每个请求都发得极慢(比如每分钟发一个字节),占满服务器的连接数,正常用户连不上。
|
攻击类型 |
原理 |
Nginx 防护 |
|---|---|---|
|
Slowloris |
大量半连接,请求头发得极慢 |
limit_conn + 超时配置 |
|
Slow POST |
发大 body 但传得极慢 |
client_body_timeout |
|
Slow Read |
读取响应极慢 |
client_header_timeout |
# 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;
}
|
超时参数 |
默认值 |
推荐值 |
说明 |
|---|---|---|---|
|
client_header_timeout |
60s |
30s |
请求头发送超时 |
|
client_body_timeout |
60s |
30s |
请求体发送超时 |
|
send_timeout |
60s |
30s |
响应发送超时 |
|
keepalive_timeout |
75s |
30-65s |
空闲连接超时 |
|
client_max_body_size |
1m |
按需设置 |
请求体最大大小 |
四、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 内网 + VPN,deny all |
|
内部 API |
allow 内网,deny all |
|
封禁已知恶意 IP |
deny 恶意IP,allow all |
|
只允许特定国家访问 |
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;
}
}
|
方法 |
用途 |
是否需要 |
|---|---|---|
|
GET |
获取资源 |
普通网站需要 |
|
POST |
提交数据 |
表单/API 需要 |
|
HEAD |
只取响应头 |
健康检查需要 |
|
PUT |
上传/更新资源 |
RESTful API |
|
DELETE |
删除资源 |
RESTful API |
|
TRACE |
调试用 |
必须禁止
(XST 攻击) |
|
CONNECT |
代理隧道 |
必须禁止 |
|
OPTIONS |
预检请求 |
CORS 需要 |
|
PATCH |
部分更新 |
RESTful API |
⚠️ 注意:
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/ |
源码泄露 |
deny all |
/.env |
数据库密码、API Key 泄露 |
deny all |
/.htaccess |
Apache 配置泄露 |
deny all |
/.htpasswd |
密码文件泄露 |
deny all |
/wp-admin/ |
WordPress 后台 |
IP 白名单 |
/phpmyadmin/ |
数据库管理工具 |
IP 白名单 |
/*.sql |
数据库备份泄露 |
deny all |
/*.bak |
备份文件泄露 |
deny all |
/*.log |
日志文件泄露 |
deny all |
🚨 警告:
.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;
}
}
|
valid_referers 参数 |
含义 |
|---|---|
none |
允许无 Referer(直接访问) |
blocked |
允许 Referer 被代理删除的请求 |
server_names |
允许 Referer 中的域名匹配 server_name |
*.example.com |
允许特定域名的子域 |
example.com |
允许特定域名 |
💡 提示: 如果图片需要被第三方 CDN 加载(比如又拍云、七牛云),把 CDN 域名加到
valid_referers里。valid_referers 是基础防护,不能完全防止盗链,对高价值资源考虑用签名 URL。
九、防 DDoS 基础
9.1 DDoS 攻击类型
|
类型 |
原理 |
Nginx 能防吗 |
|---|---|---|
|
CC 攻击 |
大量 HTTP 请求打满后端 |
✅ limit_req |
|
连接耗尽 |
大量连接占满 worker |
✅ limit_conn |
|
慢速攻击 |
慢速请求占满连接 |
✅ 超时配置 |
|
SYN Flood |
TCP 半连接洪泛 |
❌ 内核层防护(sysctl) |
|
UDP/ICMP 洪泛 |
带宽耗尽 |
❌ 需要 ISP/高防IP |
|
放大攻击 |
利用 DNS/NTP 放大流量 |
❌ 需要 ISP/高防IP |
⚠️ 注意: 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
|
参数 |
含义 |
推荐值 |
|---|---|---|
|
findtime |
统计时间窗口(秒) |
600(10 分钟) |
|
maxretry |
窗口内最大触发次数 |
10 |
|
bantime |
封禁时长(秒) |
7200(2 小时) |
# 查看 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;
}
|
协议 |
是否安全 |
处理 |
|---|---|---|
|
SSLv2 |
❌ 不安全 |
禁用 |
|
SSLv3 |
❌ 不安全 |
禁用(POODLE) |
|
TLSv1.0 |
❌ 不安全 |
禁用 |
|
TLSv1.1 |
❌ 不安全 |
禁用 |
|
TLSv1.2 |
✅ 安全 |
保留 |
|
TLSv1.3 |
✅ 安全 |
优先使用 |
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 |
包含 X-Frame-Options 等 |
|
限速生效 |
ab -n 100 -c 20 https://example.com/ |
超出部分返回 429 |
|
.git 已封禁 |
curl -I https://example.com/.git/config |
403 或 404 |
|
.env 已封禁 |
curl -I https://example.com/.env |
403 或 404 |
|
TRACE 方法已禁止 |
curl -X TRACE https://example.com |
405 |
|
HSTS 已启用 |
curl -I https://example.com |
包含 Strict-Transport-Security |
|
HTTPS 强制跳转 |
curl -I http://example.com |
301 到 https |
|
后台 IP 白名单 |
非白名单 IP 访问 /admin |
403 |
|
fail2ban 运行 |
sudo fail2ban-client status |
活跃 jail 列表 |
部署完成后用这些命令跑一遍,确认防护措施都生效了。
十三、常见问题排查
13.1 限速配置不生效
|
可能原因 |
检查方法 |
|---|---|
|
limit_req_zone 写在 server 块里 |
必须写在 http 块 |
|
limit_req 写在了 http 块 |
必须写在 server 或 location 块 |
|
没有 reload |
sudo nginx -s reload |
|
zone 名不匹配 |
检查 limit_req 的 zone 名和 zone 定义一致 |
|
burst 太大 |
burst 太大等于没限速 |
|
走了 CDN |
CDN 回源时 IP 是 CDN 的,需要用 |
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 不生效
|
可能原因 |
检查方法 |
|---|---|
|
规则顺序错误 |
allow/deny 从上到下匹配,先匹配先生效 |
|
走了反向代理 |
$remote_addr
是代理 IP,需配 real_ip |
|
location 优先级冲突 |
正则 location 优先级高于前缀 location |
13.4 fail2ban 不封禁
|
可能原因 |
检查方法 |
|---|---|
|
日志路径不对 |
确认 |
|
filter 正则不匹配 |
sudo fail2ban-regex /var/log/nginx/error.log /etc/fail2ban/filter.d/nginx-limit-req.conf |
|
findtime 太短 |
调大 findtime |
|
maxretry 太大 |
调小 maxretry |
|
iptables 规则被覆盖 |
sudo iptables -L -n
检查 fail2ban 链 |
十四、速查表
限速速查
|
指令 |
作用 |
推荐值 |
|---|---|---|
limit_req_zone ... rate= |
定义限速区域和频率 |
10r/s |
limit_req zone= ... burst= |
启用限速 |
burst=20 |
nodelay |
突发请求不延迟 |
加 |
limit_req_status |
限速响应码 |
429 |
封禁速查
|
方法 |
适用场景 |
示例 |
|---|---|---|
allow/deny |
少量 IP 白名单/黑名单 |
deny 1.2.3.4; |
geo
模块 |
大量 IP 段分类 |
geo $blocked { ... } |
fail2ban |
自动封禁高频攻击 IP |
监控日志 + iptables 封禁 |
|
iptables |
系统级 IP 封禁 |
iptables -A INPUT -s 1.2.3.4 -j DROP |
安全头速查
|
安全头 |
推荐值 |
|---|---|
|
X-Content-Type-Options |
nosniff |
|
X-Frame-Options |
SAMEORIGIN |
|
Strict-Transport-Security |
max-age=31536000; includeSubDomains |
|
Referrer-Policy |
strict-origin-when-cross-origin |
|
Content-Security-Policy |
按需配置 |
超时速查
|
参数 |
推荐值 |
作用 |
|---|---|---|
|
client_header_timeout |
30s |
请求头超时 |
|
client_body_timeout |
30s |
请求体超时 |
|
send_timeout |
30s |
响应发送超时 |
|
keepalive_timeout |
30s |
空闲连接超时 |
|
client_max_body_size |
2m |
请求体最大大小 |
防护层级速查
|
防护层 |
工具/配置 |
防护内容 |
|---|---|---|
|
应用层 L7 |
Nginx limit_req |
CC 攻击、API 滥用 |
|
应用层 L7 |
Nginx limit_conn |
连接耗尽、慢速攻击 |
|
应用层 L7 |
fail2ban |
自动封禁恶意 IP |
|
传输层 L4 |
iptables |
端口扫描、IP 封禁 |
|
内核层 |
sysctl |
SYN Flood、ICMP |
|
网络层 |
云服务商高防 IP / CDN |
大流量 DDoS |
小结
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,每一层都挡一部分攻击,叠加起来才能扛住真实流量。
第三层:CDN+WAF(低成本)
当攻击流量超过你的服务器带宽时,就得靠CDN扛了。
国内主流选择:Cloudflare(免费版就够用)、阿里云CDN+WAF、腾讯云EdgeOne。
关键配置:
1. 隐藏源站IP — 这是前提,否则攻击者绕过CDN直打源站,CDN白接了
2. 开启Under Attack模式 — Cloudflare一键开启,给每个访问者加5秒JS挑战
3. WAF规则 — 拦截SQL注入、XSS、命令注入等应用层攻击
怎么确认源站IP是否泄露?
# 检查历史DNS记录 nslookup -type=any yourdomain.com # 确保源站只允许CDN回源IP访问 iptables -A INPUT -p tcp --dport 443 -s 173.245.48.0/20 -j ACCEPT iptables -A INPUT -p tcp --dport 443 -j DROP
| 场景 | 推荐方案 | 月成本 |
|---|---|---|
| 个人博客 | 内核调优+Nginx限流 | 0元 |
| 中小业务 | +Cloudflare免费版 | 0元 |
| 电商/SaaS | 阿里云WAF+CDN | 500-2000元 |
| 大规模业务 | DDoS高防IP | 5000元起 |
第四层:专业DDoS防护(有成本但保命)
当攻击流量超过10Gbps,前三层都不够用了。这时候需要专业的DDoS清洗服务:
攻击来了的应急流程
1. 判断攻击类型 — tcpdump -i eth0 -c 100 -nn 抓包看是SYN Flood还是HTTP Flood
2. 启用已有防护 — 开启Under Attack模式,加iptables限流规则
3. 联系运营商 — 大流量攻击需要在上游清洗
4. 保留证据 — 抓包保存,后续分析和报警
5. 复盘 — 攻击结束后检查源站IP是否泄露,防护是否有效
DDoS不是防不住,而是你得提前做好准备。等攻击来了再想对策,已经晚了。
你的公司有没有被DDoS过?最后是怎么解决的?评论区分享一下经验,大家互相学习。
核心观点:DDoS防御是分层体系,从内核参数到CDN到专业清洗,4层防线逐级升级。核心是提前部署,而不是事后补救。
### 推荐标签
#DDoS防御 #网络安全 #流量清洗 #CDN防护 #iptables #Nginx限流 #运维实战
- 阿里云DDoS高防 — 国内最主流,10Gbps起步,按天计费
- 腾讯云大禹 — 弹性防护,攻击时自动扩容
- Cloudflare Enterprise — 国际业务首选,全球Anycast网络
openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构
更多推荐


所有评论(0)