Index: src/middleware/rate-limit.js
===================================================================
--- src/middleware/rate-limit.js	(revision 7bc636b391c66ac399c33e54f7173a022c6a3cbd)
+++ src/middleware/rate-limit.js	(revision c88783ea96376fcf4e77aedc4e82bad5d86fbaf0)
@@ -13,4 +13,15 @@
 import rateLimit from 'express-rate-limit';
 import { renderPage } from './render.js';
+
+// Achter Cloudflare/Caddy kan req.ip binnenkomen als "1.2.3.4:11046" (IPv4 met
+// poort). express-rate-limit v7 valideert het IP en gooit anders
+// ERR_ERL_INVALID_IP_ADDRESS — onafgevangen async → het proces crasht (en pm2
+// loopt in een restart-loop). Strip een trailing IPv4-poort, val terug op de
+// socket, en laat IPv6 (meerdere dubbele punten) ongemoeid.
+function clientKey(req) {
+  let ip = req.ip || req.socket?.remoteAddress || '';
+  if (/^\d{1,3}(\.\d{1,3}){3}:\d+$/.test(ip)) ip = ip.split(':')[0];
+  return ip || 'unknown';
+}
 
 function blockedHandler(viewName, bodyClass, friendlyMsg) {
@@ -42,4 +53,6 @@
   standardHeaders: true,
   legacyHeaders: false,
+  keyGenerator: clientKey,
+  validate: { ip: false },
   // Only count failed attempts. Successful logins don't burn the budget.
   skipSuccessfulRequests: true,
@@ -52,4 +65,6 @@
   standardHeaders: true,
   legacyHeaders: false,
+  keyGenerator: clientKey,
+  validate: { ip: false },
   skipSuccessfulRequests: false,   // any attempt counts (registration spam is the concern)
   handler: blockedHandler('pages/auth-register', 'on-special', 'Too many signup attempts.'),
