引言:Nginx 不只是反向代理
很多運(yùn)維工程師對(duì) Nginx 的認(rèn)知停留在"反向代理"和"負(fù)載均衡",但實(shí)際上 Nginx 在安全防護(hù)方面也相當(dāng)強(qiáng)大——限流可以防止 CC 攻擊和 API 濫用,黑白名單可以精準(zhǔn)控制訪(fǎng)問(wèn)來(lái)源,基礎(chǔ)安全配置可以防護(hù)常見(jiàn)的 Web 攻擊。
這篇文章系統(tǒng)講解 Nginx 的限流機(jī)制、黑白名單配置、以及生產(chǎn)環(huán)境推薦的安全配置,幫助你把 Nginx 打造成一道堅(jiān)實(shí)的安全防線(xiàn)。
前置知識(shí):Nginx 基礎(chǔ)配置、HTTP 協(xié)議基礎(chǔ)
實(shí)驗(yàn)環(huán)境:CentOS Stream 9 / Ubuntu 24.04 LTS,Nginx 1.26+
1 限流機(jī)制深度解析
1.1 限流的類(lèi)型
Nginx 限流類(lèi)型: │ ├── 請(qǐng)求速率限制 (rate limiting) │ └── limit_req:限制每秒/每分鐘請(qǐng)求數(shù) │ ├── 并發(fā)連接限制 (connection limiting) │ └── limit_conn:限制單個(gè) IP 的并發(fā)連接數(shù) │ ├── 帶寬限制 (bandwidth limiting) │ └── limit_rate:限制單連接傳輸速率 │ └── 白名單/黑名單 (access limiting) └── geo + map:基于 IP 的訪(fǎng)問(wèn)控制
1.2 限流算法原理
令牌桶算法(Token Bucket):
算法描述:
- 桶中最多存放 N 個(gè)令牌
- 每秒放入 R 個(gè)令牌
- 每個(gè)請(qǐng)求消耗 1 個(gè)令牌
- 桶滿(mǎn)時(shí),令牌溢出
示意圖:
令牌生成速率 R=10/秒
桶容量 B=20
時(shí)間 t=0: [桶滿(mǎn)了]
┌─────┐
│●●●●●│
│●●●●●●●●●●●│ 20個(gè)
│●●●●●●●●●●●●●●●│
└─────┘
時(shí)間 t=1: [消耗了10個(gè),放入10個(gè)]
┌─────┐
│●●●●●│
│●●●●●●●│ 20個(gè)
│●●●●●●●●●│
└─────┘
漏桶算法(Leaky Bucket):
算法描述:
- 請(qǐng)求以任意速率進(jìn)入桶
- 桶以固定速率漏水
- 桶滿(mǎn)時(shí),新請(qǐng)求被拒絕
示意圖:
請(qǐng)求輸入 (可變速率) 桶 (隊(duì)列) 輸出 (固定速率)
●●●● ┌─────┐ ●
●●●●●●●● ──────> │ ● │ ──────> ●●●
│ ●● │ ●●●
│ ●●●│ ●●●●
└─────┘
(桶滿(mǎn)則拒絕)
Nginxlimit_req使用令牌桶算法,limit_conn使用簡(jiǎn)單計(jì)數(shù)。
1.3 limit_req 配置詳解
# /etc/nginx/nginx.conf # 定義限流區(qū)域(共享內(nèi)存) # 語(yǔ)法:limit_req_zone key zone=name:size rate=rate; # - key: 用于識(shí)別請(qǐng)求的變量(通常是 $binary_remote_addr) # - zone: 區(qū)域名稱(chēng)和共享內(nèi)存大小 # - rate: 速率(r/s 或 r/m) limit_req_zone $binary_remote_addr zone=api_limit:10m rate=10r/s; limit_req_zone $binary_remote_addr zone=login_limit:10m rate=1r/m; limit_req_zone $binary_remote_addr zone=search_limit:10m rate=5r/s; # 按服務(wù)器維度限流 limit_req_zone $server_name zone=server_limit:10m rate=1000r/s; http { # 限流區(qū)域 limit_req_zone $binary_remote_addr zone=global:100m rate=100r/s; server { listen 80; server_name example.com; # 全局限流 limit_req zone=global burst=20 nodelay; # API 限流:每秒 10 請(qǐng)求,允許突發(fā) 50 location /api/ { limit_req zone=api_limit burst=50 nodelay; proxy_pass http://backend; } # 登錄限流:每分鐘 1 次,防止暴力破解 location /login { limit_req zone=login_limit burst=5 nodelay; limit_req_status 429; # 超限返回 429 proxy_pass http://backend; } # 搜索限流:每秒 5 請(qǐng)求 location /search { limit_req zone=search_limit burst=20 nodelay; proxy_pass http://backend; } } }
1.4 突發(fā)與排隊(duì)機(jī)制
# burst 參數(shù)詳解
# burst=N: 允許 N 個(gè)請(qǐng)求排隊(duì)
# nodelay: 不延遲排隊(duì)請(qǐng)求,立即處理
# 示例 1:允許突發(fā),不延遲
limit_req_zone $binary_remote_addr zone=test:10m rate=10r/s;
location / {
limit_req zone=test burst=20 nodelay;
}
# 效果:允許最多 30 個(gè)請(qǐng)求同時(shí)處理(10 個(gè)正常 + 20 個(gè)突發(fā))
# 示例 2:允許突發(fā),延遲處理
location / {
limit_req zone=test burst=20; # 超過(guò) 10r/s 的請(qǐng)求會(huì)延遲
}
# 效果:請(qǐng)求被延遲而不是拒絕,響應(yīng)變慢但不返回錯(cuò)誤
# 示例 3:不允許突發(fā)
location / {
limit_req zone=test burst=0; # 嚴(yán)格按速率處理
}
# 效果:超過(guò)速率的請(qǐng)求直接拒絕
# 示例 4:delay 參數(shù)(延遲突發(fā))
location / {
limit_req zone=test burst=20 delay=10;
}
# 效果:前 10 個(gè)突發(fā)請(qǐng)求正常處理,后續(xù)請(qǐng)求延遲
1.5 limit_req 日志控制
# 限流日志配置
limit_req_log_level info warn error;
server {
listen 80;
server_name example.com;
# 設(shè)置返回狀態(tài)碼
limit_req_status 503;
location /api/ {
limit_req zone=api_limit burst=50 nodelay;
# 觸發(fā)限流時(shí)記錄 warn 級(jí)別日志
proxy_pass http://backend;
}
}
2 限并發(fā)(limit_conn)配置
2.1 并發(fā)連接限制原理
# 定義并發(fā)限制區(qū)域
# 語(yǔ)法:limit_conn_zone key zone=name:size;
limit_conn_zone $binary_remote_addr zone=addr:10m;
limit_conn_zone $server_name zone=server_conn:10m;
server {
listen 80;
server_name example.com;
# 限制單個(gè) IP 最多 10 個(gè)并發(fā)連接
limit_conn addr 10;
# 限制整個(gè)服務(wù)器最多 1000 個(gè)并發(fā)連接
limit_conn server_conn 1000;
# 設(shè)置返回狀態(tài)碼
limit_conn_status 503;
location / {
proxy_pass http://backend;
}
}
2.2 針對(duì)特定資源的并發(fā)限制
# 下載站點(diǎn)并發(fā)限制
server {
listen 80;
server_name download.example.com;
# 整體并發(fā)限制
limit_conn addr 100;
location /download/ {
# 下載目錄限制為 5 個(gè)并發(fā)
limit_conn addr 5;
limit_conn_status 503;
alias /var/www/download/;
autoindex on;
}
location /video/ {
# 視頻限制為 2 個(gè)并發(fā)(節(jié)省帶寬)
limit_conn addr 2;
limit_conn_status 503;
alias /var/www/video/;
}
}
2.3 帶寬限制
server {
listen 80;
server_name example.com;
location / {
proxy_pass http://backend;
}
# 限制下載速度(字節(jié)/秒)
# 這里 500k = 512000 bytes/s
location /download/ {
alias /var/www/download/;
limit_rate_after 10m; # 前 10MB 不限速
limit_rate 500k; # 之后限速 500KB/s
}
# 根據(jù)狀態(tài)限制速度
map $status $limit_rate {
200 1m; # 成功響應(yīng)限速 1MB/s
302 5m; # 重定向限速 5MB/s
default 500k; # 其他情況限速 500KB/s
}
location /video/ {
alias /var/www/video/;
limit_rate $limit_rate;
}
}
3 黑白名單配置
3.1 基于 IP 的訪(fǎng)問(wèn)控制
server {
listen 80;
server_name example.com;
# 基本deny/allow 語(yǔ)法(舊方法,按順序匹配)
# 注意:舊方法按順序匹配,匹配到就停止
# 允許內(nèi)網(wǎng),deny 其他所有
allow 10.0.0.0/8;
allow 172.16.0.0/12;
allow 192.168.0.0/16;
deny all;
# 或者反過(guò)來(lái):拒絕某些IP
deny 192.168.1.100;
deny 192.168.1.200;
allow all;
location / {
proxy_pass http://backend;
}
}
3.2 geo 模塊實(shí)現(xiàn)動(dòng)態(tài)黑白名單
# geo 模塊:根據(jù) IP 地址映射值
geo $geo {
default 0;
# 白名單
10.0.0.0/8 1;
172.16.0.0/12 1;
192.168.0.0/16 1;
# 黑名單(高風(fēng)險(xiǎn) IP)
192.168.1.100 2;
192.168.1.200 2;
# 特定 IP
1.2.3.4 0; # 明確放行
5.6.7.8 99; # 明確拒絕
}
server {
listen 80;
server_name example.com;
# 黑名單直接拒絕
if ($geo = 99) {
return 403;
}
# 白名單特殊處理
if ($geo = 1) {
# 內(nèi)網(wǎng)用戶(hù)不限流
set $limit_rate 0;
}
location / {
# 黑名單用戶(hù)限制
if ($geo = 2) {
limit_rate 10k;
}
proxy_pass http://backend;
}
}
3.3 map 模塊實(shí)現(xiàn)復(fù)雜規(guī)則
# map 模塊:根據(jù)變量值映射到另一個(gè)值
# 定義 IP 名單文件
map $remote_addr $ip_whitelist {
default 0;
include /etc/nginx/whitelist.conf;
}
map $remote_addr $ip_blacklist {
default 0;
include /etc/nginx/blacklist.conf;
}
map $request_uri $rate_limit {
default 100r/s;
~* ^/api/v1/login 1r/m;
~* ^/api/v1/search 5r/s;
~* ^/admin 10r/s;
}
server {
listen 80;
server_name example.com;
# 黑名單拒絕
if ($ip_blacklist = 1) {
return 403;
}
# 限流
limit_req_zone $binary_remote_addr zone=api:10m rate=$rate_limit;
location / {
limit_req zone=api burst=50 nodelay;
proxy_pass http://backend;
}
}
3.4 維護(hù)模式實(shí)現(xiàn)
# 維護(hù)模式配置
map $ Maintenance_mode $limit {
default 0;
on 99; # 維護(hù)模式返回 99,即無(wú)限流
}
geo $whitelist {
default 0;
# 允許運(yùn)維人員 IP 訪(fǎng)問(wèn)
10.0.1.0/24 1;
192.168.1.100 1;
}
server {
listen 80;
server_name example.com;
# 維護(hù)開(kāi)關(guān)(可通過(guò) API 或文件控制)
set $maintenance false;
# 或者從文件讀取
# set_from_config_file $maintenance /var/www/maintenance.flag;
# 白名單在維護(hù)模式下仍可訪(fǎng)問(wèn)
if ($whitelist = 1) {
set $maintenance false;
}
if ($maintenance = true) {
return 503;
}
location / {
proxy_pass http://backend;
}
# 503 錯(cuò)誤時(shí)顯示維護(hù)頁(yè)面
error_page 503 @maintenance;
location @maintenance {
root /var/www/html;
rewrite ^(.*)$ /maintenance.html break;
internal;
}
}
4 黑白名單文件管理
4.1 名單文件格式
# /etc/nginx/whitelist.conf 格式 # 每行一個(gè) IP 或網(wǎng)段 10.0.0.0/8 172.16.0.0/12 192.168.0.0/16 203.0.113.0/24 123.45.67.89 # /etc/nginx/blacklist.conf 格式 # 被禁止的 IP 192.168.1.100 10.10.10.50 123.123.123.123 # 也可以是網(wǎng)段 192.168.100.0/24
4.2 動(dòng)態(tài)更新名單腳本
#!/bin/bash
# update_blacklist.sh - 自動(dòng)更新黑名單
BLACKLIST_FILE="/etc/nginx/blacklist.conf"
BLACKLIST_URL="https://api.example.com/blacklist/ipv4"
# 備份當(dāng)前黑名單
cp"$BLACKLIST_FILE""${BLACKLIST_FILE}.bak"
# 下載新黑名單
curl -s"$BLACKLIST_URL">"$BLACKLIST_FILE"
# 檢查文件是否有效
if[ $? -eq 0 ] && [ -s"$BLACKLIST_FILE"];then
# 測(cè)試 Nginx 配置
nginx -t
if[ $? -eq 0 ];then
# 重新加載 Nginx
nginx -s reload
echo"$(date): 黑名單已更新"
else
# 恢復(fù)備份
mv"${BLACKLIST_FILE}.bak""$BLACKLIST_FILE"
echo"$(date): Nginx 配置測(cè)試失敗,已恢復(fù)"
fi
else
echo"$(date): 下載失敗,已恢復(fù)"
mv"${BLACKLIST_FILE}.bak""$BLACKLIST_FILE"
fi
# 添加到 crontab echo"*/5 * * * * /usr/local/bin/update_blacklist.sh">> /var/spool/cron/root # 或者使用 systemd timer cat > /etc/systemd/system/nginx-blacklist.service <'EOF' [Unit] Description=Nginx Blacklist Update After=network.target [Service] Type=oneshot ExecStart=/usr/local/bin/update_blacklist.sh EOF cat > /etc/systemd/system/nginx-blacklist.timer <'EOF' [Unit] Description=Nginx Blacklist Update Timer Requires=nginx-blacklist.service [Timer] OnCalendar=*/5 * * * * Persistent=true [Install] WantedBy=timers.target EOF sudo systemctl?enable?--now nginx-blacklist.timer
5 基礎(chǔ)安全配置
5.1 隱藏版本號(hào)和指紋
server {
listen 80;
server_name example.com;
# 隱藏 Nginx 版本號(hào)
server_tokens off;
# 隱藏 PHP 版本號(hào)(需在 php-fpm 配置)
# /etc/php-fpm.d/www.conf
# php_admin_value[expose_php] = off
# 自定義響應(yīng)頭
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 "no-referrer-when-downgrade" always;
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline';" always;
# 移除暴露信息的響應(yīng)頭
proxy_set_header X-Powered-By "";
proxy_set_header X-AspNetMvc-Version "";
location / {
proxy_pass http://backend;
}
}
5.2 防止常見(jiàn)攻擊
# 防止 SQL 注入
if ($query_string ~* "union.*select.*(") {
return 403;
}
# 防止 XSS
if ($query_string ~* "
澄江县|
收藏|
郎溪县|
桃源县|
蓬莱市|
桐城市|
宝坻区|
壤塘县|
万载县|
景宁|
惠来县|
新巴尔虎左旗|
蕉岭县|
安吉县|
杂多县|
伊川县|
屏东县|
沾化县|
汉阴县|
抚州市|
宣汉县|
高碑店市|
南漳县|
阿瓦提县|
衢州市|
眉山市|
西盟|
溧阳市|
平武县|
天津市|
彝良县|
阿克|
建昌县|
临邑县|
喜德县|
伊宁县|
阿拉善盟|
灵川县|
囊谦县|
广宗县|
四子王旗|
Nginx的限流機(jī)制深度解析