Enable browser caching in Nginx by returning the right HTTP response headers from the location that serves each resource. Use long, immutable freshness for files whose URLs contain a content hash or version, and use no-cache with validators for HTML and other responses that can change at the same URL. Nginx’s expires directive generates Expires and Cache-Control; add_header lets you write an explicit cache policy.
Antihost offers Linux VPS plans with full root access, unlimited traffic on a 1 Gbps port and a 14-day free trial.
What browser caching in Nginx actually controls
A browser decides whether it can reuse a response from its local cache by reading the response headers sent by your web server. Nginx does not switch on a separate “browser cache” feature. You enable the behavior by adding cache headers to the response produced by the matching server or location block.
As an Amazon Associate I earn from qualifying purchases.
The two directives used most often are part of Nginx’s ngx_http_headers_module:
expiressets anExpiresdate and also creates a correspondingCache-Controlfreshness policy.add_headerwrites an explicitly chosen response header value, such asCache-Control: public, max-age=31536000, immutable.
Both directives can be used in the http, server, and location contexts. An add_header can also be used in an if block inside a location, although a normal location rule is easier to audit and less prone to surprises.
#1 Best Overall
“Fresh for one year” is not automatically safe for every file. If a browser stores /app.js for 365 days and you deploy new bytes at that same URL, visitors can continue running the old script. Long caching is appropriate when the URL changes whenever the bytes change—for example, app.abc123.js. A stable entry document such as index.html normally needs revalidation so it can point users to the newest asset URLs.
Choose a policy for each kind of response
Decide how a URL changes before choosing its lifetime. The table below gives practical starting points; the right value depends on your deployment process and how much delay is acceptable when content changes.
| Resource | Example policy | When it is appropriate |
|---|---|---|
| Fingerprint-named CSS, JavaScript, images and fonts | public, max-age=31536000, immutable |
The filename contains a content hash or release version, so a changed file is published at a new URL. The 31536000 value is one year in seconds and is a configuration example, not a measured performance guarantee. |
| Unversioned CSS or JavaScript | Short max-age or no-cache |
The same URL may receive new bytes. A short lifetime limits staleness; no-cache stores the response but requires a validation check before reuse. |
| HTML entry documents | no-cache with ETag and/or Last-Modified |
The browser can keep a local copy while checking whether the document changed. An unchanged representation can be returned as 304 Not Modified without sending the body again. |
| Personalized or sensitive responses | An application-appropriate private or no-store policy |
User-specific data should not be made publicly reusable by shared caches. Select the directive that matches your privacy and session requirements. |
The important distinction is that no-cache does not mean “never store.” It means “do not reuse the stored representation without checking with the origin.” no-store is the directive used when a response should not be retained at all.
Prepare the Nginx configuration
Use the configuration file loaded by the active Nginx instance, and first identify the root and location that serve the files. A request such as /assets/app.abc123.js might be handled by a dedicated location /assets/, a regular-expression location, or a broader location /. Put the policy in the block that actually serves the response.
Before changing production behavior, make a copy of the current configuration and check which file is loaded on your system. A syntactically valid rule in an unused virtual host will have no effect, while a broad rule in the wrong server block can affect unrelated sites.
After editing, always run:
nginx -t
Proceed only when the test reports that the configuration syntax is successful. Reload Nginx through the service manager used by your host so existing connections can finish cleanly; common installations use a command equivalent to systemctl reload nginx. If your platform uses a different manager, use its normal reload operation rather than stopping the server.
Crashes, No Sound, or Screen Glitches?
Random freezes, missing sound and display glitches usually trace back to one bad driver. Find and replace yours safely.Free scan · under a minuteWindows Errors? Fix Them Before They Spread
Repair common Windows errors and clear accumulated junk for a smoother, more stable PC - no reinstall needed.Free scan · no reinstallBaseline configuration for hashed assets and HTML
This example separates entry documents from static files. The regular-expression locations are case-insensitive because of ~*. Adjust the document root and extensions to match your application.
server {
root /var/www/example;
# Documents can change at the same URL; validate before reuse.
location ~* .html$ {
expires -1;
add_header Cache-Control "no-cache";
}
# Use this only when these URLs contain a content hash or release version.
location ~* .(css|js|png|jpg|jpeg|gif|svg|webp|woff2?)$ {
expires 1y;
add_header Cache-Control "public, immutable";
}
}
expires 1y creates a positive freshness lifetime and a future Expires value. The additional Cache-Control header marks the response as publicly cacheable and immutable while it is fresh. Use this pattern only when deployment changes the URL along with the bytes.
Rank #2
A negative duration such as expires -1 generates a past expiration and a no-cache cache-control policy. If you need one precisely controlled header value, prefer an explicit add_header rule and inspect the result so you do not accidentally emit conflicting or duplicate policies. The essential requirement is that the final response seen by the browser expresses one intentional policy.
expires versus add_header
Use expires for duration-based policies
expires is convenient when your policy is naturally described as a duration. Examples include expires 1y for a year, expires 7d for a week, or expires -1 for a revalidation policy. Nginx calculates the HTTP date and the corresponding freshness value for you.
Nginx also supports special values such as epoch, max, and off. off disables the module’s automatic modification of the expiration fields in that context. Treat these as configuration controls, not as substitutes for deciding whether a URL is versioned or personalized.
Use add_header for an explicit Cache-Control value
add_header is useful when you want to state the complete policy directly, including directives such as public, private, immutable, must-revalidate, or no-store. For example:
location /assets/ {
add_header Cache-Control "public, max-age=604800";
}
Here, 604800 is seven days in seconds. This example deliberately does not claim a year of freshness; it is suitable for an unversioned asset directory only when a week of possible staleness is acceptable. If the directory contains fingerprinted files, a longer lifetime can reduce repeat downloads, provided the URL-changing rule is enforced.
Understand the always parameter
By default, Nginx applies an add_header only to response status codes in the module’s normal list. Add always when the header must also be attached to statuses outside that list, such as an error response:
Free tools Windows power users keep installed
One-click scans. No signup required.
location /assets/ {
add_header Cache-Control "public, max-age=604800" always;
}
Use this intentionally. A cache policy on an error page is different from a policy on a successful asset, and caching an unexpected error for too long can hide a recovery. Test both successful and error responses when you depend on always.
Place rules where Nginx will apply them
Nginx selects a location by matching the request URI, then maps that URI to a file using the applicable root. A cache rule belongs in the most specific block that serves the resource. For example, a dedicated location /assets/ is easier to reason about than a server-wide rule that also catches HTML, API responses, or downloads.
Rank #3
Header inheritance is a frequent source of surprises. An add_header defined at a higher level is inherited only when the current level has no add_header directives of its own. Adding one header inside a nested location can therefore prevent other headers from the parent from being inherited. Review the complete set of security and cache headers after introducing a nested rule, and repeat them in the child location when that is the intended result.
Regular-expression locations can also win over a prefix location depending on Nginx’s matching rules. If a request is not receiving the policy you expect, inspect the effective configuration and test the exact URI rather than assuming the visually nearest block handled it.
Quick wins for a faster PC:
Clear out junk files and repair common Windows errorsFree Scan →Fix the driver behind crashes, sound loss and screen glitchesFind Drivers →Make long caching safe with URL fingerprinting
An immutable policy works because the URL becomes a release identifier. A build system may transform app.js into app.abc123.js, where abc123 represents the content or a build version. When the source changes, the next deployment emits a different name, such as app.98f7de.js. Browsers can keep the old file for its full lifetime because the HTML or manifest will reference the new URL.
The deployment sequence matters:
- Build the new assets and generate their fingerprinted filenames.
- Publish the new files before exposing HTML that references them.
- Deploy the updated HTML or manifest so new visitors discover the new URLs.
- Retain old files long enough for already-cached HTML or in-flight sessions to request them, or provide a deliberate rollback strategy.
Do not apply max-age=31536000 and immutable to a stable index.html URL merely because it is small. If that document changes but its URL does not, a browser can keep presenting an obsolete application shell until the year-long freshness period expires or the user performs a cache-clearing action.
Use validators for HTML and changing resources
Revalidation is efficient when the server can identify whether the representation changed. Nginx or the upstream application may provide an ETag, a Last-Modified date, or both. A browser that has a stored response sends If-None-Match and/or If-Modified-Since. If the representation is unchanged, the server returns 304 Not Modified with no response body; the browser reuses its stored bytes.
This saves bandwidth while still allowing an entry document to update promptly. A no-cache response can therefore be a good default for HTML: storage is allowed, but reuse is conditional on a check. If an application generates personalized HTML, add the appropriate privacy directives instead of making the response public.
Do these 3 things before closing this tab:
1Fix the driver behind crashes, sound loss and screen glitches2Repair Windows errors before they cause bigger problems3Scan for outdated or missing drivers - takes under a minuteValidators are not a replacement for correct URL versioning. They handle changes at a stable URL, whereas fingerprinting lets immutable assets avoid revalidation altogether. Many sites use both approaches: revalidated HTML points to immutable CSS, JavaScript, images, and fonts.
Apply and verify the configuration
Use a representative fingerprinted asset and the entry document when testing. A successful configuration change should be observable in the actual response headers, not inferred from a browser setting.
Rank #4
- Run
nginx -tand fix every reported error before reloading. - Reload Nginx with the host’s normal service manager.
- Inspect an asset response:
curl -I https://example.test/assets/app.abc123.js
Look for the intended Cache-Control value and an Expires date when you used expires. Then inspect the entry document:
curl -I https://example.test/
Confirm that the document is not accidentally receiving the long-lived asset policy. Header order and formatting can vary, but the effective directives must match the location that handled the request.
What’s actually slowing this PC down?
Pick the symptom - the matching free tool is one click away.
To test revalidation, first capture the validator from the response. For an ETag, send it back in a conditional request:
curl -I -H 'If-None-Match: "your-etag-value"' https://example.test/
For a last-modified validator, use the value returned by the server:
curl -I -H 'If-Modified-Since: your-last-modified-value' https://example.test/
An unchanged representation should produce 304 Not Modified. A 200 OK response can be correct when the validator is absent, malformed, expired, or no longer matches the current representation.
Test a URL with a fingerprint and one without it. This catches an overly broad regular-expression location that gives unversioned files an unsafe year-long lifetime. If you rely on always, request a known error path as well as a successful file and verify whether the header appears on both.
The Tool Desk
Outbyte Driver Updater FREEFix the driver behind crashes, sound loss and screen glitchesFind Drivers →Outbyte PC Repair FREERepair Windows errors before they cause bigger problemsFix Now →Why the browser may still download the same asset
The request is not hitting the location you edited
Check the exact hostname, URI, and virtual host. A CDN, load balancer, or another Nginx server may be responding instead of the instance whose configuration you changed. Compare headers from the public hostname with a direct origin request when your architecture allows it.
A proxy or application is replacing the headers
An upstream application, reverse proxy, or CDN can add, replace, or strip Cache-Control, Expires, ETag, or Last-Modified. Inspect the headers at the browser-facing endpoint. The policy that matters is the one the user agent receives, not necessarily the one emitted by an internal hop.
Best Value
The file URL is stable but the bytes changed
A long-lived response at /main.js remains fresh even after you overwrite the file. Rename the asset when its content changes, or shorten the policy and rely on validation. Updating only the file on disk cannot invalidate copies already stored by browsers.
You are confusing reload behavior with normal navigation
Developer tools often provide options such as disabling the cache while the panel is open. A hard reload can also request resources differently from an ordinary navigation. Test in a normal browsing session and inspect the request and response headers before changing Nginx rules.
A nested header changed inheritance
If a child location contains one add_header, parent-level headers may no longer be inherited there. Compare the effective headers for URLs handled by the parent and child locations, then define the complete intended set at the level that serves the response.
Browser cache and Nginx proxy cache are different layers
Browser caching is client-side and is controlled by headers delivered to the user agent. Nginx proxy caching is server-side storage of responses obtained from an upstream application. Enabling one does not enable the other.
For Nginx proxy caching, configure a cache zone with proxy_cache_path in the http context, select it with proxy_cache in the relevant proxy location, and define freshness with directives such as proxy_cache_valid. Applications commonly also need bypass or no-cache conditions for logged-in sessions, cookies, authorization headers, or other personalized requests.
A response can be stored by a browser while not being stored by Nginx, or stored by Nginx while the browser revalidates every time. Diagnose the layer that is failing by checking the browser-facing headers and, separately, the proxy cache status and upstream request path.
Recommended Free Tools
Privacy, security and operational safeguards
- Do not mark user-specific pages or API responses
publicunless you have verified that shared caches cannot serve one user’s representation to another. - Use
no-storefor responses that must not remain in browser storage, such as particularly sensitive data, when that matches the application’s security requirements. - Keep HTML revalidation-friendly so a deployment can change asset references without waiting for an old document to expire.
- Only use
immutablewhen the URL is guaranteed to change with the content. It is a promise that revalidation is unnecessary while the response is fresh. - Document the cache policy alongside the release process. A future developer who changes the build output or filename scheme must know whether the one-year rule is still safe.
- After any CDN, framework, or reverse-proxy change, recheck the final public headers; an upstream layer may override Nginx’s values.
Production checklist
- Identify the exact
locationserving each class of resource. - Use long freshness only for fingerprinted or otherwise versioned URLs.
- Use
no-cacheplusETagand/orLast-Modifiedfor HTML that changes at a stable URL. - Choose either a duration-based
expirespolicy or a deliberately explicitCache-Controlvalue, and inspect for duplicate or conflicting headers. - Review header inheritance whenever you add an
add_headerin a nested location. - Run
nginx -t, reload safely, and inspect representative responses withcurl -I. - Send conditional requests and confirm
304 Not Modifiedfor unchanged, revalidated resources. - Test both versioned and unversioned URLs, plus error responses when using
always. - Check the browser-facing endpoint for CDN or upstream header overrides.
With those checks in place, Nginx can keep immutable static assets out of repeat downloads while still allowing HTML and changing responses to update promptly.
Quick Recap
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

