Malware‑bots kunnen niet gestopt worden. Laten we ze in de app blokkeren – hoe je vreemde URL’s op nginx‑niveau filtert

nginx blackhole.conf om vreemde URL’s te filteren

Wanneer je een webapplicatie op het internet zet, stroomt er ongeacht het framework een berg vreemde verzoeken binnen.

  • /wp-admin/ die niet bestaat
  • /index.php, /xmlrpc.php die je niet gebruikt
  • .git, .env, /cgi-bin/ en andere gevoelige bestanden/directories
  • wso.php, shell.php, test.php

Het zijn niet jouw eigen services, maar wereldwijde scanners en bots die “is er een kwetsbaarheid?” proberen. Eerlijk gezegd, er is geen server die hier niet door getroffen wordt.

Gelukkig, als de applicatielaag goed beveiligd is, worden de meeste verzoeken al als 400/404 afgehandeld. Het probleem begint daarna.

  • Log‑ruis – echte gebruikersverzoeken worden overschaduwd door de vreemde verzoeken.
  • CPU‑verbruik – het lijkt alsof er onnodige berekeningen plaatsvinden.
  • Moeheid – elke keer dat je de logs bekijkt, zie je honderden regels met /wp-login.php

Daarom kijk ik ernaar om alles al vóór de app te blokkeren – een “blackhole” op hostniveau.

In dit artikel bespreek ik:

  • Waarom een eerste blokkering op nginx‑niveau nuttig is
  • Hoe je een blackhole.conf kunt gebruiken om gemeenschappelijke regels te beheren
  • Praktische nginx‑configuratie‑voorbeelden

1. Waarom alleen in de applicatie blokkeren niet genoeg is



Onze standaard aanpak is:

  • URL’s die niet in router/controller staan → 404
  • Foutafhandeling → 400/500
  • Loggen → APM / logcollector

Functioneel is dit prima, maar operationeel zijn er nadelen:

  1. Log‑ruis * APM en logging systemen tonen een vertekend beeld van fout/404‑ratio’s. * Je moet telkens visueel bepalen of het een echte gebruiker of een scanner is.
  2. Te laag niveau * Het verzoek heeft de app al bereikt, wat betekent dat het door framework, middleware en routering is gegaan. * Vraag jezelf af: “Moet dit verzoek echt doorgaan?”
  3. Kleine, maar cumulatieve resource‑verbruik * Eén of twee verzoeken zijn niet erg, maar 24/7 scanner‑traffic kan veel verzoeken opbouwen.

Daarom geef ik de voorkeur aan het schoonmaken van verzoeken in een hoger niveau.


2. Een blackhole op nginx: direct 444 teruggeven

nginx heeft een eigen statuscode die niet in de HTTP‑standaard voorkomt:

return 444;
  • Geen response‑headers, geen body
  • Alleen de TCP‑verbinding wordt stilletjes verbroken
  • Voor de client lijkt het alsof de verbinding is verbroken

Met dit kun je “100% vreemde URL’s” meteen sluiten zonder een antwoord te sturen.

Voordelen:

  • De app wordt niet bereikt (CPU 0)
  • Access‑logs kunnen optioneel helemaal worden weggelaten
  • De app‑logs blijven schoon

3. Een blackhole.conf als centrale beheer‑patroon



In plaats van regels per serverblock te schrijven, maak ik een blackhole.conf en verzamel hier de gemeenschappelijke patronen.

# /etc/nginx/blackhole.conf (pad naar keuze)

# === 1. Verborgen/ontwikkel‑directories (.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. Niet gebruikte CMS‑admin/kwetsbaarheidsscan ===
location ^~ /administrator/                                    { return 444; }  # Joomla, etc.
location ^~ /phpmyadmin/                                      { return 444; }

# === 3. PHP/CGI/WordPress‑sporen ===
# Alleen aanbevolen voor stacks die geen PHP gebruiken (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. Veelgebruikte scanner‑paden ===
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/temporals/dumps ===
location ~* \.(?:bak|backup|old|orig|save|swp|swo|tmp|sql(?:\.gz)?|tgz|zip|rar)$ {
    return 444;
}

# === 6. .well-known: alleen ACME, rest blokken ===
location ^~ /.well-known/acme-challenge/ {
    root /var/www/letsencrypt;
}
location ^~ /.well-known/ {
    return 444;
}

# === 7. Method‑guard (optioneel): blokkeer zelden gebruikte methoden ===
if ($request_method !~ ^(GET|HEAD|POST|PUT|PATCH|DELETE|OPTIONS)$) {
    return 405;
}

Voeg vervolgens in elk serverblock een enkele regel toe:

http {
    # ...

    server {
        listen 80;
        server_name example.com;

        include /etc/nginx/blackhole.conf;

        # overige routing …
    }
}

Nu worden de meeste “onbenutte pad‑scans” al op nginx‑niveau afgehandeld.


4. Log‑ruis nog verder verminderen (optioneel)

Wil je dat verzoeken die in de blackhole gaan helemaal niet in access_log verschijnen, kun je een map gebruiken:

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 → geen log
  • Alleen echte gebruikersverzoeken worden gelogd

In productie kun je eerst de logs aanzetten om patronen te verifiëren, daarna if=$drop_noise_log gebruiken.


5. Richtlijnen om valse positieven te voorkomen

Belangrijkste punt: niet te agressief blokkeren.

  1. Pas regels aan je stack aan * Projecten zonder PHP: blokkeer .php volledig. * Laravel/WordPress: laat .php staan. * Legacy systemen die /cgi-bin gebruiken: sluit die regel uit.
  2. Begin met 404/403 * In eerste instantie kun je return 403; gebruiken, observeer of echte gebruikers er ooit op klikken. * Pas dan naar 444 als je zeker bent.
  3. Wees conservatief bij kritieke paden * .git, .env, back-ups, phpinfo.php zijn altijd te blokkeren.
  4. Locatie‑prioriteit * Plaats include blackhole.conf bovenaan het serverblock, maar onder eventuele globale location /‑regels.

6. App, WAF, nginx: elk op zijn eigen terrein

De aanpak in dit artikel is een eerste stap om noise te verminderen.

  • nginx blackhole: snijdt onnodige paden vroeg af.
  • App‑niveau: domeinlogica, autorisatie, input‑validatie.
  • WAF/veiligheidsoplossing: patroon‑ en signatuur‑detectie, L7‑DDoS, geavanceerde bot‑filtering.

Elk niveau complementeert elkaar; ze vervangen elkaar niet.


Conclusie

Voor een publiekelijk toegankelijke service is het praktisch onmogelijk om “vreemde URL‑scans” volledig te voorkomen.

Een andere benadering: laat de app nooit zien en sluit alles op nginx‑niveau.

Met een blackhole.conf en 444‑responses kun je:

  • App‑logs schoner maken
  • Monitoring en analyse vereenvoudigen
  • Serverresources vrijmaken

Het meest bevredigende moment? De log openen op 2 uur ’s nachts en alleen echte gebruikersverzoeken te zien, in plaats van een eindeloze rij /wp-login.php‑regels.

Als je een draaiende nginx hebt, maak dan nu een eigen blackhole.conf en test het. Binnen een paar dagen zul je je afvragen waarom je het niet eerder had gedaan.

image of nginx block malicious url