Last-Modified — サーバー上のリソースの最終更新日時を示すHTTPレスポンスヘッダーであり、クライアントがIf-Modified-Sinceを使用して条件付きリクエストを実行できるようにします。指定された日付以降にリソースが変更されていない場合、サーバーはレスポンスボディを送信せずに304 Not Modifiedを返すため、トラフィックを大幅に節約できます。RFC 7232(IETF、2014年)によると、Last-Modifiedを使用した条件付きリクエストは再訪問時のページ読み込み時間を30〜60%削減します。このヘッダーはほとんどのHTTPサーバーとプロキシで自動的にサポートされています。
重要なポイント
Last-Modified — 条件付きリクエスト(conditional requests)のグループに属するHTTPヘッダーです。サーバーはGETまたはHEADへのレスポンスにこれを追加し、リクエストされたリソースの最終更新日時をHTTP-date形式で示します:Last-Modified: Wed, 02 Jul 2025 14:30:00 GMT。クライアント(ブラウザ、モバイルアプリ、プロキシ)はこの日付をキャッシュされたリソースとともに保存します。再リクエスト時にクライアントは同じ日付をIf-Modified-Sinceヘッダーに含めて送信し、サーバーはそれをリソースの現在の更新時刻と比較します。
Last-Modifiedを使用した条件付きリクエストのプロトコルはRFC 7232で定義されており、すべての最新HTTPサーバーでサポートされています。日付形式は厳格に規定されており、タイムゾーンを指定しないGMT(Greenwich Mean Time)のみが使用されます。サーバーは3つの形式で日付を返すことができます:RFC 1123(標準)、RFC 850(旧式)、またはANSI C asctime。実際には、ほぼすべてのサーバーが固定長29文字のRFC 1123形式を使用しています。
Last-Modifiedはキャッシュ検証メカニズムの一種です。これはクライアントに対してレスポンスをキャッシュしてよいかを伝えるのではなく、既にキャッシュされたリソースの最新性を確認するためのツールを提供します。キャッシュポリシーはCache-Controlヘッダーで別途設定します。Akamai(2025年)の調査によると、Last-ModifiedをCache-Controlと適切に組み合わせて設定することで、静的コンテンツのオリジンサーバー負荷を最大70%削減できます。
Last-ModifiedヘッダーはHTTP/1.0(RFC 1945、1996年)で定義され、ウェブにおける最初のキャッシュ制御メカニズムの1つとなりました。HTTP/1.1でETagが登場するまでは、条件付きリクエストを実行する唯一の方法でした。長い歴史があるにもかかわらず、このヘッダーはそのシンプルさゆえに今でも有効です。サーバーはコンテンツのハッシュを計算する必要がなく、ファイルシステムからファイルのタイムスタンプを読み取るか、データベースからupdated_atフィールドを取得するだけで済みます。
全体のサイクルは3つの段階で構成されます。最初のリクエストでは、サーバーがLast-ModifiedヘッダーとHTTPステータス200 OKとともにリソースを返します。クライアントはレスポンスを日付とともにキャッシュします。再リクエスト時にクライアントは保存された日付をIf-Modified-Sinceヘッダーに含めて送信します。サーバーはこの日付をリソースの現在の更新時刻と比較します。リソースが変更されていない場合は、空のボディで304 Not Modifiedを返します。変更されている場合は、新しいデータと新しいLast-Modifiedを含む200 OKを返します。
// 最初のリクエスト — サーバーが日付とともにリソースを返す
HTTP/1.1 200 OK
Content-Type: application/json
Last-Modified: Wed, 02 Jul 2025 14:30:00 GMT
[{"id": 1, "name": "Alice"}]
// 再リクエスト — クライアントが保存した日付を送信
GET /api/users HTTP/1.1
Host: example.com
If-Modified-Since: Wed, 02 Jul 2025 14:30:00 GMT
// レスポンス — データは変更されていません
HTTP/1.1 304 Not Modified
モバイルアプリにとって、Last-Modifiedはデータ同期時に特に便利です。アプリは最後に成功した更新日時を保存し、それをIf-Modified-Sinceでサーバーに送信します。データがそれ以上ないか、変更されていない場合はサーバーは304を返し、アプリはローカルコピーを使用します。データが変更されている場合はサーバーが完全なデータセットを返します。OkHttpとURLSessionは組み込みのキャッシュシステムを通じてこのメカニズムを自動的にサポートしています。
静的ファイルの場合、NginxとApacheはファイルシステムの属性(mtime — 更新時刻)から日付を取得します。動的コンテンツの場合、サーバーコードはビジネスロジックに基づいて明示的にLast-Modifiedを設定する必要があります:データベースのupdated_atフィールド、Gitの最終コミット日、アーティファクトのビルドタイムスタンプなど。Last-Modifiedが明示的に設定されていない場合、サーバーはヘッダーをまったく返さない可能性があり、クライアントは日付ベースの条件付きリクエストを実行できません。
Last-ModifiedとETagは類似したタスク(クライアントがキャッシュの最新性を確認できるようにする)を実行しますが、根本的な違いがあります。Last-Modifiedはタイムスタンプを使用し、ETagはバージョンの一意の識別子を使用します。それぞれのアプローチには効果的なシナリオがあり、HTTP仕様では両方のヘッダーを併用することを推奨しています。
| 基準 | Last-Modified | ETag |
|---|---|---|
| 概要 | 最終更新日時 | バージョンの一意の識別子 |
| 精度 | 秒単位 | ビット単位(ハッシュ) |
| 実装の複雑さ | 低 — ファイルシステムから自動取得 | 中 — ハッシュ計算が必要 |
| クラスターサーバー | 問題:ノード間でmtimeが異なる可能性あり | ノード間でデータが同じなら安定 |
| 範囲リクエスト対応 | Range requestsに影響なし | 範囲には強いETagが必要 |
| 推奨 | 静的ファイルとシンプルなAPI向け | 正確な検証が重要なAPI向け |
Last-Modifiedの主な利点はシンプルさです。サーバーはコンテンツのハッシュを計算する必要がなく、リクエストごとにCPUリソースを節約できます。静的ファイルや明確なタイムスタンプを持つデータを配信する高負荷プロジェクトでは、Last-Modifiedが最適な選択肢です。一方ETagは絶対的な精度を提供します。JSONレスポンスの1文字の変更でもETagは変わりますが、日付は変わらない可能性があります(同じバージョンでファイルが上書きされた場合など)。
仕様では両方のヘッダーを同時に返すことを推奨しています。サーバーは200 OKレスポンスにLast-ModifiedとETagの両方を含めます。クライアントは両方の条件付きヘッダー(If-Modified-SinceとIf-None-Match)を送信します。サーバーは最初にETag(優先)をチェックし、次にLast-Modifiedをチェックします。少なくとも1つのヘッダーが変更を示した場合、完全なレスポンスが返されます。これにより最大の柔軟性が得られます:ETagが精度を保証し、Last-ModifiedがETagをサポートしないクライアントのための予備チェックを提供します。
Last-Modifiedの設定はサーバーの種類によって異なります。NginxとApacheでは、静的ファイルのLast-Modifiedがmtimeに基づいて自動的に設定されます。動的アプリケーションの場合は、サーバーコードでヘッダーを設定する必要があります。主要なプラットフォームでの設定を見てみましょう。
// Express.js — Last-Modifiedの設定
app.get("/api/users", async (req, res) => {
const updatedAt = await getLastUpdate()
const ifModifiedSince = req.get("If-Modified-Since")
// 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)
})
Express.jsの例では、サーバーがデータベースからデータの最終更新日時を取得し、クライアントからのIf-Modified-Sinceを確認して、キャッシュが最新の場合は304を返します。データが変更されている場合は新しいLast-Modifiedを設定して完全なレスポンスを返します。toUTCString()は日付を必要なHTTP形式に変換します。本番環境では、RedisにupdatedAtをキャッシュして、リクエストのたびにデータベースクエリを実行しないようにすることをお勧めします。
Nginxは静的ファイルに対して、ファイルの最終更新時刻に基づいてLast-Modifiedを自動的に設定します。動作を無効化または変更するには、etagディレクティブ(ETagの無効化)またはngx_http_headers_moduleモジュールを使用します。バックエンドへのプロキシリクエストの場合、Last-Modifiedはアップストリームのレスポンスからそのまま渡されます。重要:バックエンドがLast-Modifiedを返さない場合、Nginxは動的レスポンスに対して自動的に追加しません。
Last-Modifiedにはいくつかの既知の制限があります。主なものは秒単位の精度です。リソースが1秒以内に2回変更された場合、クライアントが新しいバージョンを見逃す可能性があります。実際には稀なシナリオですが、高頻度更新(為替レートフィード、チャットなど)の場合はETagが推奨されます。2つ目の制限はクラスタリングの問題です。異なるサーバーでは、コピーやデプロイのタイミングによってファイルのmtimeが異なる可能性があり、Last-Modifiedが一貫しなくなることがあります。
3つ目の制限は、秒単位の精度でのIf-Modified-Since処理が、高頻度のポーリング時に余分なリクエストを発生させる可能性があることです。クライアントが500ミリ秒ごとにIf-Modified-Sinceを送信する場合、日付は変わっていないためサーバーは毎回200 OKを返しますが、リソースは実際には既に更新されている可能性があります。解決策はETagとの組み合わせです。ETagが1秒以内の変更を検出し、Last-Modifiedはフォールバックとして機能します。
4つ目の問題は、Last-Modifiedが同じ日付を持つ同一リソースの異なるバージョンを区別できないことです。ファイルがバックアップから復元され、そのmtimeが元のものと一致した場合、コンテンツが変更されていてもクライアントは気づきません。ETagはこの問題を解決します。コンテンツのハッシュは、タイムスタンプに関係なくデータの変更時に確実に変化します。重要なデータの場合は常に両方のヘッダーを使用してください。
よくある質問
RFC 1123形式のGMT(Greenwich Mean Time)のみです:曜日、日、月、年、時:分:秒。例:Wed, 02 Jul 2025 14:30:00 GMT。タイムゾーンは常にGMTで、他の形式は許可されません。
技術的には可能ですが、RFC 7232に違反します。サーバーが未来の日付を返した場合、その日付が来るまでクライアントはリソースを更新しません。このような設定はエラーとみなされます。日付は過去または現在である必要があります。
いいえ、If-Modified-Since条件付きリクエストはGETとHEADでのみ機能します。POSTリクエストはキャッシュされず、日付による検証も使用しません。POSTでのデータ最新性確認にはETagまたはカスタムメカニズムを使用してください。
Cache-Controlはキャッシュポリシー(最大保存時間、キャッシュ可能な主体など)を定義し、Last-Modifiedは期限切れキャッシュの検証メカニズムです。max-ageの期限が切れると、クライアントは最新性を確認するためにIf-Modified-Sinceを送信します。
サーバーが実際のデータソース(データベース、ファイルシステム、API)から正しくヘッダーを設定しているか確認してください。動的レスポンスの場合は、ハンドラーコード内で明示的にres.setHeader("Last-Modified", ...)を呼び出していることを確認してください。
まとめ
ターンキー方式のモバイルアプリケーションを開発します
IT Sectrは2017年からスタートアップや企業向けにiOS・Androidアプリケーションを開発しています。私たちがご相談に乗り、最適なソリューションをご提案します。