iTechGuides is reader-supported. When you buy through links on our site, we may earn an affiliate commission. As an Amazon Associate I earn from qualifying purchases. Learn more
Antihost offers Linux VPS plans with full root access, unlimited traffic on a 1 Gbps port and a 14-day free trial.
Enable browser caching in Nginx by returning the right HTTP cache headers from the location that serves each resource. Use long-lived caching only for fingerprinted, immutable files; make HTML and other URLs that can change at the same address revalidate with Cache-Control: no-cache and validators such as ETag or Last-Modified. Nginx’s expires directive creates Expires and Cache-Control fields, while add_header lets you write an explicit policy.
What browser caching does in Nginx
A browser decides whether it can reuse a response from its local cache by examining response headers. Nginx does not turn on browser caching with a separate cache database; it sends those headers when it serves a file or response. The ngx_http_headers_module supplies the expires and add_header directives for this job.
The Tool Desk
Outbyte Driver Updater FREEScan for outdated or missing drivers - takes under a minuteDriver Scan →Outbyte PC Repair FREEClear out junk files and repair common Windows errorsFree Scan →When a cached response is still fresh, the browser can use it without downloading the body again. When a response is stored but marked no-cache, the browser must ask the origin to validate it before reuse. If the representation has not changed, Nginx can answer with 304 Not Modified, avoiding retransmission of the body while still checking for updates.
Browser caching is separate from Nginx’s server-side proxy cache. Browser caching is controlled by headers sent to visitors. Proxy caching stores upstream responses on the Nginx server and requires directives such as proxy_cache_path and proxy_cache.
#1 Best Overall
Choose a policy before editing nginx.conf
The URL’s ability to identify one immutable version is more important than the file extension. A one-year lifetime is appropriate for a file whose URL changes whenever its bytes change, but dangerous for an unversioned URL that keeps the same name after deployment.
| Resource | Recommended policy | Why |
|---|---|---|
| Fingerprint-named CSS, JavaScript, images and fonts | public, max-age=31536000, immutable |
A content hash or version in the URL points browsers to a new address when the bytes change. |
| Unversioned CSS or JavaScript | Short max-age or no-cache |
The same URL can begin serving different bytes after a deployment. |
| HTML entry documents | no-cache with ETag and/or Last-Modified |
The browser stores the document but validates it before using it again. |
| Personalized or sensitive responses | An application-appropriate private or no-store policy |
A public response could be reused by another user or shared cache. |
max-age is measured in seconds. The value 31536000 represents one year as a configuration example; it is not a universal performance guarantee or a measured benchmark. Choose a shorter lifetime when deployments are infrequent, rollback behavior is uncertain, or the URL cannot be versioned.
Configure a safe baseline
Option A: use expires for a simple lifetime
Place rules in the server that serves the static files. This example gives HTML a validation requirement and gives files below /assets/ a one-year lifetime. Use the long lifetime only when the asset filenames contain a hash or release version.
server {
root /var/www/example;
location ~* .html$ {
expires -1;
add_header Cache-Control "no-cache";
}
location /assets/ {
expires 1y;
}
}
expires 1y creates a future Expires value and a positive Cache-Control: max-age. A negative duration such as expires -1 creates a Cache-Control: no-cache policy. Nginx also supports epoch, max, and off. A zero duration makes the response immediately stale, while epoch sets an expiration date in the past. max requests the largest representable future lifetime; it should therefore be reserved for content that is genuinely immutable.
If your HTML is not served with a filename ending in .html, adapt the location to the real URI. For example, a front controller serving / may need a dedicated exact or prefix location rather than the regular expression shown above. Always test the public URL, not only a filesystem path.
Option B: write an explicit Cache-Control value
Use add_header when you need an exact directive combination such as public, max-age=31536000, immutable. Because expires already generates a Cache-Control field, do not add a second, conflicting lifetime accidentally. For an explicit policy, turn generated expiration off in that location.
Do these 3 things before closing this tab:
1Scan for outdated or missing drivers - takes under a minute2Repair Windows errors before they cause bigger problems3Fix the driver behind crashes, sound loss and screen glitchesserver {
root /var/www/example;
location ~* .html$ {
expires off;
add_header Cache-Control "no-cache";
}
location /assets/ {
expires off;
add_header Cache-Control "public, max-age=31536000, immutable";
}
}
Multiple Cache-Control fields can be combined by clients, but relying on that behavior makes diagnostics harder. Choose either Nginx’s generated policy or one explicit header for each location, then inspect the actual response. If an upstream application already emits Cache-Control, decide which layer owns the policy instead of silently creating contradictory values.
How expires works
The directive accepts a duration, a special value, or off. A positive duration sets an expiration time in the future and a corresponding positive max-age. A zero duration makes the response immediately stale. A negative duration produces Cache-Control: no-cache, which permits storage but requires validation before reuse. epoch sets an expiration date in the past, max uses the largest representable future lifetime, and off stops Nginx from adding an expiration policy at that level.
expires is valid in the http, server, and location contexts. Keep a broad default in http or server only if every matching resource really shares that policy; otherwise place it in the most specific location that serves the files. A broad default can accidentally cache an HTML page, an upload response, or a user-specific endpoint for the same period as a static image.
How add_header works
add_header Cache-Control "..." writes the field value you specify. It can be used in http, server, location, and an if inside a location. By default, Nginx adds a header only for its normal set of successful and redirect status codes. Add the always parameter when the policy must also appear on other responses, such as an error response.
Quick wins for a faster PC:
Repair Windows errors before they cause bigger problemsFix Now →Fix the driver behind crashes, sound loss and screen glitchesFind Drivers →Clear out junk files and repair common Windows errorsFree Scan →Rank #2
location /assets/ {
expires off;
add_header Cache-Control "public, max-age=604800" always;
}
The always keyword does not make an unsafe response public; it only changes which status codes receive the field. Decide separately whether error pages, authentication failures, or other non-success responses should be cached. For a private or sensitive response, use an application-appropriate private or no-store policy instead of copying the public asset rule.
Put rules in the location that really serves the file
Nginx maps a request URI to a file using root and the selected location. A cache rule in an unrelated location has no effect on the response you are testing. Put the policy beside the static-file rule that handles the request, and check regex locations carefully when several blocks could match. A case-insensitive extension expression such as location ~* .(css|js|png|jpg|jpeg|gif|svg|webp|woff2?)$ can cover common assets, but do not use it as a substitute for checking whether every matching URL is versioned.
Header inheritance is a frequent source of surprises. A child location inherits add_header directives from its parent only when it has no add_header of its own. Adding one new header in a nested block can therefore replace the inherited set rather than append to it. If a child needs both the parent’s security headers and its own cache policy, repeat the required headers explicitly or reorganize the locations so the intended set is unambiguous.
Also check for headers inserted by an upstream application, reverse proxy, or CDN. The header visible in the browser is the result after those layers have had a chance to add, replace, or remove fields. A successful local test can therefore differ from the public hostname if another layer changes the response.
Use fingerprinted URLs for long-lived assets
A long lifetime is safe only when a changed representation receives a changed URL. Build systems commonly produce names such as app.abc123.js or styles.v4.css. At deployment, publish the new file and update HTML to reference the new name. Old URLs can remain cacheable because they continue to identify the old bytes.
If you keep a stable name such as /assets/app.js, do not give it a year-long immutable policy unless your deployment process guarantees that the URL is never reused for different content. Use a short lifetime or no-cache instead, and make sure the response supplies validators. Query-string versioning can also identify releases when your serving and intermediary layers preserve the query string, but whichever versioning scheme you choose must reliably produce a new URL for new bytes.
Handle HTML and APIs differently
HTML documents
Entry documents normally change when a release points to new CSS, JavaScript, or markup. Marking index.html as immutable for a year can leave visitors using old asset references after deployment. A no-cache response lets the browser keep the document locally while requiring a validation request before reuse. This is different from no-store: no-cache does not prohibit storage.
API responses
There is no single policy for APIs. Public, identical responses can use a bounded max-age; user-specific responses should not be marked public. Select private, no-store, or another application policy according to whether the response contains account, authorization, or other sensitive data. Consider the response’s URL and request headers as well as its status: an endpoint that sometimes returns personalized data should not inherit a blanket public rule from a neighboring static location.
What’s actually slowing this PC down?
Pick the symptom - the matching free tool is one click away.
Validators and 304 responses
Pair revalidation with ETag and/or Last-Modified. On a later request the browser sends If-None-Match or If-Modified-Since. If the representation is unchanged, Nginx can return 304 Not Modified without sending the response body again. A 304 is not evidence that the resource was never stored; it is the result of a validation check. If the validator no longer matches, the server returns the current representation and a new validator.
Apply the change safely
- Back up the active Nginx configuration or make the change in version control.
- Edit the relevant
serveror static-filelocation. Confirm that the URL pattern covers the files you intend to cache and does not cover HTML or personalized responses accidentally. - Check syntax before reloading:
nginx -t. Fix every reported error; a reload should not be attempted with an invalid configuration. - Reload Nginx through the host’s normal service manager, for example
sudo systemctl reload nginx. A reload applies the configuration without intentionally stopping existing connections. - Request one fingerprinted asset and one HTML document so that each policy is tested independently.
Verify the headers from the command line
Inspect response headers rather than relying only on a browser’s size column:
Rank #3
curl -I https://example.test/assets/app.abc123.js
curl -I https://example.test/
For the fingerprinted asset, confirm the intended Cache-Control lifetime and, when using expires, the corresponding Expires field. For HTML, confirm Cache-Control: no-cache and look for ETag or Last-Modified. Check the response from the same hostname, scheme, and path that visitors use; a different virtual host can select a different server block.
To test revalidation, copy the returned validator and send it back in an If-None-Match or If-Modified-Since request. An unchanged representation should produce 304 Not Modified; a changed one should produce a normal response with the new body and validator. Test a successful response and an error response if you used always.
Recommended Free Tools
Test both a fingerprinted and an unversioned URL. This catches an overly broad regular expression or a parent location that gives a long lifetime to files that can change in place. Repeat the check after a deployment or configuration reload so that the policy is verified at the point where a stale asset would otherwise be noticed.
Troubleshoot a browser that keeps downloading assets
The response has no cache header
The request may be handled by a different location than the one you edited. Confirm the active configuration, the selected server block, and the final response with curl -I. An upstream or CDN may also remove the header before it reaches the browser.
The policy is present but appears ineffective
Check whether the browser is performing a forced reload, whether developer tools have “Disable cache” enabled, and whether the URL changed between requests. Inspect the actual response status and headers instead of assuming that a transfer shown in developer tools means a full body was downloaded. A validation request followed by 304 is expected for no-cache resources.
HTML remains stale after deployment
Look for a long max-age or immutable policy applied to the HTML location. Move that policy to fingerprinted assets and give the entry document no-cache with validators. Also check whether a CDN or another proxy has retained an older header or representation.
Headers differ between status codes
add_header has default status-code restrictions. If a policy must be present on errors, add always and retest the relevant status. Do not make error pages public merely to make the header appear.
A nested block lost other headers
Review inheritance. A new add_header in a child location can replace inherited add_header directives. Re-declare the headers that must remain or move the cache rule to a level where the complete set is defined.
Two layers disagree
Inspect Nginx, the upstream application, and any CDN or reverse proxy separately. One layer may overwrite Cache-Control or add a second value. The browser follows the final response it receives, not the configuration file you intended to use.
Browser cache versus Nginx proxy cache
Use browser headers when the goal is to let each visitor reuse files locally. Configure proxy caching when the goal is to store upstream responses on the Nginx server for reuse across requests. Proxy caching requires proxy_cache_path in the http context, proxy_cache to select the cache, proxy_cache_valid to define freshness, and bypass conditions where authenticated or otherwise special requests must go to the upstream. Enabling one layer does not automatically configure the other. A browser may still revalidate a response retrieved from a proxy, and a proxy may obey or alter upstream cache headers according to its own configuration.
Free tools Windows power users keep installed
One-click scans. No signup required.
Rank #4
A practical decision checklist
- Does the URL contain a content hash or release version? If not, avoid an unconditional year-long immutable policy.
- Can the response contain user-specific or confidential data? If yes, do not mark it public.
- Must the browser discover deployments at the same URL? Use
no-cacheand validators. - Do you need an exact combination of directives? Prefer one explicit
add_header Cache-Controland disable generated expiration in that location. - Are you relying on headers for non-success responses? Use
alwaysonly after deciding those responses’ privacy and caching behavior. - Have you tested the final response through every CDN, proxy, and application layer? Verify with
curl -Iat the public URL. - Will old URLs continue to serve the old bytes after a release? If not, shorten the lifetime or add reliable URL versioning before deploying.
- Does the configuration pass
nginx -tand reload cleanly? Treat syntax and service status as part of the cache change.
What a correct result looks like
A versioned asset should return a deliberate long-lived policy, for example public, max-age=31536000, immutable, and its URL should change whenever its bytes change. An HTML entry document should return no-cache with a validator so the browser can reuse it only after checking for updates. Those two policies solve the common conflict between fast repeat visits and reliable deployments without confusing local browser storage with Nginx’s server-side proxy cache.
Frequently Asked Questions
How do I enable browser caching in Nginx for CSS and JavaScript?
Put an expires or explicit add_header Cache-Control rule in the location that serves those files. Use a long-lived, immutable policy only when filenames contain a content hash or release version; otherwise use a short lifetime or no-cache.
Should I use expires or add_header?
Use expires for a simple duration because it generates both Expires and a matching Cache-Control policy. Use add_header when you need exact directives such as public, max-age=31536000, immutable, and disable generated expiration in that location to avoid conflicting fields.
Does Cache-Control: no-cache prevent the browser from storing a file?
No. no-cache permits storage but requires validation before reuse. With ETag or Last-Modified, the browser can receive 304 Not Modified when the bytes have not changed.
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 minutePC Slower Than It Used to Be?
A free scan shows the junk files, broken settings and background clutter dragging Windows down - then fixes them in one click.Free scan · Windows 10 & 11Why is my browser still downloading the same asset?
Inspect the final response with curl -I. The request may match another location, have no cache header, be forced to reload, or have developer tools’ cache disabled. A no-cache resource also makes a validation request by design; check whether it returns 304 rather than assuming a full body was transferred.
Is a one-year cache safe for index.html?
Usually not when the URL stays the same across deployments. Stale HTML can continue pointing to old CSS or JavaScript. Use no-cache with validators for the entry document and reserve a one-year immutable policy for versioned assets.
How do I cache private API responses?
Do not mark user-specific or sensitive responses public. Choose private, no-store, or another policy that matches the application’s privacy requirements. Browser caching and shared proxy caching have different exposure risks.
What does the always parameter do?
It makes an add_header field apply to response statuses beyond Nginx’s normal successful and redirect set, including selected errors. It does not make the response safe or public; decide the error response’s caching and privacy policy separately.
Why did adding a cache header remove another header?
add_header inheritance is not additive when a child location defines its own directive. The child can replace the parent’s inherited set. Re-declare required security or other headers in the child, or reorganize the locations.
What is the difference between browser caching and proxy_cache?
Browser caching stores responses on each visitor’s device according to response headers. proxy_cache stores upstream responses on the Nginx server and uses directives such as proxy_cache_path, proxy_cache, and proxy_cache_valid. Configuring one does not configure the other.
The Bottom Line
Cache fingerprinted assets for a long period with public, max-age=31536000, immutable; keep HTML and changeable URLs on no-cache with validators; then verify the final public headers and revalidation behavior with curl.
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.

