feat(nginx): canonical-host guard + scanner-probe blocking
Deploy to VPS / deploy (push) Has been cancelled

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) <noreply@anthropic.com>
This commit is contained in:
2026-06-08 08:29:39 -05:00
parent 8a98f88047
commit 673c32d0e1
+46
View File
@@ -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;