Last-Modified는 서버에서 리소스가 마지막으로 수정된 날짜와 시간을 나타내는 HTTP 응답 헤더로, 클라이언트가 If-Modified-Since를 통해 조건부 요청을 수행할 수 있게 합니다. 지정된 날짜 이후로 리소스가 변경되지 않은 경우, 서버는 응답 본문을 보내지 않고 304 Not Modified를 반환하여 대역폭을 크게 절약합니다. RFC 7232(IETF, 2014)에 따르면, Last-Modified를 사용한 조건부 요청은 재방문 시 페이지 로드 시간을 30-60% 단축합니다. 이 헤더는 대부분의 HTTP 서버와 프록시에서 자동으로 지원됩니다.
주요 포인트
Last-Modified는 조건부 요청 헤더 그룹에 속하는 HTTP 헤더입니다. 서버는 GET 또는 HEAD에 대한 응답에 이를 추가하여 HTTP-date 형식으로 요청된 리소스의 마지막 수정 날짜와 시간을 나타냅니다: Last-Modified: Wed, 02 Jul 2025 14:30:00 GMT. 클라이언트(브라우저, 모바일 앱, 프록시)는 이 날짜를 캐시된 리소스와 함께 저장합니다. 반복 요청 시 클라이언트는 동일한 날짜로 If-Modified-Since 헤더를 보내고, 서버는 이를 리소스의 현재 수정 시간과 비교합니다.
Last-Modified를 사용한 조건부 요청 프로토콜은 RFC 7232에 정의되어 있으며 모든 최신 HTTP 서버에서 지원됩니다. 날짜 형식은 엄격하게 규제됩니다 — 시간대 표시 없이 GMT(그리니치 표준시)만 사용합니다. 서버는 세 가지 가능한 형식(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)에서 정의되었으며 웹에서 최초의 캐시 관리 메커니즘 중 하나가 되었습니다. HTTP/1.1에서 ETag가 등장하기 전까지는 조건부 요청을 수행하는 유일한 방법이었습니다. 오래되었음에도 불구하고, 이 헤더는 단순성 덕분에 여전히 관련성을 유지하고 있습니다 — 서버는 콘텐츠 해시를 계산할 필요 없이 파일 시스템에서 파일 타임스탬프나 데이터베이스의 updated_at 필드만 읽으면 됩니다.
전체 사이클은 세 단계로 구성됩니다. 첫 번째 요청에서 서버는 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 요청에 영향 없음 | 범위에 강력한 ETag 필요 |
| 권장 | 정적 파일 및 단순 API용 | 정확한 검증이 중요한 API용 |
Last-Modified의 주요 장점은 단순성입니다. 서버는 콘텐츠 해시를 계산할 필요가 없어 각 요청마다 CPU 리소스를 절약합니다. 정적 파일이나 명확한 타임스탬프가 있는 데이터를 제공하는 트래픽이 많은 프로젝트의 경우 Last-Modified가 최적의 선택입니다. 반면 ETag는 절대적인 정밀도를 제공합니다 — JSON 응답에서 한 글자를 변경하면 ETag가 변경되지만, 날짜는 변경되지 않을 수 있습니다(파일이 동일한 버전으로 덮어쓰여진 경우).
명세는 두 헤더를 동시에 반환할 것을 권장합니다. 서버는 200 OK 응답에 Last-Modified와 ETag를 모두 포함합니다. 클라이언트는 두 조건부 헤더 — If-Modified-Since와 If-None-Match — 를 모두 보냅니다. 서버는 먼저 ETag(우선순위 있음)를 확인한 다음 Last-Modified를 확인합니다. 하나라도 변경을 알리는 경우 — 전체 응답이 반환됩니다. 이는 최대의 유연성을 제공합니다: 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초 이내에 두 번 변경되면 클라이언트가 새 버전을 놓칠 수 있습니다. 실제로는 드문 시나리오이지만, 고빈도 업데이트(티커 피드, 채팅)의 경우 ETag가 권장됩니다. 두 번째 제한은 클러스터링 문제입니다: 다른 서버에서는 복사나 배포로 인해 파일의 mtime이 다를 수 있어 Last-Modified가 일관되지 않게 됩니다.
세 번째 제한 — 초 단위 정밀도의 If-Modified-Since 처리는 서버를 자주 폴링할 때 불필요한 요청을 유발할 수 있습니다. 클라이언트가 500ms마다 If-Modified-Since를 보내는 경우, 서버는 날짜가 변경되지 않았기 때문에 매번 200 OK를 반환하지만, 리소스는 실제로 이미 업데이트되었습니다. 해결책은 ETag와의 조합을 사용하는 것입니다: ETag가 1초 내의 변경을 감지하고, Last-Modified는 폴백으로 유지됩니다.
네 번째 문제 — Last-Modified는 동일한 날짜의 동일 리소스의 다른 버전을 구분하지 못합니다. 파일이 백업에서 복원되고 mtime이 원본과 일치하는 경우, 클라이언트는 콘텐츠가 변경되었음을 인지하지 못합니다. ETag는 이 문제를 해결합니다: 콘텐츠 해시는 타임스탬프와 관계없이 데이터 변경 시 확실히 변경됩니다. 중요한 데이터에는 항상 두 헤더를 모두 사용하세요.
자주 묻는 질문
RFC 1123 형식의 GMT(그리니치 표준시)만 사용: 요일, 일, 월, 연도, 시:분:초. 예: 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 애플리케이션을 만듭니다. 저희가 상담해 드리고 최적의 솔루션을 제안하겠습니다.