{"id":154,"date":"2026-06-15T16:47:56","date_gmt":"2026-06-15T16:47:56","guid":{"rendered":"https:\/\/shattered.io\/it\/2026\/06\/15\/rate-limiting-nodejs\/"},"modified":"2026-06-15T16:49:19","modified_gmt":"2026-06-15T16:49:19","slug":"rate-limiting-nodejs","status":"publish","type":"post","link":"https:\/\/shattered.io\/it\/2026\/06\/15\/rate-limiting-nodejs\/","title":{"rendered":"Rate Limiting in Node.js: API Sicura in 12 Step [2026]"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\">Un singolo client pu\u00f2 inviare migliaia di richieste al secondo a un&#8217;API Node.js non protetta. Senza un controllo del traffico, quel client esaurisce CPU, memoria e connessioni al database, degrada il servizio per tutti gli altri utenti e apre la porta ad attacchi brute force sui form di login. Il <strong>rate limiting<\/strong> \u00e8 la difesa pi\u00f9 semplice ed efficace contro questo scenario, e nel 2026 \u00e8 considerato un controllo di base in ogni guida alla sicurezza delle API Node.js.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Questo tutorial ti guida, in 12 step pratici, dalla configurazione di un limiter di base con <code>express-rate-limit<\/code> fino a un sistema distribuito basato su Redis, pronto per la produzione. Tempo di completamento stimato: circa 45 minuti. Alla fine avrai un progetto Express funzionante con limiti differenziati per endpoint, protezione contro gli attacchi di forza bruta, header conformi agli standard IETF e una gestione corretta dei proxy. Tutto il codice \u00e8 testato su Node.js 22 LTS.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"cose-il-rate-limiting-e-perche-e-critico-nel-2026\">Cos&#8217;\u00e8 il rate limiting e perch\u00e9 \u00e8 critico nel 2026<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Il rate limiting \u00e8 la tecnica che limita il numero di richieste che un client (identificato per indirizzo IP, chiave API o ID utente) pu\u00f2 inviare a un server in una finestra di tempo definita. Quando il client supera la soglia, il server risponde con lo stato HTTP <code>429 Too Many Requests<\/code> invece di elaborare la richiesta. \u00c8 un meccanismo di protezione delle risorse, non di autenticazione: non decide <em>chi<\/em> pu\u00f2 accedere, ma <em>quanto spesso<\/em>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">L&#8217;OWASP classifica l&#8217;assenza di questi controlli come <strong>API4:2023 Unrestricted Resource Consumption<\/strong>, una delle dieci vulnerabilit\u00e0 pi\u00f9 gravi delle API moderne. Senza limiti, un attaccante pu\u00f2 esaurire le risorse di calcolo, generare costi cloud incontrollati e rendere indisponibile il servizio. Le linee guida sulla sicurezza Node.js del 2026 trattano il rate limiting come un livello standard, allo stesso titolo di autenticazione, validazione degli input, CORS, header di sicurezza e HTTPS.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">La tempistica conta. Il progetto Node.js ha annunciato il rilascio di patch di sicurezza per le linee attive 26.x, 24.x e 22.x a partire dal 17 giugno 2026, con severit\u00e0 massima dichiarata HIGH. In un periodo di aggiornamenti frequenti, mantenere un livello di difesa applicativo come il rate limiting riduce la finestra di esposizione mentre si pianificano i rebuild dei container e i test di regressione. Per chi sviluppa per il mercato italiano ed europeo, queste protezioni si inseriscono anche nel quadro normativo di NIS2 e DORA, che richiedono misure tecniche adeguate contro l&#8217;abuso dei servizi.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Il rate limiting protegge da quattro categorie di problemi concreti: gli attacchi brute force e credential stuffing contro gli endpoint di autenticazione, lo scraping massivo dei contenuti, gli abusi che generano costi (invio di email, SMS, chiamate a servizi a pagamento) e i picchi di traffico accidentali causati da client mal configurati. In tutti questi casi, la soglia agisce come una valvola di sicurezza.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"i-quattro-algoritmi-di-rate-limiting-a-confronto\">I quattro algoritmi di rate limiting a confronto<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Prima di scrivere codice, devi capire quale algoritmo usare. La scelta determina la precisione del conteggio, l&#8217;uso della memoria e il comportamento durante i picchi di traffico. Esistono quattro famiglie principali, e le librerie Node.js le implementano tutte.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"fixed-window-sliding-window-token-bucket-e-leaky-bucket\">Fixed window, sliding window, token bucket e leaky bucket<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Il <strong>fixed window<\/strong> conta le richieste in intervalli fissi (per esempio 100 richieste ogni 60 secondi). \u00c8 semplice e veloce, ma soffre del problema del &#8220;bordo della finestra&#8221;: un client pu\u00f2 inviare 100 richieste alla fine di una finestra e altre 100 all&#8217;inizio della successiva, raddoppiando di fatto il limite in pochi istanti. Lo <strong>sliding window<\/strong> risolve questo difetto calcolando una finestra mobile che scorre con il tempo, distribuendo il conteggio in modo pi\u00f9 uniforme al costo di un calcolo leggermente pi\u00f9 oneroso.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Il <strong>token bucket<\/strong> riempie un secchio con un numero fisso di token a un ritmo costante: ogni richiesta consuma un token e, quando il secchio \u00e8 vuoto, le richieste vengono rifiutate. Permette picchi controllati (burst) fino alla capacit\u00e0 del secchio, ed \u00e8 l&#8217;algoritmo preferito per le API pubbliche. Il <strong>leaky bucket<\/strong> elabora le richieste a un ritmo costante in uscita, come un secchio che perde acqua da un foro: appiana i picchi forzando un flusso regolare, ideale quando il sistema a valle ha una capacit\u00e0 di elaborazione fissa.<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table><thead><tr><th>Algoritmo<\/th><th>Precisione<\/th><th>Gestione burst<\/th><th>Memoria<\/th><th>Caso d&#8217;uso tipico<\/th><\/tr><\/thead><tbody><tr><td>Fixed window<\/td><td>Bassa (problema del bordo)<\/td><td>Permette picchi al bordo<\/td><td>Minima (1 contatore)<\/td><td>Limiti grezzi, prototipi<\/td><\/tr><tr><td>Sliding window<\/td><td>Alta<\/td><td>Uniforme<\/td><td>Media<\/td><td>API generiche in produzione<\/td><\/tr><tr><td>Token bucket<\/td><td>Alta<\/td><td>Burst controllato<\/td><td>Bassa (2 valori)<\/td><td>API pubbliche, quote utente<\/td><\/tr><tr><td>Leaky bucket<\/td><td>Alta<\/td><td>Appiana i picchi<\/td><td>Media (coda)<\/td><td>Code verso servizi a capacit\u00e0 fissa<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">Per la maggior parte delle API la combinazione vincente \u00e8 uno sliding window per il traffico generale e un token bucket per le quote per utente. La libreria <code>express-rate-limit<\/code> implementa un fixed window efficiente per default, mentre <code>rate-limiter-flexible<\/code> offre token bucket, sliding window e blocco progressivo. Useremo entrambe in questo tutorial.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"prerequisiti-e-versioni-del-software\">Prerequisiti e versioni del software<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Prima di iniziare, verifica di avere installato gli strumenti corretti. Le versioni indicate sono quelle testate per questo tutorial; usa sempre l&#8217;ultima release stabile di ogni componente quando possibile.<\/p>\n\n\n\n<ul class=\"wp-block-list\"><li><strong>Node.js 22 LTS<\/strong> o versione successiva (consigliata una linea LTS attiva con patch aggiornate)<\/li><li><strong>npm 10<\/strong> o superiore, incluso con Node.js<\/li><li><strong>Express 4.x o 5.x<\/strong> come framework web<\/li><li><strong>express-rate-limit<\/strong> versione 7 per il rate limiting in-memory<\/li><li><strong>rate-limiter-flexible<\/strong> per gli algoritmi avanzati e il blocco progressivo<\/li><li><strong>rate-limit-redis<\/strong> come store distribuito per express-rate-limit<\/li><li><strong>ioredis<\/strong> come client Redis<\/li><li><strong>Redis 7<\/strong> o superiore, in locale o gestito (necessario solo dallo step 6 in poi)<\/li><\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">Verifica le versioni di Node.js e npm dal terminale:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>$ node --version\nv22.14.0\n\n$ npm --version\n10.9.2<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Per gli step distribuiti ti serve un&#8217;istanza Redis. Il modo pi\u00f9 rapido in ambiente di sviluppo \u00e8 Docker:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>$ docker run -d --name redis-ratelimit -p 6379:6379 redis:7-alpine\n\n$ docker exec -it redis-ratelimit redis-cli ping\nPONG<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Se ricevi <code>PONG<\/code>, Redis \u00e8 pronto. Una conoscenza di base di Express e delle Promise di JavaScript \u00e8 utile ma non indispensabile, perch\u00e9 ogni step \u00e8 commentato. Se parti da zero con la sicurezza delle API, la nostra guida all&#8217;<a href=\"\/it\/autenticazione-jwt-nodejs\/\">autenticazione JWT in Node.js<\/a> \u00e8 un buon punto di partenza complementare.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"step-1-e-2-inizializzare-il-progetto-express\">Step 1 e 2: inizializzare il progetto Express<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Crea una cartella per il progetto e inizializza npm. Useremo i moduli ES (import\/export), quindi imposta <code>\"type\": \"module\"<\/code> nel <code>package.json<\/code>.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>$ mkdir api-rate-limit &amp;&amp; cd api-rate-limit\n$ npm init -y\n$ npm pkg set type=module\n$ npm install express express-rate-limit<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Crea il file <code>server.js<\/code> con un server Express minimale e due endpoint: uno pubblico e uno che simula un&#8217;operazione sensibile. Questa \u00e8 la base su cui aggiungeremo i limiti.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ server.js\nimport express from 'express';\n\nconst app = express();\napp.use(express.json());\n\napp.get('\/api\/pubblico', (req, res) =&gt; {\n  res.json({ messaggio: 'Endpoint pubblico, traffico elevato consentito' });\n});\n\napp.post('\/api\/login', (req, res) =&gt; {\n  \/\/ Simulazione: in produzione qui verifichi le credenziali\n  res.json({ messaggio: 'Tentativo di login ricevuto' });\n});\n\nconst PORT = process.env.PORT || 3000;\napp.listen(PORT, () =&gt; {\n  console.log(`Server in ascolto sulla porta ${PORT}`);\n});<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Avvia il server con <code>node server.js<\/code> e verifica che risponda:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>$ curl http:\/\/localhost:3000\/api\/pubblico\n{\"messaggio\":\"Endpoint pubblico, traffico elevato consentito\"}<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">A questo punto il server accetta richieste illimitate. Chiunque pu\u00f2 inviare migliaia di POST a <code>\/api\/login<\/code> e provare combinazioni di credenziali senza alcun freno. Risolviamo subito questo problema.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"step-3-il-primo-limiter-con-express-rate-limit\">Step 3: il primo limiter con express-rate-limit<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">La libreria <code>express-rate-limit<\/code> \u00e8 il modo pi\u00f9 diretto per aggiungere un limite globale. Funziona come middleware Express: lo crei una volta e lo applichi a tutte le rotte. Il limiter di default usa un algoritmo fixed window con uno store in-memory.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ server.js (aggiornato)\nimport express from 'express';\nimport { rateLimit } from 'express-rate-limit';\n\nconst app = express();\napp.use(express.json());\n\n\/\/ Limite globale: 100 richieste ogni 15 minuti per IP\nconst limiterGlobale = rateLimit({\n  windowMs: 15 * 60 * 1000, \/\/ 15 minuti\n  limit: 100,               \/\/ massimo 100 richieste per finestra\n  standardHeaders: 'draft-8',\n  legacyHeaders: false,\n  message: { errore: 'Troppe richieste, riprova piu tardi.' },\n});\n\napp.use(limiterGlobale);\n\napp.get('\/api\/pubblico', (req, res) =&gt; {\n  res.json({ messaggio: 'Endpoint pubblico' });\n});<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Il parametro <code>windowMs<\/code> definisce la durata della finestra in millisecondi, mentre <code>limit<\/code> (in passato chiamato <code>max<\/code>) fissa il numero massimo di richieste. Quando un IP supera la soglia, il middleware interrompe la catena e restituisce automaticamente lo stato 429 con il messaggio configurato. Tutte le richieste successive nella stessa finestra ricevono lo stesso errore finch\u00e9 il contatore non si azzera.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Riavvia il server e prova a superare il limite con un ciclo. Dopo la centesima richiesta vedrai la risposta cambiare:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>$ for i in $(seq 1 101); do curl -s -o \/dev\/null -w \"%{http_code} \" http:\/\/localhost:3000\/api\/pubblico; done\n200 200 200 ... 200 429<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">La centunesima richiesta restituisce 429. Hai appena protetto l&#8217;intera API con poche righe di codice. Nei prossimi step renderemo questo controllo molto pi\u00f9 preciso e adatto alla produzione.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"step-4-header-standard-e-comunicazione-del-limite\">Step 4: header standard e comunicazione del limite<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Un&#8217;API ben progettata comunica al client quanto traffico residuo ha a disposizione. Lo standard IETF definisce i campi header <code>RateLimit<\/code> proprio per questo scopo. Impostando <code>standardHeaders: 'draft-8'<\/code> abiliti gli header conformi all&#8217;ultima bozza, mentre <code>legacyHeaders: false<\/code> disattiva i vecchi header <code>X-RateLimit-*<\/code> non standard.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Ispeziona gli header di una risposta per vederli in azione:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>$ curl -i http:\/\/localhost:3000\/api\/pubblico\nHTTP\/1.1 200 OK\nRateLimit-Policy: 100;w=900\nRateLimit-Limit: 100\nRateLimit-Remaining: 99\nRateLimit-Reset: 900\nContent-Type: application\/json; charset=utf-8<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">L&#8217;header <code>RateLimit-Remaining<\/code> indica quante richieste restano nella finestra corrente, mentre <code>RateLimit-Reset<\/code> dice tra quanti secondi il contatore si azzera. Quando arriva la risposta 429, il middleware aggiunge anche l&#8217;header <code>Retry-After<\/code>, che i client ben educati rispettano prima di ritentare. Comunicare questi valori riduce le richieste inutili: un client SDK pu\u00f2 attendere il tempo indicato invece di martellare il server.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Esporre questi header \u00e8 una buona pratica documentata anche dalla guida HTTP di Mozilla per lo stato 429. Evita per\u00f2 di esporre header che rivelano la tua infrastruttura interna, e ricorda che gli header da soli non sostituiscono il rifiuto effettivo della richiesta: sono solo un segnale informativo per i client legittimi.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"step-5-limiti-differenziati-per-endpoint\">Step 5: limiti differenziati per endpoint<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Un limite globale \u00e8 un punto di partenza, ma non tutti gli endpoint sono uguali. Una rotta di login deve essere molto pi\u00f9 restrittiva di una rotta di lettura pubblica, perch\u00e9 \u00e8 il bersaglio degli attacchi brute force. La soluzione \u00e8 creare limiter specifici e applicarli alle singole rotte.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ limiter di autenticazione: solo 5 tentativi ogni 15 minuti\nconst limiterAuth = rateLimit({\n  windowMs: 15 * 60 * 1000,\n  limit: 5,\n  standardHeaders: 'draft-8',\n  legacyHeaders: false,\n  skipSuccessfulRequests: true, \/\/ conta solo i login falliti\n  message: { errore: 'Troppi tentativi di accesso. Riprova tra 15 minuti.' },\n});\n\n\/\/ limiter per la creazione di risorse: 20 ogni ora\nconst limiterScrittura = rateLimit({\n  windowMs: 60 * 60 * 1000,\n  limit: 20,\n  standardHeaders: 'draft-8',\n  legacyHeaders: false,\n});\n\napp.post('\/api\/login', limiterAuth, (req, res) =&gt; {\n  res.json({ messaggio: 'Tentativo di login ricevuto' });\n});\n\napp.post('\/api\/risorse', limiterScrittura, (req, res) =&gt; {\n  res.status(201).json({ messaggio: 'Risorsa creata' });\n});<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">L&#8217;opzione <code>skipSuccessfulRequests: true<\/code> \u00e8 particolarmente utile sul login: conta solo le richieste che falliscono (risposta 4xx o 5xx), cos\u00ec un utente legittimo che accede correttamente non consuma il proprio budget. Per far funzionare questo conteggio devi restituire uno stato di errore appropriato quando le credenziali sono sbagliate, ad esempio <code>res.status(401)<\/code>.<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table><thead><tr><th>Endpoint<\/th><th>Finestra<\/th><th>Limite<\/th><th>Strategia<\/th><th>Rischio mitigato<\/th><\/tr><\/thead><tbody><tr><td>GET pubblico<\/td><td>15 min<\/td><td>100<\/td><td>Globale<\/td><td>Scraping, picchi accidentali<\/td><\/tr><tr><td>POST \/login<\/td><td>15 min<\/td><td>5<\/td><td>Solo fallimenti<\/td><td>Brute force, credential stuffing<\/td><\/tr><tr><td>POST \/reset-password<\/td><td>60 min<\/td><td>3<\/td><td>Per IP + per account<\/td><td>Abuso di email, enumerazione<\/td><\/tr><tr><td>POST \/risorse<\/td><td>60 min<\/td><td>20<\/td><td>Per utente autenticato<\/td><td>Spam di contenuti<\/td><\/tr><tr><td>POST \/invia-email<\/td><td>24 ore<\/td><td>50<\/td><td>Per utente<\/td><td>Costi di invio incontrollati<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">Questa stratificazione riflette il principio della difesa in profondit\u00e0: ogni endpoint riceve un limite proporzionato al suo rischio. Gli endpoint che generano costi o inviano notifiche meritano i limiti pi\u00f9 severi. La stessa logica si applica alle rotte di reset password, dove un limite combinato per IP e per account previene sia gli attacchi distribuiti sia l&#8217;enumerazione degli utenti.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"step-6-rate-limiting-distribuito-con-redis\">Step 6: rate limiting distribuito con Redis<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Lo store in-memory ha un limite fondamentale: il conteggio vive nel processo Node.js. Se esegui pi\u00f9 istanze dietro un load balancer (lo scenario normale in produzione), ogni istanza ha il proprio contatore separato. Un client che colpisce quattro istanze pu\u00f2 fare quattro volte le richieste prima di essere bloccato. La soluzione \u00e8 uno store condiviso, e Redis \u00e8 la scelta standard.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>$ npm install rate-limit-redis ioredis<\/code><\/pre>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ redis-store.js\nimport { RedisStore } from 'rate-limit-redis';\nimport { Redis } from 'ioredis';\nimport { rateLimit } from 'express-rate-limit';\n\nexport const redisClient = new Redis({\n  host: process.env.REDIS_HOST || '127.0.0.1',\n  port: process.env.REDIS_PORT || 6379,\n  \/\/ riprova in caso di disconnessione temporanea\n  maxRetriesPerRequest: 3,\n});\n\nexport function creaLimiterRedis(opzioni) {\n  return rateLimit({\n    standardHeaders: 'draft-8',\n    legacyHeaders: false,\n    store: new RedisStore({\n      sendCommand: (...args) =&gt; redisClient.call(...args),\n      prefix: opzioni.prefix || 'rl:',\n    }),\n    ...opzioni,\n  });\n}<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Ora ogni istanza Node.js scrive il contatore nello stesso Redis. Il client riceve un limite coerente indipendentemente da quale istanza serve la richiesta. Il parametro <code>prefix<\/code> ti permette di separare i contatori di limiter diversi nella stessa istanza Redis, evitando collisioni tra il limiter globale e quello di autenticazione.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Usa la factory nel server:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>import { creaLimiterRedis } from '.\/redis-store.js';\n\nconst limiterGlobale = creaLimiterRedis({\n  windowMs: 15 * 60 * 1000,\n  limit: 100,\n  prefix: 'rl:global:',\n});\n\napp.use(limiterGlobale);<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Verifica i contatori direttamente in Redis durante i test. Le chiavi compaiono con il prefisso scelto:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>$ docker exec -it redis-ratelimit redis-cli KEYS 'rl:global:*'\n1) \"rl:global:::1\"\n$ docker exec -it redis-ratelimit redis-cli GET 'rl:global:::1'\n\"42\"<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Il valore <code>42<\/code> indica le richieste gi\u00e0 conteggiate per quell&#8217;IP nella finestra corrente. Quando il valore raggiunge il limite, scattano le risposte 429. Redis gestisce automaticamente la scadenza delle chiavi alla fine della finestra tramite TTL.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"step-7-algoritmo-token-bucket-con-rate-limiter-flexible\">Step 7: algoritmo token bucket con rate-limiter-flexible<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Per quote per utente con burst controllato, <code>rate-limiter-flexible<\/code> offre un controllo pi\u00f9 fine. Supporta token bucket, sliding window, blocco progressivo e diversi backend (Redis, memoria, processo cluster). \u00c8 la libreria giusta quando hai bisogno di logica personalizzata oltre al semplice middleware.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>$ npm install rate-limiter-flexible<\/code><\/pre>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ limiter-flexible.js\nimport { RateLimiterRedis } from 'rate-limiter-flexible';\nimport { redisClient } from '.\/redis-store.js';\n\n\/\/ 10 token, ricarica di 10 token ogni 60 secondi\nexport const limiterUtente = new RateLimiterRedis({\n  storeClient: redisClient,\n  keyPrefix: 'rlflex:user',\n  points: 10,        \/\/ capacita del secchio (burst)\n  duration: 60,      \/\/ finestra di ricarica in secondi\n  blockDuration: 0,  \/\/ nessun blocco aggiuntivo oltre la finestra\n});\n\nexport async function middlewareUtente(req, res, next) {\n  \/\/ usa l'ID utente se autenticato, altrimenti l'IP\n  const chiave = req.user?.id || req.ip;\n  try {\n    const esito = await limiterUtente.consume(chiave, 1);\n    res.set('RateLimit-Remaining', esito.remainingPoints);\n    next();\n  } catch (rejRes) {\n    const secondi = Math.round(rejRes.msBeforeNext \/ 1000) || 1;\n    res.set('Retry-After', String(secondi));\n    res.status(429).json({ errore: 'Quota esaurita', riprovaTra: secondi });\n  }\n}<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Il metodo <code>consume(chiave, punti)<\/code> sottrae i punti dal budget e restituisce una Promise. Se ci sono punti sufficienti, la Promise si risolve con i punti rimanenti; altrimenti viene rifiutata con un oggetto che contiene <code>msBeforeNext<\/code>, il tempo in millisecondi prima della prossima ricarica. Puoi assegnare un costo diverso a operazioni diverse: una query pesante pu\u00f2 consumare 5 punti invece di 1, riflettendo il suo impatto reale sulle risorse.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Questo approccio basato sul costo \u00e8 potente: invece di contare le richieste, conti il consumo effettivo di risorse. Un&#8217;API che combina rate limiting con autenticazione robusta, come spieghiamo nella guida ai <a href=\"\/it\/totp-2fa-nodejs\/\">codici TOTP 2FA in Node.js<\/a>, ottiene una difesa molto pi\u00f9 solida contro gli abusi automatizzati.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"step-8-gestire-i-proxy-e-lheader-x-forwarded-for\">Step 8: gestire i proxy e l&#8217;header X-Forwarded-For<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Questo \u00e8 l&#8217;errore pi\u00f9 comune e pi\u00f9 pericoloso nelle configurazioni reali. Quando la tua app gira dietro un reverse proxy (Nginx, un load balancer cloud, Cloudflare), l&#8217;indirizzo IP che Express vede in <code>req.ip<\/code> \u00e8 quello del proxy, non quello del client reale. Se non configuri correttamente la fiducia nel proxy, succede una di due cose: tutti i client condividono lo stesso contatore (perch\u00e9 hanno tutti l&#8217;IP del proxy) oppure un attaccante pu\u00f2 falsificare l&#8217;header per evadere i limiti.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Express usa l&#8217;impostazione <code>trust proxy<\/code> per decidere di quali proxy fidarsi. Non impostarla mai su <code>true<\/code> in modo indiscriminato in produzione, perch\u00e9 consente a chiunque di falsificare l&#8217;header <code>X-Forwarded-For<\/code>. Specifica invece il numero esatto di proxy davanti alla tua app o i loro indirizzi.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ CORRETTO: un solo proxy fidato davanti all'app\napp.set('trust proxy', 1);\n\n\/\/ CORRETTO: fidati solo di indirizzi specifici\napp.set('trust proxy', ['127.0.0.1', '10.0.0.0\/8']);\n\n\/\/ PERICOLOSO: non fare questo in produzione\n\/\/ app.set('trust proxy', true);<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Con <code>trust proxy<\/code> impostato correttamente su <code>1<\/code>, Express prende l&#8217;ultimo IP nella catena <code>X-Forwarded-For<\/code> come IP del client, ignorando i valori che un client potrebbe aver iniettato a monte. La documentazione ufficiale di Express dedica una pagina a questo comportamento, ed \u00e8 una lettura obbligatoria prima di andare in produzione. Le versioni recenti di <code>express-rate-limit<\/code> emettono un avviso se rilevano una configurazione <code>trust proxy<\/code> potenzialmente insicura.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Verifica quale IP viene effettivamente usato aggiungendo temporaneamente una rotta di debug che restituisce <code>req.ip<\/code>, e controlla che corrisponda all&#8217;IP reale del client passando attraverso il tuo proxy. Questo semplice test ti evita ore di debug quando i limiti sembrano comportarsi in modo strano.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"step-9-blocco-progressivo-contro-il-brute-force\">Step 9: blocco progressivo contro il brute force<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Per gli endpoint di autenticazione, un limite fisso non basta. La tecnica migliore \u00e8 il blocco progressivo: dopo un certo numero di fallimenti consecutivi, il client viene bloccato per un periodo crescente. <code>rate-limiter-flexible<\/code> supporta questo schema combinando due limiter e l&#8217;opzione <code>blockDuration<\/code>.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ brute-force.js\nimport { RateLimiterRedis } from 'rate-limiter-flexible';\nimport { redisClient } from '.\/redis-store.js';\n\n\/\/ blocca 5 tentativi falliti, poi blocco di 15 minuti\nconst limiterFalliti = new RateLimiterRedis({\n  storeClient: redisClient,\n  keyPrefix: 'login_fail',\n  points: 5,\n  duration: 60 * 15,\n  blockDuration: 60 * 15,\n});\n\nexport async function registraFallimento(ip) {\n  try {\n    await limiterFalliti.consume(ip, 1);\n  } catch (rejRes) {\n    \/\/ l'utente e ora bloccato per blockDuration\n  }\n}\n\nexport async function resetTentativi(ip) {\n  await limiterFalliti.delete(ip); \/\/ login riuscito: azzera\n}<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">La logica \u00e8 chiara: ogni login fallito chiama <code>registraFallimento<\/code>, che consuma un punto. Al sesto fallimento il limiter rifiuta e applica <code>blockDuration<\/code>, bloccando l&#8217;IP per 15 minuti anche se smette di provare. Un login riuscito chiama <code>resetTentativi<\/code>, che cancella il contatore e restituisce all&#8217;utente legittimo il pieno budget. Questo schema rende inefficace il credential stuffing senza penalizzare chi sbaglia la password una volta.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Per un&#8217;ulteriore robustezza, puoi mantenere due chiavi separate: una per IP e una per coppia IP+username. In questo modo un attaccante che prova molti username dallo stesso IP viene fermato, ma un singolo utente non blocca altri utenti dietro lo stesso IP aziendale condiviso. Questa difesa si integra bene con le altre misure descritte nella nostra guida alla <a href=\"\/it\/protezione-csrf-nodejs\/\">protezione CSRF in Node.js<\/a>.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"step-10-risposte-429-personalizzate-e-retry-after\">Step 10: risposte 429 personalizzate e Retry-After<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Una risposta 429 generica frustra gli sviluppatori che integrano la tua API. Una risposta ben fatta indica chiaramente cosa \u00e8 successo, quando riprovare e dove leggere la documentazione. Personalizza l&#8217;handler con l&#8217;opzione <code>handler<\/code> di <code>express-rate-limit<\/code>.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>const limiterConHandler = creaLimiterRedis({\n  windowMs: 15 * 60 * 1000,\n  limit: 100,\n  prefix: 'rl:api:',\n  handler: (req, res, next, options) =&gt; {\n    const resetSecondi = Math.ceil(options.windowMs \/ 1000);\n    res.set('Retry-After', String(resetSecondi));\n    res.status(options.statusCode).json({\n      errore: 'rate_limit_superato',\n      messaggio: 'Hai superato il limite di richieste consentito.',\n      limite: options.limit,\n      riprovaTra: resetSecondi,\n      documentazione: 'https:\/\/api.esempio.it\/docs\/rate-limit',\n    });\n  },\n});<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">L&#8217;header <code>Retry-After<\/code> \u00e8 la parte pi\u00f9 importante: comunica al client esattamente quanti secondi attendere. I client SDK ben scritti leggono questo valore e mettono in pausa le richieste, riducendo il carico sul tuo server. La risposta JSON strutturata, con un codice di errore stabile come <code>rate_limit_superato<\/code>, permette ai client di gestire l&#8217;errore in modo programmatico invece di analizzare il testo del messaggio.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Ecco come appare la risposta completa quando il limite \u00e8 superato:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>HTTP\/1.1 429 Too Many Requests\nRetry-After: 900\nRateLimit-Remaining: 0\nContent-Type: application\/json; charset=utf-8\n\n{\n  \"errore\": \"rate_limit_superato\",\n  \"messaggio\": \"Hai superato il limite di richieste consentito.\",\n  \"limite\": 100,\n  \"riprovaTra\": 900,\n  \"documentazione\": \"https:\/\/api.esempio.it\/docs\/rate-limit\"\n}<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"step-11-logging-e-monitoraggio-degli-abusi\">Step 11: logging e monitoraggio degli abusi<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Bloccare le richieste \u00e8 solo met\u00e0 del lavoro: devi anche sapere quando e perch\u00e9 scattano i limiti. Un picco improvviso di risposte 429 pu\u00f2 indicare un attacco in corso, un bug in un client o un limite tarato male. Registra ogni evento di superamento con abbastanza contesto da poter indagare.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>const limiterMonitorato = creaLimiterRedis({\n  windowMs: 15 * 60 * 1000,\n  limit: 100,\n  prefix: 'rl:mon:',\n  handler: (req, res, next, options) =&gt; {\n    console.warn(JSON.stringify({\n      evento: 'rate_limit_superato',\n      ip: req.ip,\n      percorso: req.originalUrl,\n      metodo: req.method,\n      userAgent: req.get('user-agent'),\n      timestamp: new Date().toISOString(),\n    }));\n    res.status(429).json({ errore: 'Troppe richieste' });\n  },\n});<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">In produzione invia questi log a un sistema centralizzato (ELK, Grafana Loki, o un servizio gestito) e crea un avviso quando il tasso di 429 supera una soglia. Una metrica utile \u00e8 il rapporto tra IP unici bloccati e IP unici totali: se un singolo IP genera la maggior parte dei blocchi, probabilmente \u00e8 un attaccante o un bot; se i blocchi sono distribuiti su molti IP, forse il tuo limite \u00e8 troppo basso per il traffico legittimo.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Evita di registrare dati sensibili come token o password che potrebbero finire nel corpo della richiesta. Registra l&#8217;IP, il percorso e lo user agent, ma mai le credenziali. Per ambienti soggetti al GDPR, valuta la pseudonimizzazione degli indirizzi IP nei log a lungo termine, mantenendo l&#8217;IP in chiaro solo per la finestra di indagine necessaria.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"step-12-testare-il-rate-limiter-sotto-carico\">Step 12: testare il rate limiter sotto carico<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Non fidarti di un limiter che non hai testato. Lo strumento <code>autocannon<\/code> genera traffico realistico e ti mostra esattamente quante richieste passano e quante vengono respinte. Installalo globalmente o eseguilo con npx.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>$ npx autocannon -c 10 -d 5 -m GET http:\/\/localhost:3000\/api\/pubblico\n\nRunning 5s test @ http:\/\/localhost:3000\/api\/pubblico\n10 connections\n\nstat    2xx     non-2xx\ntotale  100     1.243\n\nReq\/Sec   268.6<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">In questo esempio, su circa 1.343 richieste totali solo 100 hanno ricevuto 200 (il limite della finestra) e le restanti 1.243 hanno ricevuto 429. \u00c8 esattamente il comportamento atteso: il limite di 100 viene rispettato e tutto il resto viene respinto senza che il server vada in sovraccarico. Scrivi anche un test automatico che invii N+1 richieste e verifichi che l&#8217;ultima riceva 429, cos\u00ec la protezione resta verificata a ogni deploy.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ test\/ratelimit.test.js (con node:test)\nimport { test } from 'node:test';\nimport assert from 'node:assert';\n\ntest('blocca dopo il limite', async () =&gt; {\n  let ultimoStato;\n  for (let i = 0; i &lt; 101; i++) {\n    const r = await fetch('http:\/\/localhost:3000\/api\/pubblico');\n    ultimoStato = r.status;\n  }\n  assert.strictEqual(ultimoStato, 429);\n});<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"5-errori-comuni-da-evitare\">5 errori comuni da evitare<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Anche con le librerie giuste, alcune trappole ricorrono in quasi tutti i progetti. Eccole, con la soluzione.<\/p>\n\n\n\n<ul class=\"wp-block-list\"><li><strong>Configurare trust proxy in modo errato.<\/strong> \u00c8 l&#8217;errore numero uno. Senza la configurazione corretta dietro un proxy, o tutti gli utenti condividono un contatore o gli attaccanti falsificano l&#8217;IP. Imposta <code>trust proxy<\/code> sul numero esatto di proxy fidati, mai su <code>true<\/code> aperto.<\/li><li><strong>Usare lo store in-memory in produzione multi-istanza.<\/strong> Il contatore in memoria non \u00e8 condiviso tra processi. Con pi\u00f9 istanze il limite reale si moltiplica per il numero di istanze. Passa a Redis dallo step 6.<\/li><li><strong>Limitare per IP gli utenti dietro NAT.<\/strong> Reti aziendali e operatori mobili condividono pochi IP tra molti utenti. Un limite per IP troppo basso blocca utenti legittimi. Per gli utenti autenticati, limita per ID utente invece che per IP.<\/li><li><strong>Contare i login riusciti.<\/strong> Senza <code>skipSuccessfulRequests<\/code>, un utente che accede normalmente esaurisce il budget di tentativi. Conta solo i fallimenti sugli endpoint di autenticazione.<\/li><li><strong>Dimenticare la gestione degli errori di Redis.<\/strong> Se Redis non \u00e8 raggiungibile e non gestisci l&#8217;errore, il limiter pu\u00f2 bloccare tutto il traffico o lasciarlo passare senza controllo. Definisci una strategia di fallback esplicita.<\/li><\/ul>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"risoluzione-dei-problemi-8-casi-frequenti\">Risoluzione dei problemi: 8 casi frequenti<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Quando il rate limiter non si comporta come previsto, questi sono i sintomi pi\u00f9 comuni e le rispettive cause.<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table><thead><tr><th>Sintomo<\/th><th>Causa probabile<\/th><th>Soluzione<\/th><\/tr><\/thead><tbody><tr><td>Tutti gli utenti bloccati insieme<\/td><td>trust proxy non configurato, tutti vedono l&#8217;IP del proxy<\/td><td>Imposta app.set(&#8216;trust proxy&#8217;, 1)<\/td><\/tr><tr><td>Il limite non scatta mai<\/td><td>Middleware registrato dopo le rotte<\/td><td>Registra il limiter prima delle rotte<\/td><\/tr><tr><td>Limite doppio o triplo del previsto<\/td><td>Store in-memory con pi\u00f9 istanze<\/td><td>Usa lo store Redis condiviso<\/td><\/tr><tr><td>Errore &#8220;Cannot read property of undefined&#8221;<\/td><td>req.user non definito prima dell&#8217;auth<\/td><td>Usa fallback req.user?.id || req.ip<\/td><\/tr><tr><td>429 anche con poco traffico<\/td><td>windowMs o limit tarati male<\/td><td>Verifica i valori, ricorda i ms<\/td><\/tr><tr><td>Header RateLimit assenti<\/td><td>standardHeaders non impostato<\/td><td>Imposta standardHeaders: &#8216;draft-8&#8217;<\/td><\/tr><tr><td>Contatori che non scadono in Redis<\/td><td>TTL non applicato dallo store<\/td><td>Aggiorna rate-limit-redis all&#8217;ultima versione<\/td><\/tr><tr><td>Traffico bloccato quando Redis cade<\/td><td>Nessuna strategia di fallback<\/td><td>Gestisci l&#8217;errore del client Redis<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">Per il caso del fallback Redis, la decisione progettuale \u00e8 importante: vuoi &#8220;fail open&#8221; (lasciar passare il traffico se Redis non risponde, privilegiando la disponibilit\u00e0) o &#8220;fail closed&#8221; (bloccare, privilegiando la sicurezza)? Per la maggior parte delle API pubbliche il fail open con un limiter in-memory di emergenza \u00e8 il compromesso giusto, perch\u00e9 evita un&#8217;interruzione totale del servizio per un guasto dello store. Documenta sempre la scelta nel codice.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>redisClient.on('error', (err) =&gt; {\n  console.error('Redis non raggiungibile:', err.message);\n  \/\/ strategia fail open: il middleware usa un fallback in-memory\n});<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"consigli-avanzati-per-la-produzione\">Consigli avanzati per la produzione<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Una volta padroneggiate le basi, queste tecniche portano il tuo rate limiting al livello successivo. Sono le pratiche che distinguono un&#8217;API resistente da una semplicemente funzionante.<\/p>\n\n\n\n<ul class=\"wp-block-list\"><li><strong>Limiti per piano tariffario.<\/strong> Se offri tier diversi (gratuito, pro, enterprise), assegna limiti dinamici in base al piano dell&#8217;utente leggendo <code>req.user.plan<\/code> e calcolando <code>limit<\/code> con una funzione invece di un valore fisso.<\/li><li><strong>Whitelisting selettivo.<\/strong> Usa l&#8217;opzione <code>skip<\/code> per escludere indirizzi interni di monitoraggio o partner fidati, ma mantieni la lista corta e versionata nel codice o in una configurazione sicura.<\/li><li><strong>Rate limiting a pi\u00f9 livelli.<\/strong> Combina un limite breve e aggressivo (per secondo) con uno lungo e generoso (per giorno). Il primo ferma i burst, il secondo impone una quota complessiva.<\/li><li><strong>Costo variabile per operazione.<\/strong> Con <code>rate-limiter-flexible<\/code>, fai consumare pi\u00f9 punti alle operazioni costose. Una ricerca complessa pu\u00f2 valere 10 punti, una lettura semplice 1.<\/li><li><strong>Sincronizza il rate limiting con il tuo gateway.<\/strong> Se usi un API gateway o un WAF a monte, coordina i limiti per evitare doppi conteggi e definisci chiaramente quale livello \u00e8 la fonte di verit\u00e0.<\/li><\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">Un&#8217;ultima raccomandazione operativa: rivedi periodicamente i tuoi limiti analizzando i dati reali di traffico. Limiti tarati una volta e mai aggiornati diventano o troppo stretti (frustrano gli utenti che crescono) o troppo larghi (non proteggono pi\u00f9). Tratta le soglie come configurazione viva, non come costanti scolpite nella pietra.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"il-progetto-completo-funzionante\">Il progetto completo funzionante<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Ecco il file <code>server.js<\/code> completo che mette insieme tutti gli step: limite globale su Redis, limiter di autenticazione con blocco progressivo, header standard, gestione proxy e risposte 429 strutturate. \u00c8 pronto per essere adattato alla tua API.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ server.js (progetto completo)\nimport express from 'express';\nimport { creaLimiterRedis, redisClient } from '.\/redis-store.js';\nimport { registraFallimento, resetTentativi } from '.\/brute-force.js';\n\nconst app = express();\napp.use(express.json());\n\n\/\/ 1. Fidati di un solo proxy davanti all'app\napp.set('trust proxy', 1);\n\n\/\/ 2. Limite globale condiviso via Redis\napp.use(creaLimiterRedis({\n  windowMs: 15 * 60 * 1000,\n  limit: 100,\n  prefix: 'rl:global:',\n}));\n\n\/\/ 3. Limiter severo per l'autenticazione\nconst limiterAuth = creaLimiterRedis({\n  windowMs: 15 * 60 * 1000,\n  limit: 5,\n  prefix: 'rl:auth:',\n  skipSuccessfulRequests: true,\n});\n\napp.get('\/api\/pubblico', (req, res) =&gt; {\n  res.json({ messaggio: 'Endpoint pubblico' });\n});\n\napp.post('\/api\/login', limiterAuth, async (req, res) =&gt; {\n  const { username, password } = req.body;\n  const valido = username === 'demo' &amp;&amp; password === 'segreto';\n\n  if (!valido) {\n    await registraFallimento(req.ip);\n    return res.status(401).json({ errore: 'Credenziali non valide' });\n  }\n\n  await resetTentativi(req.ip);\n  res.json({ messaggio: 'Accesso effettuato', token: 'jwt-di-esempio' });\n});\n\nconst PORT = process.env.PORT || 3000;\nconst server = app.listen(PORT, () =&gt; {\n  console.log(`Server protetto in ascolto sulla porta ${PORT}`);\n});\n\n\/\/ Chiusura pulita: rilascia la connessione Redis\nprocess.on('SIGTERM', async () =&gt; {\n  await redisClient.quit();\n  server.close();\n});<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">La struttura finale del progetto comprende quattro file: <code>server.js<\/code> per le rotte, <code>redis-store.js<\/code> per la factory dei limiter, <code>limiter-flexible.js<\/code> per le quote per utente e <code>brute-force.js<\/code> per il blocco progressivo. Questa separazione mantiene il codice leggibile e ti permette di riutilizzare i limiter in pi\u00f9 servizi. Per approfondire la cifratura dei dati che la tua API gestisce, la guida alla <a href=\"\/it\/crittografia-end-to-end-nodejs\/\">crittografia end-to-end in Node.js<\/a> completa il quadro della sicurezza applicativa.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"conclusioni-il-rate-limiting-come-livello-di-base\">Conclusioni: il rate limiting come livello di base<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Hai costruito un sistema di rate limiting completo, dal middleware in-memory di tre righe fino a un&#8217;architettura distribuita su Redis con blocco progressivo, header standard e monitoraggio. Il punto chiave \u00e8 che il rate limiting non \u00e8 una funzionalit\u00e0 opzionale ma un livello di difesa di base, allo stesso titolo di HTTPS e validazione degli input.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Parti sempre da limiti conservativi e allentali in base ai dati reali, mai il contrario. Tieni Redis come fonte di verit\u00e0 in produzione, configura <code>trust proxy<\/code> con cura e tratta gli endpoint di autenticazione come la priorit\u00e0 assoluta. Con queste fondamenta, la tua API Node.js resiste ad attacchi brute force, scraping e picchi accidentali senza penalizzare gli utenti legittimi. Per mantenere la protezione efficace, rivedi le soglie a ogni rilascio importante e aggiorna le librerie quando escono nuove versioni di sicurezza di Node.js.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"approfondimenti-correlati\">Approfondimenti correlati<\/h3>\n\n\n\n<ul class=\"wp-block-list\"><li><a href=\"\/it\/autenticazione-jwt-nodejs\/\">Autenticazione JWT in Node.js: 12 Step<\/a><\/li><li><a href=\"\/it\/protezione-csrf-nodejs\/\">Protezione CSRF in Node.js: 12 Step<\/a><\/li><li><a href=\"\/it\/totp-2fa-nodejs\/\">TOTP 2FA in Node.js: Autenticatore in 12 Step<\/a><\/li><li><a href=\"\/it\/hashing-password-bcrypt-nodejs\/\">Hashing Password con bcrypt in Node.js: 12 Step<\/a><\/li><li><a href=\"\/it\/crittografia-end-to-end-nodejs\/\">Crittografia End-to-End in Node.js: 12 Step<\/a><\/li><li><a href=\"\/it\/certbot-lets-encrypt-https\/\">Let&#8217;s Encrypt e Certbot: HTTPS Gratis in 10 Step<\/a><\/li><\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">Per approfondire gli standard tecnici citati, consulta le fonti ufficiali: l&#8217;avviso di sicurezza di <a href=\"https:\/\/nodejs.org\/en\/blog\/vulnerability\/june-2026-security-releases\" target=\"_blank\" rel=\"noopener\">Node.js per giugno 2026<\/a>, la voce OWASP <a href=\"https:\/\/owasp.org\/API-Security\/editions\/2023\/en\/0xa4-unrestricted-resource-consumption\/\" target=\"_blank\" rel=\"noopener\">API4:2023 Unrestricted Resource Consumption<\/a>, la documentazione di <a href=\"https:\/\/github.com\/express-rate-limit\/express-rate-limit\" target=\"_blank\" rel=\"noopener\">express-rate-limit<\/a> e di <a href=\"https:\/\/github.com\/animir\/node-rate-limiter-flexible\" target=\"_blank\" rel=\"noopener\">rate-limiter-flexible<\/a>, la guida di Express su come operare <a href=\"https:\/\/expressjs.com\/en\/guide\/behind-proxies.html\" target=\"_blank\" rel=\"noopener\">dietro i proxy<\/a> e la pagina Mozilla sullo stato <a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/HTTP\/Status\/429\" target=\"_blank\" rel=\"noopener\">429 Too Many Requests<\/a>.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"domande-frequenti-sul-rate-limiting-in-node-js\">Domande frequenti sul rate limiting in Node.js<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"qual-e-la-differenza-tra-express-rate-limit-e-rate-limiter-flexible\">Qual \u00e8 la differenza tra express-rate-limit e rate-limiter-flexible?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\"><code>express-rate-limit<\/code> \u00e8 un middleware semplice e dichiarativo, ideale per applicare limiti per IP alle rotte Express con poche righe di configurazione. <code>rate-limiter-flexible<\/code> \u00e8 una libreria pi\u00f9 potente che offre token bucket, sliding window, blocco progressivo e costi variabili per operazione, oltre al supporto per pi\u00f9 backend. Usa la prima per i casi standard e la seconda quando ti serve logica personalizzata, quote per utente o protezione brute force avanzata.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"devo-usare-per-forza-redis-per-il-rate-limiting\">Devo usare per forza Redis per il rate limiting?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">No, se esegui una singola istanza Node.js lo store in-memory \u00e8 sufficiente e pi\u00f9 semplice. Redis diventa necessario quando esegui pi\u00f9 istanze dietro un load balancer, perch\u00e9 il conteggio deve essere condiviso tra i processi. Poich\u00e9 quasi tutte le applicazioni in produzione scalano orizzontalmente, Redis \u00e8 la scelta standard per gli ambienti reali.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"come-imposto-i-limiti-per-gli-utenti-dietro-lo-stesso-indirizzo-ip\">Come imposto i limiti per gli utenti dietro lo stesso indirizzo IP?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Limita per ID utente invece che per IP quando l&#8217;utente \u00e8 autenticato. Usa una chiave come <code>req.user?.id || req.ip<\/code>, cos\u00ec gli utenti autenticati hanno un budget individuale e solo il traffico anonimo viene limitato per IP. Questo evita di bloccare interi uffici o reti mobili che condividono pochi indirizzi pubblici tramite NAT.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"cosa-succede-al-rate-limiting-se-redis-va-offline\">Cosa succede al rate limiting se Redis va offline?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Dipende dalla strategia che configuri. Con &#8220;fail open&#8221; il traffico passa anche senza Redis, privilegiando la disponibilit\u00e0; con &#8220;fail closed&#8221; le richieste vengono bloccate, privilegiando la sicurezza. Per le API pubbliche \u00e8 comune il fail open con un limiter in-memory di emergenza, cos\u00ec un guasto di Redis non causa un&#8217;interruzione totale del servizio. Gestisci sempre l&#8217;evento <code>error<\/code> del client Redis.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"il-rate-limiting-protegge-da-attacchi-ddos\">Il rate limiting protegge da attacchi DDoS?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Solo in parte. Il rate limiting a livello applicativo ferma gli abusi da singoli client e gli attacchi brute force, ma non regge un DDoS volumetrico distribuito su decine di migliaia di IP, perch\u00e9 le richieste arrivano comunque al tuo server. Per i DDoS servono difese a monte come un CDN, un WAF o la protezione del provider cloud. Il rate limiting \u00e8 un livello complementare, non sostitutivo.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"quale-stato-http-devo-restituire-quando-il-limite-e-superato\">Quale stato HTTP devo restituire quando il limite \u00e8 superato?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Lo stato corretto \u00e8 <code>429 Too Many Requests<\/code>, definito dallo standard HTTP proprio per questo scopo. Accompagnalo sempre con l&#8217;header <code>Retry-After<\/code>, che indica al client quanti secondi attendere prima di riprovare. Evita di usare 403 o 503, perch\u00e9 hanno significati diversi e confondono i client che integrano la tua API.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"quanto-devono-essere-severi-i-limiti-sugli-endpoint-di-login\">Quanto devono essere severi i limiti sugli endpoint di login?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Molto pi\u00f9 severi del traffico normale. Un valore di riferimento ragionevole \u00e8 5 tentativi falliti ogni 15 minuti per IP, con blocco progressivo dopo il superamento. Conta solo i fallimenti, non gli accessi riusciti, e azzera il contatore quando l&#8217;utente accede correttamente. Combina questa misura con l&#8217;autenticazione a due fattori per una protezione robusta contro il credential stuffing.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Un singolo client pu\u00f2 inviare migliaia di richieste al secondo a un&#8217;API Node.js non protetta. Senza un controllo del traffico, quel client esaurisce CPU, memoria e connessioni al database, degrada\u2026<\/p>\n","protected":false},"author":8,"featured_media":155,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[3],"tags":[],"class_list":["post-154","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-security"],"_links":{"self":[{"href":"https:\/\/shattered.io\/it\/wp-json\/wp\/v2\/posts\/154","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/shattered.io\/it\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/shattered.io\/it\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/shattered.io\/it\/wp-json\/wp\/v2\/users\/8"}],"replies":[{"embeddable":true,"href":"https:\/\/shattered.io\/it\/wp-json\/wp\/v2\/comments?post=154"}],"version-history":[{"count":1,"href":"https:\/\/shattered.io\/it\/wp-json\/wp\/v2\/posts\/154\/revisions"}],"predecessor-version":[{"id":156,"href":"https:\/\/shattered.io\/it\/wp-json\/wp\/v2\/posts\/154\/revisions\/156"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/shattered.io\/it\/wp-json\/wp\/v2\/media\/155"}],"wp:attachment":[{"href":"https:\/\/shattered.io\/it\/wp-json\/wp\/v2\/media?parent=154"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/shattered.io\/it\/wp-json\/wp\/v2\/categories?post=154"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/shattered.io\/it\/wp-json\/wp\/v2\/tags?post=154"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}