This is a case study of a small business website we designed and built. To see whether the design held up, we read its own edge logs over a 28-day window. Nothing here is a client’s data, and the site is not named. It is a Next.js application on Node behind a reverse proxy, and a worked example of how a modern site is actually attacked, and how building it carefully answered that.
Start with the single most-requested URL in those 28 days: /wp-admin/install.php, hit 518 times. The site has never run WordPress. There is no PHP anywhere in it. That one fact is the whole point.
The shape of the noise
Over 28 days the site absorbed roughly 8,900 targeted malicious requests. That is about 318 a day, from 373 distinct addresses. Every one of those addresses belonged to a cloud, hosting, or VPN network, the usual providers rented by the hour. None were residential. This is not people looking at your site. It is automated infrastructure spraying the whole internet with a fixed list of guesses.
| What the scanners hunted | Share | Typical paths |
|---|---|---|
| Secrets and config files | ~64% | /.env and variants, /.git/config, cloud credentials, backups |
| PHP panels and shells | ~22% | phpinfo, admin panels, dropped shells |
| WordPress | ~11% | /wp-admin/install.php, /xmlrpc.php |
| Database tools | ~1% | phpMyAdmin, exposed actuator endpoints |
The /.env probe alone arrived 228 times, before counting the dozen spelling variants the same bot tries in a row. Almost none of this traffic maps to a real weakness in a Next.js application. It is aimed at PHP and WordPress, which this site does not have. Every one of those requests resolved to a 404 or a 403. The volume is enormous and the danger is close to zero.
Why a wall of blocked requests is the wrong thing to feel good about
It is easy to watch the blocked-request counter climb and conclude the site is well defended. What that counter actually proves is that the site is not running the software the bots assumed. It is a comforting number that measures the wrong thing.
The traffic that can hurt a Next.js app does not look like this. It does not request a known path. It sends a well-formed request to a real endpoint, shaped by knowledge of the framework, and it leaves a log line that looks almost legitimate. To find that risk you stop reading the access log and start reading the advisory feed for your own stack.
The first real example is React2Shell, CVE-2025-55182: a CVSS 10.0 unauthenticated remote code execution in React Server Components, disclosed in December 2025, exploited in the wild within days, and quickly added to the US CISA Known Exploited Vulnerabilities catalog. A default Next.js app built for production was exploitable with no code change by the developer. One crafted request, code execution, no login. No configuration of the app itself closes it, and edge or WAF rules are only a stopgap; the only reliable fix is the patched release. Next.js issued a related advisory, CVE-2025-66478.
The second is the middleware authorization bypass, CVE-2025-29927. If your authentication or authorization logic lives in Next.js middleware, an attacker could skip it entirely by sending an internal header the framework trusted without checking its origin. Again: a well-formed request to a real route, no odd path, invisible in the noise.
These are the class of problem that matters for this stack: framework-level flaws reached through legitimate-looking requests. The plugin-hunting bots will never find them. A person who knows Next.js will.
What the build actually did about it
The largest category of noise was defeated by deployment hygiene. The /.env hunt is 64% of the targeted traffic, and it fails for one reason: the file it wants is not on the served filesystem. Secrets are injected as runtime environment variables. Nothing sensitive is written to a web-reachable path, and nothing is baked into the container image. When a scanner asks for /.env, there is nothing to hand it.
The edge refuses anything that is not a genuine request for the site. A blind scanner usually arrives with an IP address and no correct hostname, so requests carrying the wrong or missing host are turned away before they ever reach the application.
The framework flaws are handled by the one thing that works against them: patching fast. You cannot configure your way out of a React2Shell. What you can do is build so that updating is routine and the surface is small: dependencies scanned for known vulnerabilities on every build, kept to a minimal set, and a release process where shipping a patched version is a short, rehearsed step. When a 10.0 lands, the distance between the advisory and the fix is measured in hours, not weeks.
Optional attack surface is left off: features that are not needed are not enabled, so their flaws have nowhere to live. Content is served under a strict content-security policy that blocks inline and third-party scripts, which removes the most common way an injected script could run. The contact form is protected against automated submissions without a CAPTCHA, using lightweight server-side checks rather than a puzzle that tracks the visitor.
Almost none of this is a product you buy. It is a set of decisions made while the site is being written, which is the whole argument for building a site rather than assembling one.
Where this stops
It is worth being precise about the limits. This is secure development and application security: how the code, the configuration, and the deploy are built to reduce and survive attack. It is not managed monitoring, a security operations centre, network defence, or incident response as a service. The 28 days of logs were a one-time measurement to check whether the build held, not an ongoing watch. And a custom site is not secure by magic. It is only as secure as the people who build it. What building removes is the enormous, unmaintained third-party surface that most breaches actually come through. The framework flaws it still owns in full, which is exactly why the discipline is updating fast, not feeling safe.
The honest takeaway
The loud attacks in the logs are theatre. They hunt software you probably do not run, and a blocked-request counter is a poor measure of safety. The attacks that matter are quiet, framework-shaped, and only visible if you read your own advisory feed and build so that patching is cheap. Secure the boring things once, keep the surface small, and treat updating as a first-class habit.
Sources
React2Shell (CVE-2025-55182) — Google Cloud threat-intelligence blog
React2Shell technical analysis — Rapid7
Next.js middleware authorization bypass (CVE-2025-29927) — Datadog Security Labs
Top comments (0)