Master Browser Caching: HTTP Headers, Spring MVC Code, and Nginx Configuration
This article explains how browsers cache resources, the role of HTTP response headers such as Last-Modified, Expires, Cache-Control, ETag, and Age, and provides practical examples of controlling cache behavior with Spring MVC code and detailed Nginx configuration for both static and proxy caching.
Introduction
Many people ask about browser caching; this article explains how browsers cache resources, the relevant HTTP response headers, and how to control caching from Java Spring MVC and Nginx.
Browser cache basics
When a browser requests a page, the server can include caching headers (Last-Modified, Expires, Cache-Control). The browser stores the response and may later validate it with an If-Modified-Since request; a 304 status means the cached copy is still fresh.
Example: first request to http://item.jd.com/1856588.html returns full headers; after pressing F5 the server returns a 304 response.
Controlling cache in Java (Spring MVC)
Sample controller demonstrates setting Last-Modified, Expires and Cache-Control headers.
@RequestMapping("/cache")
public ResponseEntity<String> cache(HttpServletRequest request,
@RequestParam("millis") long lastModifiedMillis,
@RequestHeader(value = "If-Modified-Since", required = false) Date ifModifiedSince) {
if (ifModifiedSince != null && ifModifiedSince.getTime() == lastModifiedMillis) {
return new ResponseEntity<>(HttpStatus.NOT_MODIFIED);
}
DateFormat gmtDateFormat = new SimpleDateFormat("EEE, d MMM yyyy HH:mm:ss 'GMT'", Locale.US);
String body = "<a href=''>点击访问当前链接</a>";
MultiValueMap<String, String> headers = new HttpHeaders();
headers.add("Last-Modified", gmtDateFormat.format(new Date(lastModifiedMillis)));
headers.add("Date", gmtDateFormat.format(new Date()));
long maxAge = 20;
headers.add("Expires", gmtDateFormat.format(new Date(System.currentTimeMillis() + maxAge)));
headers.add("Cache-Control", "max-age=" + maxAge);
return new ResponseEntity<>(body, headers, HttpStatus.OK);
}When the client sends If-Modified-Since that matches the stored last-modified time, the controller returns HttpStatus.NOT_MODIFIED (304).
First request
http://localhost:9080/cache?millis=1471349916709returns status 200 with headers:
Last-Modified – document’s last modification time.
Expires – HTTP/1.0 expiration time.
Cache-Control – max-age in seconds (e.g., 20).
Pressing F5 sends
If-Modified-Sinceand receives 304; Ctrl+F5 forces a fresh fetch with
Cache-Control: no-cacheand
Pragma: no-cache.
Additional response headers
Age – time the object has been cached in a proxy (CDN).
Vary – indicates which request headers affect the cached representation (e.g., Accept-Encoding).
Via – shows the proxy chain the response passed through.
ETag – entity tag used for strong or weak validation.
Example ETag controller returns a weak ETag and uses
If-None-Matchfor validation.
@RequestMapping("/cache/etag")
public ResponseEntity<String> cache(HttpServletRequest request,
@RequestHeader(value = "If-None-Match", required = false) String ifNoneMatch) {
long now = System.currentTimeMillis();
long maxAge = 10;
String body = "<a href=''>点击访问当前链接</a>";
String etag = "W/\"" + md5(body) + "\"";
if (StringUtils.equals(ifNoneMatch, etag)) {
return new ResponseEntity<>(HttpStatus.NOT_MODIFIED);
}
DateFormat gmtDateFormat = new SimpleDateFormat("EEE, d MMM yyyy HH:mm:ss 'GMT'", Locale.US);
MultiValueMap<String, String> headers = new HttpHeaders();
headers.add("ETag", etag);
headers.add("Date", gmtDateFormat.format(new Date(now)));
headers.add("Cache-Control", "max-age=" + maxAge);
return new ResponseEntity<>(body, headers, HttpStatus.OK);
}Summary of caching rules
Server‑sent Last-Modified is checked with If-Modified-Since ; unchanged content yields 304.
Cache-Control:max-age and Expires define how long the browser may keep the copy.
HTTP/1.1 Cache-Control overrides HTTP/1.0 Expires .
Typical Expires value equals current time plus max-age .
ETag provides an alternative validation mechanism.
Nginx cache configuration
Nginx can set
expires,
etagand
if-modified-sincedirectives for static resources.
location /img {
alias /export/img/;
expires 1d;
}When used as a reverse proxy, Nginx can add
proxy_cachedirectives to cache upstream responses.
proxy_buffering on;
proxy_cache_path /export/cache/proxy_cache levels=1:2 keys_zone=cache:512m inactive=5m max_size=8g use_temp_path=off;
location = /cache {
proxy_pass http://backend_tomcat/cache$is_args$args;
proxy_cache cache;
proxy_cache_key $scheme$proxy_host$request_uri;
proxy_cache_valid 200 5s;
add_header cache-status $upstream_cache_status;
}Cache status values include HIT, MISS, EXPIRED, UPDATING, STALE, REVALIDATED, BYPASS.
Additional directives such as
proxy_cache_min_uses,
proxy_no_cache,
proxy_cache_bypass,
proxy_cache_use_stale, and
proxy_cache_lockfine‑tune caching behavior and prevent cache stampedes.
Cache invalidation
For non‑Plus Nginx, the
ngx_cache_purgemodule can purge specific keys.
location ~ /purge(/.*) {
allow 127.0.0.1;
deny all;
proxy_cache_purge cache$1$is_args$args;
}Efficient Ops
This public account is maintained by Xiaotianguo and friends, regularly publishing widely-read original technical articles. We focus on operations transformation and accompany you throughout your operations career, growing together happily.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.