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=1471349916709 returns 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-Since and receives 304; Ctrl+F5 forces a fresh fetch with Cache-Control: no-cache and 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-Match for 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, etag and if-modified-since directives for static resources.
location /img {
alias /export/img/;
expires 1d;
}When used as a reverse proxy, Nginx can add proxy_cache directives 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_lock fine‑tune caching behavior and prevent cache stampedes.
Cache invalidation
For non‑Plus Nginx, the ngx_cache_purge module can purge specific keys.
location ~ /purge(/.*) {
allow 127.0.0.1;
deny all;
proxy_cache_purge cache$1$is_args$args;
}Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
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.
