Los bots maliciosos no se pueden detener. Cortémoslos antes de llegar a la app: limpiando URLs extrañas en nginx

Limpiar URLs extrañas con blackhole.conf de nginx

Cuando expones una aplicación web a Internet, sin importar el framework, se inundan de peticiones extrañas.

  • /wp-admin/ que no existe
  • /index.php, /xmlrpc.php que llegan aunque no uses PHP
  • .git, .env, /cgi-bin/ y otros archivos/directorios sensibles
  • wso.php, shell.php, test.php

En realidad son scanners y bots de todo el mundo que intentan descubrir vulnerabilidades. Si tu servidor no los rechaza, es casi seguro que está expuesto.

Afortunadamente, si la capa de aplicación está bien protegida, la mayoría de estas peticiones terminan en 400/404. El problema surge después:

  • El log se ensucia antes de que lleguen peticiones reales.
  • Consume algo de CPU con cálculos innecesarios.
  • Cansas al ver cientos de líneas de /wp-login.php en los logs.

Por eso prefiero cortarlas antes de que lleguen a la app: enviarlas a un “blackhole” a nivel de host.

En este artículo:

  • Por qué la primera capa de bloqueo en nginx es útil
  • Cómo gestionar reglas comunes con un archivo blackhole.conf
  • Ejemplo de configuración de nginx que puedes usar

1. Por qué es insuficiente bloquear solo en la aplicación



La práctica habitual es:

  • URL no en el router/controlador → 404
  • Excepciones → 400/500
  • Log → APM / colector de logs

Funcionalmente funciona, pero desde la operación hay varios inconvenientes:

  1. Ruido de logs * El ratio de errores/404 se inflama por los scanners. * Cada vez hay que distinguir visualmente si es un usuario real o un bot.
  2. Bloqueo demasiado bajo * La petición ya ha pasado por framework, middleware y router. * ¿Realmente necesita llegar tan lejos?
  3. Consumo acumulado * Un par de peticiones no importa, pero el tráfico de scanners 24/7 puede ser enorme.

Prefiero limpiar las peticiones “inútiles” en la capa superior.


2. Crear un blackhole en nginx: cerrar con 444

nginx tiene un código de estado propio que no está en el estándar HTTP:

return 444;
  • No envía cabeceras ni cuerpo.
  • Sólo cierra silenciosamente la conexión TCP.

Con esto, las URLs que consideremos 100 % extrañas pueden cerrarse sin responder.

Ventajas:

  • La aplicación nunca se ve afectada (CPU 0).
  • Se puede evitar registrar en access_log.
  • Los logs de la app quedan mucho más limpios.

3. Gestionar reglas con un solo blackhole.conf



En lugar de repetir reglas en cada bloque de servidor, mantengo un archivo blackhole.conf con patrones comunes.

# /etc/nginx/blackhole.conf (ruta libre)

# === 1. Directorios de configuración y archivos sensibles ===
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 y escáneres de vulnerabilidades ===
location ^~ /administrator/                                    { return 444; }  # Joomla, etc.
location ^~ /phpmyadmin/                                      { return 444; }

# === 3. PHP/CGI/WordPress ===
# Recomendado solo si no usas PHP
location ~* \.(?:php\d*|phtml|phps|phar)(/|$)                  { return 444; }
location ^~ /cgi-bin/                                         { return 444; }
location ~* ^/(wp-admin|wp-includes|wp-content)(/|$)          { return 444; }

# === 4. Archivos que los scanners buscan con frecuencia ===
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. Backups/temporal/dumps ===
location ~* \.(?:bak|backup|old|orig|save|swp|swo|tmp|sql(?:\.gz)?|tgz|zip|rar)$ {
    return 444;
}

# === 6. well-known: solo ACME, el resto se corta ===
location ^~ /.well-known/acme-challenge/ {
    root /var/www/letsencrypt;
}
location ^~ /.well-known/ {
    return 444;
}

# === 7. Método guard (opcional) ===
# Si no usas TRACE, CONNECT, WebDAV, etc., bloquea de antemano
if ($request_method !~ ^(GET|HEAD|POST|PUT|PATCH|DELETE|OPTIONS)$) {
    return 405;
}

Luego, en cada bloque de servidor, solo incluyes el archivo:

http {
    # ...

    server {
        listen 80;
        server_name example.com;

        include /etc/nginx/blackhole.conf;

        # Resto de rutas normales...
    }
}

Con esto, la mayoría de los escaneos de rutas no usadas se eliminan antes de llegar a la app.


4. Si quieres eliminar también el ruido de logs (opcional)

Puedes usar map para evitar registrar peticiones que van al blackhole.

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;

        access_log /var/log/nginx/access.log combined if=$drop_noise_log;
    }
}
  • $drop_noise_log = 1 → no se registra.
  • Solo las peticiones reales quedan en el log.

En producción, primero activa el log para validar los patrones, luego cambia a if=$drop_noise_log cuando estés seguro.


5. Guía para evitar falsos positivos

Lo más importante es no ser demasiado agresivo. Algunas recomendaciones:

  1. Activa reglas según tu stack * Si no usas PHP, bloquea .php. * Si usas Laravel/WordPress, no bloquees .php ni /wp-*. * Si tu sistema legacy usa /cgi-bin, exclúyelo.
  2. Empieza con 404/403 * Puedes usar return 403; antes de cambiar a 444. * Observa si usuarios reales usan esas URLs.
  3. Bloquea conservadormente los riesgos altos * .git, .env, backups, phpinfo.php son seguros de bloquear.
  4. Orden y prioridad * location en nginx tiene prioridades: exacto, prefijo, regex. * Coloca include blackhole.conf antes de la ruta principal (location /).

6. Nginx, la app y WAF: cada uno en su lugar

El método presentado es una eliminación de ruido inicial.

  • Nginx blackhole: corta rutas que nunca deben existir.
  • Validación a nivel de aplicación: lógica de dominio, permisos, validación de entrada.
  • WAF/solución de seguridad: patrones, firmas, DDoS L7, filtrado de bots.

Cada capa complementa a las demás. El blackhole de nginx es simple, efectivo y mejora la salud mental del equipo.


Conclusión

Para cualquier servicio expuesto a Internet, evitar los escaneos de URLs es casi imposible. Cambia la perspectiva: si vas a recibirlas, córtalas antes de que lleguen a la app.

Con un solo blackhole.conf y respuestas 444, obtienes:

  • Logs más limpios.
  • Análisis y monitoreo más sencillo.
  • Menor uso de recursos.

La experiencia de abrir los logs a las 2 a.m. y ver solo peticiones de usuarios reales es muy gratificante.

Si ya tienes nginx en producción, crea tu blackhole.conf y pruébalo. En pocos días notarás la diferencia.

image of nginx block malicious url