From 673c32d0e17f093b1ec2162956b00d4eb3eb02f0 Mon Sep 17 00:00:00 2001 From: DavidHerran Date: Mon, 8 Jun 2026 08:29:39 -0500 Subject: [PATCH] feat(nginx): canonical-host guard + scanner-probe blocking MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Hardens the edge against the bot noise and IP-based access seen in the production logs (raw-IP hits, SSRF probes to 169.254.169.254 / localhost / metadata.google.internal, scans for /config/database.php, /.git-credentials, wp-admin, etc.). 1. Canonical-host guard — default_server blocks on 80 and 443 that catch any Host that is NOT rf-flux.com/www.rf-flux.com and return 444 (drop). - Kills the redirect-to-raw-IP bug at the edge: IP requests never reach Next.js, so the middleware can't build an IP-based redirect. - Blocks SSRF probes and most bot scans before they touch the app. - ACME HTTP-01 still works (acme-challenge location kept on :80). - Legitimate traffic is unaffected: exact server_name beats default_server, so the rf-flux.com blocks always win. 2. Scanner-probe blocking — a regex location in the rf-flux.com server that returns 444 for .php/.env/.git/wp-admin/etc. This is a Next.js app so none of those are real; the patterns never match real assets (.jpg/.png/.webp/.mp4/.glb/.pdf) or app routes. Apply with `nginx -t` then `nginx -s reload` — no rebuild, no downtime. Co-Authored-By: Claude Opus 4.8 (1M context) --- nginx/conf.d/flux.conf | 46 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/nginx/conf.d/flux.conf b/nginx/conf.d/flux.conf index 473fe99..84e71d2 100644 --- a/nginx/conf.d/flux.conf +++ b/nginx/conf.d/flux.conf @@ -8,6 +8,41 @@ upstream nextjs { keepalive 32; } +# ───────────────────────────────────────────────────────────────────────── +# CANONICAL-HOST GUARD (default_server for ports 80 + 443) +# Catches every request NOT addressed to rf-flux.com / www.rf-flux.com — +# raw-IP access (135.125.53.234), SSRF probes (Host: 169.254.169.254, +# localhost, metadata.google.internal) and the bulk of bot scans that hit +# the bare IP. Returns 444 (drop the connection, send nothing). +# +# Legitimate traffic is unaffected: the rf-flux.com server blocks below win +# because an exact server_name match always beats default_server. +# ───────────────────────────────────────────────────────────────────────── +server { + listen 80 default_server; + server_name _; + + # Keep ACME HTTP-01 working so certbot can still renew on any host. + location /.well-known/acme-challenge/ { root /var/www/certbot; } + + location / { return 444; } +} + +server { + listen 443 ssl default_server; + http2 on; + server_name _; + + # A cert is required to complete the TLS handshake before the Host is + # known; reuse the rf-flux.com cert, then drop. Bots hitting the IP get + # a cert-name mismatch and a closed connection — nothing is proxied. + ssl_certificate /etc/letsencrypt/live/rf-flux.com/fullchain.pem; + ssl_certificate_key /etc/letsencrypt/live/rf-flux.com/privkey.pem; + ssl_protocols TLSv1.2 TLSv1.3; + + return 444; +} + # Legacy domain redirect — anyone landing on lethepowerflux.com lands on # the canonical https://www.rf-flux.com host instead. SEO-safe 301. server { @@ -55,6 +90,17 @@ server { add_header Referrer-Policy "strict-origin-when-cross-origin" always; add_header Permissions-Policy "camera=(), microphone=(), geolocation=()" always; + # ── Scanner / exploit-probe blocking ──────────────────────────────── + # This is a Next.js app — it has no PHP, no .env/.git served over HTTP, + # no wp-admin. Any request for those paths is a bot probing for secrets + # or exploits. Drop them cheaply with 444 before they touch the app. + # The patterns are scanner-specific and never match real assets + # (.jpg/.png/.webp/.mp4/.glb/.pdf/.svg) or app routes. + location ~* (?:\.(?:php|phtml|asp|aspx|jsp|cgi|env|sql|bak|ini|sh|yml|yaml|conf)$|/\.(?:git|env|aws|ssh|svn|hg|idea|vscode)|/(?:wp-admin|wp-login|wordpress|phpmyadmin|xmlrpc)) { + return 444; + access_log off; + } + # Next.js bundles use content hashing — safe to cache forever location /_next/static/ { proxy_pass http://nextjs;