恶意机器人无法阻止,改在应用前截断——在 nginx 阶段整理奇怪 URL
nginx blackhole.conf 整理奇怪 URL
当把 Web 应用暴露到互联网时,无论使用哪种框架,奇怪的请求都会蜂拥而至。
- 根本不存在的
/wp-admin/ - 甚至不写 PHP 也会出现
/index.php、/xmlrpc.php .git、.env、/cgi-bin/等敏感文件/目录wso.php、shell.php、test.php…
这不是你自己写的服务,而是全球扫描器和机器人在“是否存在漏洞?”时尝试的 URL。说实话,几乎没有服务器能完全免受这些请求。
幸运的是,如果应用层的防御机制做得好,绝大多数会被 400/404 正确处理。问题在于之后的后果。
- 日志被污染。真正用户请求之前,奇怪请求就占据了日志的一半。
- CPU 也被消耗。虽然“安全”但无谓的运算让人不安。
- 心情疲惫。每次查看日志看到
/wp-login.php占据数百行,疲劳感堆积。
因此,我把这些请求完全在应用前的 nginx 层截断,让它们在主机层直接进入“黑洞”。
本文将整理:
- 为什么在 nginx 进行一次性拦截很有用
- 如何用
blackhole.conf等文件统一管理拦截规则 - 实际可用的 nginx 配置示例
1. 只在应用层拦截的不足
我们常用的基本做法是:
- 路由/控制器不存在的 URL → 404
- 异常处理 → 400/500
- 日志 → APM / log collector 收集
功能上没问题,但从运维角度来看有几个痛点。
- 日志噪声 * APM、日志系统中的错误/404 比例被扫描器拉高。 * 每次都得人工判断“是真正用户还是扫描器”。
- 层级过低拦截 * 请求已进入应用,意味着已通过框架、middleware、路由层。 * 这时再想“为什么要让它到这里?”就显得多余。
- 细微但累计的资源消耗 * 单次无关紧要,但 24 小时持续的扫描流量会占用大量请求。
所以我倾向于在上层尽量清理这些无用请求。
2. 在 nginx 中创建黑洞:直接返回 444
nginx 有一个HTTP 标准之外的自定义状态码:
return 444;
- 不发送响应头也不发送正文
- 仅仅静默断开 TCP 连接
- 客户端只会看到“连接已断开”之类的提示
利用这一点,可以让“明显不合理的 URL”不返回任何响应,直接断开连接。
优点很简单:
- 请求不再到达应用层(框架 CPU 0)
- 根据配置可完全不写入 access 日志
- “奇怪 URL”在 nginx 层全部过滤,应用日志更干净
3. 用 blackhole.conf 统一管理规则
与其在每个 server 块里写规则,不如把公共模式集中在一个文件里。
例如:
# /etc/nginx/blackhole.conf(路径可自行调整)
# === 1. 隐藏/配置管理目录 (.git, .env, IDE 等) ===
location ~* (^|/)\.(git|hg|svn|bzr|DS_Store|idea|vscode)(/|$) { return 444; }
location ~* (^|/)\.env(\.|$) { return 444; }
location ~* (^|/)\.(?:bash_history|ssh|aws|npm|yarn)(/|$) { return 444; }
# === 2. 未使用的 CMS 管理/漏洞扫描 ===
location ^~ /administrator/ { return 444; } # Joomla 等
location ^~ /phpmyadmin/ { return 444; } # 未使用则彻底拦截
# === 3. PHP/CGI/WordPress 迹象 ===
# 仅在完全不使用 PHP 的栈(如纯 Node、Go、Python)推荐
location ~* \.(?:php\d*|phtml|phps|phar)(/|$) { return 444; }
location ^~ /cgi-bin/ { return 444; }
location ~* ^/(wp-admin|wp-includes|wp-content)(/|$) { return 444; }
# === 4. 扫描器常抓的文件/路径 ===
location ~* ^/(?:info|phpinfo|test|wso|shell|gecko|moon|root|manager|system_log)\.php(?:/|$)? {
return 444;
}
location ~* ^/(?:autoload_classmap|composer\.(?:json|lock)|package\.json|yarn\.lock|vendor)(?:/|$) {
return 444;
}
location ~* ^/(?:_profiler|xmrlpc|xmlrpc|phpversion)\.php(?:/|$)? {
return 444;
}
# === 5. 备份/临时/转储文件 ===
location ~* \.(?:bak|backup|old|orig|save|swp|swo|tmp|sql(?:\.gz)?|tgz|zip|rar)$ {
return 444;
}
# === 6. well-known: 仅允许 ACME,其他全部断开 ===
location ^~ /.well-known/acme-challenge/ {
root /var/www/letsencrypt;
}
location ^~ /.well-known/ {
return 444;
}
# === 7. 方法守卫(可选):几乎不使用的 HTTP 方法提前拦截 ===
# 如果不使用 TRACE、CONNECT、WebDAV 等方法,可提前阻止
if ($request_method !~ ^(GET|HEAD|POST|PUT|PATCH|DELETE|OPTIONS)$) {
return 405;
}
然后在每个 server 块里只需一行:
http {
# ...
server {
listen 80;
server_name example.com;
include /etc/nginx/blackhole.conf;
# 其余正常路由配置...
}
}
这样,大多数“未使用路径的漏洞扫描”在到达应用前就被 nginx 以 444 处理。
4. 如需进一步减少日志噪声(可选)
即使已使用黑洞,仍可让这些请求不写入 access_log。可以利用 map:
http {
# 需要进入黑洞的模式,想要减少日志
map $request_uri $drop_noise_log {
default 0;
~*^/(?:\.git|\.env) 1;
~*^/(wp-admin|wp-includes|cgi-bin) 1;
~*\.php(?:/|$) 1;
}
server {
listen 80;
server_name example.com;
include /etc/nginx/blackhole.conf;
# 仅当 $drop_noise_log 为 0 时记录日志
access_log /var/log/nginx/access.log combined if=$drop_noise_log;
}
}
$drop_noise_log为 1 的请求不记录日志。- 真实用户请求仍保持干净日志。
在生产环境中,建议先开启日志验证模式,确认“正常用户不会被误拦”,再切换到 if=$drop_noise_log。
5. 避免误拦的指南
在制定拦截规则时,最重要的是不要过度攻击性。可参考以下准则:
- 仅开启适合栈的规则
* 完全不使用 PHP 的项目可直接拦截
.php。 * 若使用 Laravel、WordPress,则需去掉.php规则。 * 同理,若实际使用/cgi-bin,则排除该规则。 - 初期可先返回 403
* 先用
return 403;观察几天,确认无正常用户访问后再改为 444。 - 对危险路径保守
*
.git、.env、备份文件、phpinfo.php等若泄露风险极高,直接 444 处理即可。 - 位置与优先级
* nginx 的
location匹配优先级(精确、前缀、正则)需注意。 * 通常把include blackhole.conf放在 server 开头,主路由location / { ... }放在后面即可。
6. 应用、WAF、nginx:各自擅长的层面
本文介绍的方式仅是一次性噪声清理。
- nginx 黑洞:早期剔除无意义、不可存在的路径
- 应用层验证:域名逻辑、权限、输入校验等业务专属安全
- WAF/安全方案:基于模式/签名的攻击、L7 DDoS、精细化机器人过滤
各层应互补而非替代。nginx 黑洞是实现成本低、效果显著的“小技巧”。
结语
任何暴露到互联网的服务都难以完全避免“奇怪 URL 扫描”。
但可以从视角转变:
“如果它一定会来,为什么不在应用前让它静默消失?”
通过 blackhole.conf 统一管理规则,并用 444 及时断开,
- 应用日志更干净
- 监控/分析更简洁
- 服务器资源略有释放
最重要的是,凌晨 2 点打开日志,看到的不是无休止的 /wp-login.php,而是真正用户的请求,体验会更愉快。
如果你已有 nginx,赶紧试试自己的 blackhole.conf,几天后你会想:
“我为什么没早点做?”

目前没有评论。