前言
最近在处理云平台服务器安全问题时,发现大量来自外部的扫描请求,目标直指 /actuator、/swagger-ui、/druid、/health、/metrics 等敏感路径。这些路径一旦暴露,黑客可能获取系统内部信息、配置详情甚至数据库监控页面,为进一步渗透提供便利。
本文将介绍一种通用的 Nginx 加固方案:批量在所有 server 块中插入安全规则,让外部访问这些敏感路径时直接返回 444(Nginx 特有的"关闭连接"响应码),从源头阻断扫描,降低被攻击风险。
方案适用于所有使用标准 Nginx 的 Linux 云服务器,不依赖特定云厂商,只需本地修改 Nginx 配置,风险可控。
为什么要这样做?
黑客在对云服务器发起攻击前,通常会进行自动化扫描,探测常见的管理端点、API 文档、监控页面等。例如:
/actuator:Spring Boot 应用监控端点,可能泄露 beans、env、health、metrics 等信息。/swagger-ui、/v2/api-docs:接口文档,暴露 API 结构和参数。/druid:阿里巴巴数据库连接池监控页面,若未授权可直接查看 SQL 执行情况。/health、/metrics:应用健康检查和指标信息,可能泄露内部状态。
这些路径如果直接返回 404 或 403,虽然也能拦截,但攻击者仍可判断服务器存在,并继续尝试其他路径。而 444 是 Nginx 自定义的非标准响应码,表示直接关闭连接,不返回任何 HTTP 响应,可以让扫描器认为目标不可达或异常,从而增加攻击难度,隐藏真实服务信息。
这样做的作用
- 批量防护 :自动扫描
/etc/nginx/conf.d/下所有.conf文件,为每个server块插入统一的敏感路径封锁规则,避免遗漏。 - 精确匹配 :规则使用正则匹配,覆盖
/actuator、/api/actuator、/swagger-ui及其子路径,防止绕过。 - 安全可控 :脚本执行前自动备份配置,并在修改后运行
nginx -t检查语法;若语法错误自动回滚,不影响线上服务。 - 无业务侵入 :只拦截指定的敏感路径,正常业务接口不受影响。同时脚本会跳过
upstream块内的server指令,不会误伤后端服务定义。
适用场景与前提
- 服务器使用标准 Nginx 作为反向代理或 Web 服务器。
- 配置文件位于
/etc/nginx/conf.d/目录下(可包含子目录)。 - 确认没有内部系统依赖这些敏感路径(例如监控探针、健康检查)。
- 云负载均衡器的健康检查使用 TCP 协议,不受 HTTP 444 影响。
完整操作步骤
第 1 步:备份现有 Nginx 配置
bash
cp -r /etc/nginx/conf.d /etc/nginx/conf.d.bak.$(date +%Y%m%d_%H%M%S)
执行后会生成类似 /etc/nginx/conf.d.bak.20260821_173000 的备份目录。
验证备份:
bash
ls -ld /etc/nginx/conf.d.bak.*
第 2 步:创建敏感接口封锁规则文件
bash
mkdir -p /etc/nginx/snippets
vim /etc/nginx/snippets/block_sensitive_paths.conf
按 i 进入插入模式,粘贴以下内容:
nginx
# 敏感接口统一封锁规则(修正正则,匹配子路径)
location ~* ^/actuator(/.*)?$ { access_log off; return 444; }
location ~* ^/api/actuator(/.*)?$ { access_log off; return 444; }
location ~* ^/(swagger-resources|v2/api-docs|v3/api-docs|swagger-ui|webjars/springfox)(/.*)?$ { access_log off; return 444; }
location ~* ^/api/(swagger-resources|v2/api-docs|v3/api-docs|swagger-ui|webjars/springfox)(/.*)?$ { access_log off; return 444; }
location ~* ^/(swagger-ui\.html|doc\.html)$ { access_log off; return 444; }
location ~* ^/api/(swagger-ui\.html|doc\.html)$ { access_log off; return 444; }
location ~* ^/(druid|h2-console|jolokia)(/.*)?$ { access_log off; return 444; }
location ~* ^/api/(druid|h2-console|jolokia)(/.*)?$ { access_log off; return 444; }
location ~* ^/(health|env|beans|configprops|mappings|metrics|heapdump|threaddump|logfile|loggers|shutdown|trace|autoconfig|dump)(/.*)?$ { access_log off; return 444; }
location ~* ^/api/(health|env|beans|configprops|mappings|metrics|heapdump|threaddump|logfile|loggers|shutdown|trace|autoconfig|dump)(/.*)?$ { access_log off; return 444; }
按 Esc,输入 :wq 保存退出。
第 3 步:创建批量插入脚本
bash
vim /root/apply_block_rules.py
按 i 进入插入模式,粘贴以下完整 Python 脚本:
python
#!/usr/bin/env python3
import os
import re
import subprocess
import shutil
import sys
from datetime import datetime
CONF_ROOT = "/etc/nginx/conf.d"
SNIPPET_DIR = "/etc/nginx/snippets"
SNIPPET_FILE = os.path.join(SNIPPET_DIR, "block_sensitive_paths.conf")
BACKUP_DIR = f"/etc/nginx/conf.d.bak.{datetime.now().strftime('%Y%m%d_%H%M%S')}"
INCLUDE_LINE = f" include {SNIPPET_FILE};\n"
def backup_configs():
print(f"[1/5] 备份配置到 {BACKUP_DIR}")
shutil.copytree(CONF_ROOT, BACKUP_DIR)
print("备份完成。")
def create_snippet():
print("[2/5] 创建规则文件...")
os.makedirs(SNIPPET_DIR, exist_ok=True)
content = """# 敏感接口统一封锁规则(修正正则,匹配子路径)
location ~* ^/actuator(/.*)?$ { access_log off; return 444; }
location ~* ^/api/actuator(/.*)?$ { access_log off; return 444; }
location ~* ^/(swagger-resources|v2/api-docs|v3/api-docs|swagger-ui|webjars/springfox)(/.*)?$ { access_log off; return 444; }
location ~* ^/api/(swagger-resources|v2/api-docs|v3/api-docs|swagger-ui|webjars/springfox)(/.*)?$ { access_log off; return 444; }
location ~* ^/(swagger-ui\\.html|doc\\.html)$ { access_log off; return 444; }
location ~* ^/api/(swagger-ui\\.html|doc\\.html)$ { access_log off; return 444; }
location ~* ^/(druid|h2-console|jolokia)(/.*)?$ { access_log off; return 444; }
location ~* ^/api/(druid|h2-console|jolokia)(/.*)?$ { access_log off; return 444; }
location ~* ^/(health|env|beans|configprops|mappings|metrics|heapdump|threaddump|logfile|loggers|shutdown|trace|autoconfig|dump)(/.*)?$ { access_log off; return 444; }
location ~* ^/api/(health|env|beans|configprops|mappings|metrics|heapdump|threaddump|logfile|loggers|shutdown|trace|autoconfig|dump)(/.*)?$ { access_log off; return 444; }
"""
with open(SNIPPET_FILE, 'w') as f:
f.write(content)
print(f"规则文件已创建: {SNIPPET_FILE}")
def get_conf_files():
files = []
for root, dirs, names in os.walk(CONF_ROOT):
for name in names:
if not name.endswith(".conf"):
continue
path = os.path.join(root, name)
if ".bak" in path or "/template/" in path:
continue
files.append(path)
return files
def is_server_block_start(lines, i):
"""判断第 i 行是否是一个真正的 server 块起始(排除 upstream 块内 server 指令)"""
line = lines[i]
stripped = line.strip()
if stripped.startswith('#'):
return False
# 形式1:server { (同一行)
if re.match(r'^\s*server\s*\{', line):
return True
# 形式2:纯 server 行,后换行 {
if re.match(r'^\s*server\s*$', line):
# 向后找到第一个非注释行,检查是否包含 {
k = i + 1
while k < len(lines):
s = lines[k].strip()
if s.startswith('#'):
k += 1
continue
if '{' in lines[k]:
return True
else:
return False
return False
return False
def insert_include_in_file(file_path):
with open(file_path, 'r', encoding='utf-8', errors='ignore') as f:
lines = f.readlines()
full = "".join(lines)
if SNIPPET_FILE in full:
return False, 0
new_lines = []
i = 0
modified = False
insert_count = 0
while i < len(lines):
line = lines[i]
if is_server_block_start(lines, i):
# 找到 server 块的 { 所在行,插入 include
# 如果是 server { 同在一行,则直接在该行后插入
# 如果是纯 server 行,{ 在后续行,则在找到 { 的那一行后插入
j = i
found_brace = False
while j < len(lines):
s = lines[j].strip()
if s.startswith('#'):
j += 1
continue
if '{' in lines[j]:
found_brace = True
break
# 不应该发生,安全起见中断
if j > i + 5: # 最多向后找5行
break
j += 1
if found_brace:
# 把从 i 到 j 的行原样加入
for k in range(i, j+1):
new_lines.append(lines[k])
# 在 j 行后插入 include
new_lines.append(INCLUDE_LINE)
insert_count += 1
i = j + 1
modified = True
else:
new_lines.append(line)
i += 1
else:
new_lines.append(line)
i += 1
if modified:
with open(file_path, 'w', encoding='utf-8') as f:
f.writelines(new_lines)
return modified, insert_count
def apply_rules():
print("[3/5] 批量插入 include ...")
files = get_conf_files()
modified_files = 0
total_inserts = 0
for fp in files:
mod, cnt = insert_include_in_file(fp)
if mod:
modified_files += 1
total_inserts += cnt
print(f" [+] 修改: {fp} (插入 {cnt} 个 include)")
print(f"处理完成:修改 {modified_files} 个文件,共插入 {total_inserts} 个 include。")
def test_nginx():
print("[4/5] 检查 Nginx 语法...")
result = subprocess.run(["nginx", "-t"], capture_output=True, text=True)
print(result.stdout)
print(result.stderr)
return result.returncode == 0
def reload_nginx():
print("[5/5] 重载 Nginx ...")
subprocess.run(["systemctl", "reload", "nginx"], check=True)
print("部署成功!")
def verify():
print("\n=== 验证引用情况 ===")
files = get_conf_files()
for fp in files:
with open(fp, 'r') as f:
content = f.read()
count = content.count(SNIPPET_FILE)
if count > 0:
print(f"{fp}: {count} 个 include")
print("验证完成。")
def rollback():
print("检测到语法错误,正在自动回滚...")
if os.path.exists(CONF_ROOT):
shutil.rmtree(CONF_ROOT)
shutil.copytree(BACKUP_DIR, CONF_ROOT)
print(f"已恢复配置,备份目录保留: {BACKUP_DIR}")
print("正在重新加载 Nginx 使回滚生效...")
subprocess.run(["systemctl", "reload", "nginx"], check=False)
def main():
backup_configs()
create_snippet()
apply_rules()
if test_nginx():
reload_nginx()
verify()
print("✅ 敏感路径封锁规则已生效。")
else:
rollback()
print("❌ 语法错误,已回滚并重载 Nginx。请检查配置。")
sys.exit(1)
if __name__ == "__main__":
main()
按 Esc,输入 :wq 保存退出。
第 4 步:执行脚本
bash
chmod +x /root/apply_block_rules.py
python3 /root/apply_block_rules.py
脚本会自动完成:
- 备份配置
- 创建规则文件
- 批量插入 include(准确识别
server {}块,忽略upstream内server指令、注释) - 检查 Nginx 语法
- 语法通过则重载,失败则自动回滚并重载旧配置
- 输出验证信息
第 5 步:手动验证(可选,但建议)
5.1 查看哪些文件包含规则引用
bash
grep -rl "block_sensitive_paths.conf" /etc/nginx/conf.d/
5.2 查看每个文件的 include 数量
bash
grep -rc "block_sensitive_paths.conf" /etc/nginx/conf.d/ | grep -v ':0'
5.3 测试敏感路径拦截
bash
curl -I http://127.0.0.1/actuator
curl -I http://127.0.0.1/actuator/health
如果返回 HTTP/1.1 444 或 curl 报连接被关闭,说明拦截成功。
回退步骤
如果脚本未自动回滚或需要手动恢复,可执行以下操作:
- 查看备份目录
bash
ls -td /etc/nginx/conf.d.bak.* | head -n 5
- 恢复备份(替换为实际备份目录名)
bash
rm -rf /etc/nginx/conf.d
cp -r /etc/nginx/conf.d.bak.20260821_173000 /etc/nginx/conf.d
- 检查并重载
bash
nginx -t && systemctl reload nginx
总结
通过以上步骤,我们可以在所有 Nginx server 块中统一加入敏感路径封锁规则,让外部访问 /actuator、/swagger-ui、/druid、/health、/metrics 等路径时直接被关闭连接(444),有效隐藏敏感信息,降低被扫描攻击的风险。
整个方案具备以下特点:
- 自动化:脚本自动备份、自动插入、自动检查、自动回滚。
- 安全性:只修改 Nginx 配置,不涉及应用代码;语法检查失败自动恢复。
- 通用性:适用于任何使用标准 Nginx 的 Linux 云服务器。
- 无侵入 :不影响正常业务,不干扰
upstream定义。
建议先在测试环境验证一遍,再上生产环境执行,确保万无一失。

效果
