Terug naar de blog
27 oktober 2025Sergei Solod5 min leestijd

Mijn statische Next.js-site krijgt dagelijks duizenden kwetsbaarheidsscans. Nginx merkt er nauwelijks iets van

Mijn logs staan vol probes naar WordPress, PHP-backdoors, .env en .git/config. Op mijn statische Next.js + Nginx-site eindigt het meeste als goedkope misses en 404’s, terwijl de CPU-belasting gelijk bleef. Dit is wat die ervaring werkelijk zegt over statische beveiliging — en wat niet.

BeveiligingNext.jsNginxDevOpsStatische hostingAttack Surface

Elke dag registreren mijn serverlogs duizenden geautomatiseerde kwetsbaarheidsscans. De requests zien er behoorlijk dreigend uit:

  • /wp-admin/wp.php
  • /111.php
  • /code.php
  • /.git/config
  • /i.php
  • /admin
  • /api/.env
  • /kal.php

Wat mij verraste was niet het aantal, maar hoe weinig de server ervan leek te merken. Terwijl ik dit verkeer bekeek, veranderde de CPU-belasting niet zichtbaar.

Die observatie klopt, maar mijn oorspronkelijke verklaring was te absoluut. Statische delivery is geen magisch schild en een 404 is niet gratis. Het echte voordeel van mijn architectuur is specifieker: veel opportunistische probes zijn goedkoop omdat er in het publieke request path bijna geen server-side applicatielogica zit die ze kunnen bereiken.

Dit zijn probes, geen bewijs van een compromise

Bij /wp-admin/wp.php of /111.php is het verleidelijk om elk request een “exploit” te noemen. Technisch is dat niet precies. Wat ik vooral zie is geautomatiseerd probing: bots proberen bekende bestandsnamen en endpoints en kijken wat reageert.

Sommige paden richten zich duidelijk op WordPress of PHP. Andere, zoals /.git/config en /api/.env, zoeken naar per ongeluk gepubliceerde configuratie of secrets. /admin is nog algemener en kan een normale route zijn. Een verdacht request vertelt mij waar de scanner naar zoekt, niet dat de kwetsbaarheid bestaat.

In mijn geval is de architectuur doorslaggevend: de publieke site bestaat uit vooraf gerenderde statische HTML van Next.js en wordt rechtstreeks door Nginx geserveerd.

Waarom dit verkeer op mijn stack goedkoop is

Voor de pagina’s waar ik het over heb staat er niet achter elk request een PHP-runtime of database. Nginx serveert bestanden die al bestaan. Vraagt een scanner /111.php op en bestaat dat bestand niet, dan start er geen PHP-applicatie, wordt er geen WordPress-code uitgevoerd en draait er geen databasequery. Het request mist en eindigt als 404.

Dat verandert het kostenmodel. In een dynamische applicatie kan een request routing, framework-code, authenticatie, rendering, databasewerk of andere services activeren. Mijn statische pad heeft veel minder bewegende delen. Nginx moet nog steeds de verbinding accepteren, HTTP verwerken, de resource opzoeken, loggen en antwoorden, maar de hoeveelheid applicatiewerk is klein.

Ik gebruik ook agressieve caching, wat helpt bij normale statische delivery. Toch zou ik niet elke willekeurige 404 aan de cache toeschrijven. Een nieuwe onzin-URI kan nog steeds de normale Nginx-verwerking en een filesystem lookup doorlopen voordat de 404 terugkomt.

De belangrijke correctie: statisch betekent niet “per definitie veilig”

Dit is het deel dat ik nu zorgvuldiger zou formuleren. Een statische architectuur haalt veel server-side componenten uit het publieke request path, maar maakt beveiligingswerk niet overbodig.

  • Een exposed file blijft exposed. Als .git, een .env-bestand, een backup of secret in een publiek directory terechtkomt, kan Nginx het net zo efficiënt serveren als HTML wanneer de configuratie dat niet blokkeert.
  • Nginx, TLS, het OS en het netwerk blijven attack surface. Ze moeten gepatcht en verstandig geconfigureerd blijven.
  • Client-side code kan nog steeds kwetsbaar zijn. Statische HTML maakt JavaScript, auth-logica in de browser of third-party scripts niet automatisch veilig.
  • Resource exhaustion blijft mogelijk. Verbindingen, bandbreedte, TLS-handshakes, worker-capaciteit, log-I/O en schijfruimte zijn eindig. Genoeg verkeer kan ook een statische site raken.

De juiste conclusie is dus niet “deze requests doen er niet toe”, maar: veel applicatiespecifieke probes vinden in deze architectuur simpelweg niets om uit te voeren.

“Duizenden scans per dag” kan erger klinken dan de werkelijke belasting

Logs maken Internet-background noise heel zichtbaar omdat iedere mislukte probe een regel wordt. Maar een dagtotaal zegt op zichzelf weinig over operationele impact. Enkele duizenden requests verspreid over 24 uur zijn heel anders dan hetzelfde aantal in een korte burst.

CPU is ook maar één signaal. In mijn geval bleef die gelijk, wat bruikbaar bewijs is dat dit verkeer op dat moment geen merkbare compute pressure veroorzaakte. Het bewijst niet dat er nergens anders kosten waren. Voor een goede beoordeling zou ik ook request rate, bandbreedte, actieve verbindingen, Nginx-workers, latency, logvolume en schijfgebruik bekijken.

Wat ik van dit achtergrondruis heb geleerd

De belangrijkste les voor mij is dat architectuur hele categorieën geautomatiseerde aanvallen irrelevant kan maken zonder voor elke scanner-signature een losse regel te schrijven. Een bot kan de hele dag WordPress- en PHP-backdoors zoeken; als het publieke request path geen WordPress of PHP bevat, hebben die probes weinig om mee te werken.

Maar ik verwissel “irrelevant voor mijn stack” niet meer met “onschuldig verkeer”. Mijn huidige checklist is simpel:

  • Weten welke runtimes en services echt bereikbaar zijn vanaf het publieke request path.
  • Secrets, repository metadata, backups en build artifacts buiten de web root houden of expliciet blokkeren.
  • Herhaalde 404-probes als normaal Internet-background noise behandelen zolang er geen ander bewijs van targeting of impact is.
  • Niet alleen naar CPU kijken voordat ik abusive traffic als gratis beschouw.
  • Nginx, OS, TLS-libraries en dependencies patchen, ook als de applicatie statisch is.

Wat deze ervaring niet bewijst

Ik heb geen gecontroleerde benchmark uitgevoerd en geen DDoS gesimuleerd. Ik heb production logs en serverbelasting geobserveerd terwijl er dagelijks duizenden scanner-requests binnenkwamen. Dat is genoeg voor een praktische conclusie over mijn setup, niet voor de universele regel dat “statisch altijd wint”.

Wat ik wel precies kan bevestigen: op mijn door Nginx geserveerde Next.js static export eindigde het scannerverkeer dat ik zag meestal als goedkope file misses en 404’s, in plaats van een PHP-runtime of database-backed applicatie te bereiken. De CPU-belasting bleef tijdens mijn observatie gelijk.

Dat is het resilience-voordeel waar ik om geef. Minder bewegende delen maken een systeem niet onkwetsbaar. Ze geven willekeurig Internetruis simpelweg minder plekken om in echt applicatiewerk te veranderen.