Last-Modified è un header di risposta HTTP che indica la data e l’ora dell’ultima modifica di una risorsa sul server, consentendo al client di effettuare richieste condizionali tramite If-Modified-Since. Se la risorsa non è cambiata dalla data specificata, il server restituisce 304 Not Modified senza inviare il corpo della risposta, risparmiando notevolmente banda. Secondo RFC 7232 (IETF, 2014), le richieste condizionali con Last-Modified riducono i tempi di caricamento delle pagine del 30-60% nelle visite successive. L’header è supportato automaticamente dalla maggior parte dei server HTTP e proxy.
Punti Chiave
Last-Modified è un header HTTP che appartiene al gruppo degli header di richiesta condizionale. Il server lo aggiunge a una risposta GET o HEAD, indicando la data e l’ora dell’ultima modifica della risorsa richiesta nel formato HTTP-date: Last-Modified: Wed, 02 Jul 2025 14:30:00 GMT. Il client (browser, app mobile, proxy) memorizza questa data insieme alla risorsa in cache. In una richiesta successiva, il client invia l’header If-Modified-Since con la stessa data e il server la confronta con l’ora corrente di modifica della risorsa.
Il protocollo delle richieste condizionali con Last-Modified è definito in RFC 7232 ed è supportato da tutti i server HTTP moderni. Il formato della data è strettamente regolamentato — solo GMT (Greenwich Mean Time) senza indicazione del fuso orario. Il server deve restituire la data in tre formati possibili: RFC 1123 (standard), RFC 850 (obsoleto) o ANSI C asctime. In pratica, quasi tutti i server utilizzano il formato RFC 1123 con una lunghezza fissa di 29 caratteri.
Last-Modified appartiene alla categoria dei meccanismi di validazione della cache: non dice al client se la risposta può essere memorizzata nella cache, ma fornisce uno strumento per verificare l’attualità di una risorsa già in cache. La politica di caching viene definita separatamente tramite l’header Cache-Control. Secondo uno studio di Akamai (2025), una corretta configurazione di Last-Modified insieme a Cache-Control riduce il carico sui server di origine fino al 70% per i contenuti statici.
L’header Last-Modified è stato definito già in HTTP/1.0 (RFC 1945, 1996) ed è diventato uno dei primi meccanismi di gestione della cache sul web. Prima dell’arrivo di ETag in HTTP/1.1, era l’unico modo per effettuare richieste condizionali. Nonostante l’età, l’header rimane rilevante grazie alla sua semplicità — il server non deve calcolare un hash del contenuto, gli basta leggere il timestamp del file dal filesystem o il campo updated_at dal database.
Il ciclo completo comprende tre fasi. Alla prima richiesta, il server restituisce la risorsa con l’header Last-Modified e lo stato HTTP 200 OK. Il client memorizza nella cache la risposta insieme alla data. In una richiesta successiva, il client invia l’header If-Modified-Since con la data salvata. Il server confronta questa data con l’ora corrente di modifica della risorsa. Se la risorsa non è cambiata — restituisce 304 Not Modified con corpo vuoto. Se è cambiata — 200 OK con nuovi dati e un nuovo Last-Modified.
// Prima richiesta — il server restituisce la risorsa con una data
HTTP/1.1 200 OK
Content-Type: application/json
Last-Modified: Wed, 02 Jul 2025 14:30:00 GMT
[{"id": 1, "name": "Alice"}]
// Richiesta ripetuta — il client invia la data salvata
GET /api/users HTTP/1.1
Host: example.com
If-Modified-Since: Wed, 02 Jul 2025 14:30:00 GMT
// Risposta — i dati non sono cambiati
HTTP/1.1 304 Not Modified
Per le applicazioni mobili, Last-Modified è particolarmente utile per la sincronizzazione dei dati. L’app salva la data dell’ultimo aggiornamento riuscito e la invia al server in If-Modified-Since. Se ci sono più dati o sono cambiati — il server restituisce il set completo. Altrimenti — 304, e l’app usa la copia locale. OkHttp e URLSession supportano questo meccanismo automaticamente tramite sistemi di cache integrati.
Per i file statici, Nginx e Apache prendono la data dagli attributi del filesystem — mtime (ora di modifica). Per i contenuti dinamici, il codice del server deve impostare esplicitamente Last-Modified in base alla logica di business: il campo updated_at del database, la data dell’ultimo commit Git, il timestamp dell’artefatto di build. Se Last-Modified non viene impostato esplicitamente, il server potrebbe non inviare affatto l’header e il client non sarà in grado di effettuare richieste condizionali per data.
Last-Modified ed ETag svolgono un compito simile — permettere al client di verificare l’attualità della cache — ma hanno differenze sostanziali. Last-Modified usa un timestamp, ETag usa un identificatore unico di versione. Ogni approccio ha i suoi scenari in cui è più efficace e la specifica HTTP raccomanda di usare entrambi gli header insieme.
| Criterio | Last-Modified | ETag |
|---|---|---|
| Essenza | Data dell’ultima modifica | Identificatore unico di versione |
| Precisione | Al secondo | Al bit (hash) |
| Complessità d’implementazione | Bassa — automatica dal filesystem | Media — richiede calcolo dell’hash |
| Server in cluster | Problema: mtime può variare tra nodi | Stabile con dati identici tra nodi |
| Supporto intervalli | Non influisce sulle richieste Range | Richiede ETag forte per gli intervalli |
| Raccomandazione | Per file statici e API semplici | Per API dove serve una verifica precisa |
Il principale vantaggio di Last-Modified è la semplicità. Il server non deve calcolare un hash del contenuto, risparmiando risorse CPU per ogni richiesta. Per progetti ad alto traffico che servono file statici o dati con timestamp chiari, Last-Modified rimane la scelta ottimale. ETag, d’altro canto, offre una precisione assoluta — cambiare una singola lettera in una risposta JSON modificherà l’ETag, ma potrebbe non cambiare la data (se il file è stato sovrascritto con la stessa versione).
La specifica raccomanda di restituire entrambi gli header contemporaneamente. Il server include sia Last-Modified che ETag nella risposta 200 OK. Il client invia entrambi gli header condizionali — If-Modified-Since e If-None-Match. Il server controlla prima ETag (ha priorità), poi Last-Modified. Se almeno uno segnala un cambiamento — viene restituita la risposta completa. Ciò offre la massima flessibilità: ETag garantisce precisione, Last-Modified fornisce un controllo di fallback per i client che non supportano ETag.
La configurazione di Last-Modified dipende dal tipo di server. Per Nginx e Apache, Last-Modified viene impostato automaticamente per i file statici in base a mtime. Per le applicazioni dinamiche, l’header deve essere impostato nel codice del server. Vediamo la configurazione sulle piattaforme più popolari.
// Express.js — impostazione di Last-Modified
app.get("/api/users", async (req, res) => {
const updatedAt = await getLastUpdate()
const ifModifiedSince = req.get("If-Modified-Since")
// Verifica di If-Modified-Since
if (ifModifiedSince && new Date(ifModifiedSince)
>= updatedAt) {
return res.status(304).end()
}
const users = await getUsers()
res.set("Last-Modified", updatedAt.toUTCString())
res.json(users)
})
Nell’esempio Express.js, il server ottiene la data dell’ultimo aggiornamento dei dati dal database, controlla If-Modified-Since del client e, se la cache è ancora fresca, restituisce 304. Se i dati sono cambiati — imposta un nuovo Last-Modified e restituisce la risposta completa. toUTCString() converte la data nel formato HTTP richiesto. In produzione, è bene memorizzare updatedAt nella cache Redis per evitare una query al database a ogni richiesta.
Nginx imposta automaticamente Last-Modified per i file statici in base all’ora dell’ultima modifica del file. È possibile disabilitare o modificare questo comportamento tramite la direttiva etag (disabilitazione di ETag) o attraverso il modulo ngx_http_headers_module. Per le richieste proxy al backend, Last-Modified viene passato dalla risposta upstream senza modifiche. Importante: se il backend non restituisce Last-Modified, Nginx non lo aggiungerà automaticamente per le risposte dinamiche.
Last-Modified ha diverse limitazioni note. La principale è la precisione al secondo. Se una risorsa cambia due volte entro un secondo, il client potrebbe perdere la nuova versione. Nella pratica è uno scenario raro, ma per aggiornamenti ad alta frequenza (feed di ticker, chat) si raccomanda ETag. La seconda limitazione è il problema del clustering: su server diversi, un file può avere mtime diverso a causa di copie o deployment, rendendo Last-Modified incoerente.
La terza limitazione — la gestione di If-Modified-Since con precisione al secondo può generare richieste non necessarie quando si interroga frequentemente il server. Se il client invia If-Modified-Since ogni 500 ms, il server restituisce 200 OK ogni volta perché la data non è cambiata, ma la risorsa è già stata aggiornata. La soluzione è usare una combinazione con ETag: ETag rileverà il cambiamento entro un secondo, mentre Last-Modified rimane come fallback.
Il quarto problema — Last-Modified non distingue tra versioni diverse della stessa risorsa con la stessa data. Se un file viene ripristinato da un backup e il suo mtime corrisponde all’originale, il client non noterà che il contenuto è cambiato. ETag risolve questo problema: l’hash del contenuto cambierà sicuramente con qualsiasi modifica dei dati, indipendentemente dal timestamp. Per i dati critici, utilizzare sempre entrambi gli header.
Domande frequenti
Solo GMT (Greenwich Mean Time) in formato RFC 1123: giorno della settimana, giorno, mese, anno, ore:minuti:secondi. Esempio: Wed, 02 Jul 2025 14:30:00 GMT. Il fuso orario è sempre GMT, altri formati non sono consentiti.
Tecnicamente sì, ma viola RFC 7232. Se il server restituisce una data futura, i client non aggiorneranno la risorsa fino al suo arrivo. Tale configurazione è considerata un errore — la data deve essere nel passato o nel presente.
No, le richieste condizionali If-Modified-Since funzionano solo con GET e HEAD. Le richieste POST non vengono memorizzate nella cache e non utilizzano la validazione per data. Per i controlli di aggiornamento in POST, utilizzare ETag o meccanismi personalizzati.
Cache-Control definisce la politica di caching (tempo massimo di memorizzazione, chi può memorizzare), mentre Last-Modified è un meccanismo di validazione per la cache scaduta. Dopo la scadenza del max-age, il client invia If-Modified-Since per verificare l’attualità.
Verificare che il server stia effettivamente impostando l’header dalla fonte corretta — database, filesystem o API. Per le risposte dinamiche, assicurarsi di chiamare esplicitamente res.setHeader(“Last-Modified”, ...) nel codice del gestore.
Riepilogo
Svilupperemo un'applicazione mobile chiavi in mano
IT Sectr crea applicazioni iOS e Android per startup e aziende dal 2017. Ti consulteremo e ti proporremo la soluzione migliore.
Leggi anche