Troubleshooting

ERR_CACHE_MISS: why Chrome asks you to confirm form resubmission, and how to fix it

ERR_CACHE_MISS shows when you go Back to a page a form produced. What visitors can safely do, and the Post/Redirect/Get fix for sites, tested in Chrome.

Published ~9 min read
On this page

Short answer

ERR_CACHE_MISS means Chrome may only show a page from its cache and has no copy. Usually you pressed Back to a page a form produced, so Chrome shows Confirm Form Resubmission instead of resending your data. Visitors: reload only if repeating the action is harmless. Site owners: answer the POST with a 303 redirect (Post/Redirect/Get).

Where to start

What ERR_CACHE_MISS means

ERR_CACHE_MISS is a Chromium network error, number -400, which Chromium’s error list describes in one line: “The cache does not have the requested entry.” You see it in Chrome, Edge, Brave, Opera and Android’s WebView, which all share that network code. Firefox and Safari handle the same situation with their own messages and never show this code.

The reason it is tied to forms sits in how Chrome goes Back. For a history navigation to a page that was the answer to a POST, Chrome’s navigation code sets two load flags, LOAD_ONLY_FROM_CACHE and LOAD_SKIP_CACHE_VALIDATION: show the stored copy, and never contact the server. That is deliberate. Going Back must not place an order or send a payment a second time behind your back. If the HTTP cache holds the page, you see it instantly. If it does not, the load fails with error -400 and Chrome swaps in the resubmission page.

Chrome sets the same two flags when it restores a tab whose page answered a POST: after a restart that reopens your tabs, after a crash, or when you reopen a closed tab. So the same error can greet you without anyone pressing Back. That case comes from Chromium’s source; our test covered Back only.

The copy is usually missing because the server told the browser not to keep it. A response with Cache-Control: no-store must not be stored by any cache (RFC 9111, section 5.2.2.5), and every PHP page that calls session_start() sends that header by default. The back/forward cache, which keeps whole pages in memory, could have saved the day, but Google’s own bfcache guide says pages with no-store have historically not been eligible for it either.

Key facts

Error code
net::ERR_CACHE_MISS (-400)
Shown by
Chrome, Edge, Brave, Opera, Android WebView
Page heading after a form
Confirm Form Resubmission
Page heading otherwise
This site can’t be loaded from the cache
Typical trigger
Back, Forward or a restored tab, to a POST result
Header that causes it
Cache-Control: no-store
Server-side fix
303 See Other after the POST
Dangerous "fix"
Confirming a reload without thinking: it sends the form again

Chrome picks the wording from the request method (Chromium’s localized_error.cc). After a POST the page reads, word for word:

Chrome 153 · error page after Back to a POST result
Confirm Form Resubmission

This webpage requires data that you entered earlier in order to be properly
displayed. You can send this data again, but by doing so you will repeat any
action this page previously performed.

Press the reload button to resubmit the data needed to load the page.

ERR_CACHE_MISS

There is no Reload button on that page; Chromium removes it for POST errors, so the only way on is the browser’s own reload. For any other request the same error code gets a different page: “This site can’t be loaded from the cache” and “The saved (cached) copy of this site was unreadable.”, with a Reload button. That version is rare in a desktop browser and is covered further down.

Fixes for visitors

Nothing is broken on your computer. The page you went back to was the result of data you sent, and the site asked browsers not to keep it. Work through these in order:

  1. 1Decide whether sending the form again is safe
    Reload sends the same data again, and the site processes it again. For a search or a filter that is harmless. For a checkout, a payment, a booking or a message, check your email or your account for a confirmation first. In our test, a scripted reload on the error page produced a second POST in the server log.
  2. 2To see the page without resending, open it fresh
    Click the site’s own link to the page, for example your order list or the search page, or type the address. That is a normal GET request and never asks about form data.
  3. 3To repeat a harmless action, reload and confirm
    Press F5 or Ctrl+R (Cmd+R on a Mac). Chrome first asks again in a dialog whether to resend the data. Only when you confirm does it send the form again and show the fresh result.
  4. 4If it shows up without pressing Back
    A tab Chrome restored after a restart or a crash, or a closed tab you reopened, behaves like Back when its page came from a form. Open the page fresh as in step 2. For pages with no form at all, see the section on it.

What does not help for the form case: clearing the browsing data (the page was never stored, so there is nothing to repair), flushing DNS, resetting Winsock, resetting Chrome’s settings or updating Chrome (current Chrome behaves this way on purpose). A hard reload with Ctrl+Shift+R is still a reload: Chrome asks the same question and, if you confirm, sends the form again. Those steps fill the top hosting guides for this error, but none of them changes a single header on the site.

Site owner: answer every POST with a redirect

The fix that removes the error for every visitor is the Post/Redirect/Get pattern. The form’s POST does the work, then answers with a redirect instead of a page. The browser follows it with a GET, and that GET page is the one in the history. Going Back or reloading now repeats a harmless GET, never the form.

Use 303 See Other. RFC 9110 defines it for exactly this case: the output of a POST action is shown at a different URL, and the client always follows it with GET. A 302 works in every current browser but leaves the method change to convention; a 307 or 308 keeps the POST and its body, which is the opposite of what you want here. The redirect status code comparison has all five side by side.

PHP: before and after

Before · order.php answers the POST with the page (causes ERR_CACHE_MISS on Back)
<?php
session_start();                       // sends Cache-Control: no-store by default
$id = save_order($_POST);              // your code
?>
<h1>Thank you, order <?= (int) $id ?> is saved.</h1>
After · Post/Redirect/Get
<?php
session_start();

if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    $id = save_order($_POST);          // do the work once
    $_SESSION['flash'] = 'Order saved.';
    header('Location: /orders/' . $id, true, 303);
    exit;                              // send nothing else
}

// GET /orders/123 is rendered by its own script from the database
  • Call exit after the header. Without it the script keeps running and may print the old result page under the redirect.
  • Redirect on validation errors too. Store the errors and the entered values in the session, redirect back to the form with 303, and show them once. A form re-rendered straight from the POST is a POST result again.
  • The result page may keep no-store. Chrome fetches a GET page again when its copy is missing; only POST results are restricted to the cache. In our test the /thanks.php page sent no-store, and Back simply loaded it again from the server.
  • Frameworks have it built in. Express: res.redirect(303, "/orders/" + id). Laravel’s redirect() and Django’s HttpResponseRedirect send a 302, which browsers also follow with GET.

Check the result from any shell. You want a 303 with a Location and no page:

Linux · macOS · WSL
curl -si -d item=test https://example.com/order.php \
  | grep -iE '^(HTTP|location|cache-control)'
Example output · our test server after the change
HTTP/1.1 303 See Other
Cache-Control: no-store, no-cache, must-revalidate
Location: /thanks.php

-d already makes curl send a POST, so no -X POST is needed. Over HTTP/2 the status line reads HTTP/2 303 and the header names are lowercase; the grep -i matches both.

nginx: look for a site-wide no-store

nginx cannot do Post/Redirect/Get for your application, but its configuration can be the source of the header. A Cache-Control "no-store" in the server block applies to every response from that site, POST results included. Find it:

Debian / Ubuntu
sudo grep -rn 'no-store' /etc/nginx/

Scope it to the pages that hold private data and send no-cache elsewhere. no-cache still makes the browser check with the server before reusing a copy on a normal visit, but it allows the copy to be stored, so Back to a form result works. A map keeps the rule in one place, which matters because PHP requests usually end in a separate location ~ \.php$ block after try_files, and headers set in location / never reach them:

nginx · http block, e.g. /etc/nginx/conf.d/cache-control.conf
map $request_uri $page_cache_control {
    ~^/account/   "no-store";    # private area: never stored
    default       "no-cache";    # stored, revalidated on normal visits
}
nginx · server block
add_header Cache-Control $page_cache_control always;
  • add_header does not replace what PHP sends. If PHP already sent Cache-Control, the response then carries two of them. Remove the upstream one with fastcgi_hide_header Cache-Control; only where you mean to override it.
  • A location with its own add_header drops the inherited ones by default. Long-lived static assets keep their own rule that way; repeat your security headers there or keep them in an included snippet. The 304 Not Modified guide shows the cache rules for static files.
  • Test before reloading: sudo nginx -t && sudo systemctl reload nginx.

Which Cache-Control headers cause it

We sent the same form to the same PHP script with one header changed at a time, then clicked a link, pressed Back, and noted what Chrome did. Each case ran twice, with the back/forward cache on and with it switched off, so the HTTP cache alone decided. Both runs gave the same answer:

Header on the POST resultBack in Chrome 153Server contacted on Back?
Cache-Control: no-storeERR_CACHE_MISS, Confirm Form ResubmissionNo
PHP default after session_start() (no-store, no-cache, must-revalidate)ERR_CACHE_MISSNo
Cache-Control: no-cachePage shown from the cacheNo
Cache-Control: max-age=0Page shown from the cacheNo
Cache-Control: private, no-cachePage shown from the cacheNo
No Cache-Control header at allPage shown from the cacheNo
303 redirect to a GET page with no-storeGET page loaded againYes, a GET

The rule behind the table: only no-store stops the copy from existing, and Chrome skips validation for history navigations, so no-cache and max-age=0 do not matter on Back. That makes no-store on a page that answers a POST the one server-side setting that produces this error.

PHP sessions send no-store by default

PHP’s session.cache_limiter setting defaults to nocache, so every script that starts a session sends the headers below, whatever your own code does. This is what PHP 8.2.35 sent for each value:

session_cache_limiter() values, headers sent by PHP 8.2.35

nocache (default)
Cache-Control: no-store, no-cache, must-revalidateplus Expires: Thu, 19 Nov 1981 08:52:00 GMT and Pragma: no-cache
private
Cache-Control: private, max-age=10800plus the same 1981 Expires date and Last-Modified
private_no_expire
Cache-Control: private, max-age=10800plus Last-Modified, no Expires
public
Cache-Control: public, max-age=10800plus Expires three hours ahead; never for pages with personal data

10800 seconds is the default session.cache_expire of 180 minutes. Switching to private_no_expire also made Back work in our test, but it lets the browser keep pages with personal data for three hours. Post/Redirect/Get fixes the error without weakening that, which is why it is the better fix.

To see which Cache-Control header a public page sends, paste its address into our HTTP header checker.

net::ERR_CACHE_MISS in an Android WebView

In an Android app the error usually has nothing to do with forms. It appears on the very first loadUrl() because the WebView is not allowed to use the network and can only look in its cache. Android’s WebSettings reference spells out why: network loads are blocked by default when the app does not hold the INTERNET permission.

AndroidManifest.xml · the permission goes directly under <manifest>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
    <uses-permission android:name="android.permission.INTERNET" />

    <application ...>
        ...
    </application>
</manifest>
  • Placement and spelling matter. The line belongs to <manifest>, not inside <application>, and the name is exactly android.permission.INTERNET. Reinstall the app after changing it.
  • Cache mode. LOAD_CACHE_ONLY (value 3) means “Don’t use the network, load from the cache”, so anything not cached fails with this error. The default is LOAD_DEFAULT (-1).
  • Blocked network loads. setBlockNetworkLoads(true) has the same effect as a missing permission. With the permission present the default is false.
  • Back to a POST result. The setCacheMode() documentation says that when navigating back, content is retrieved from the cache without revalidation. A WebView that goes Back to a form result behaves like Chrome, and the fix is Post/Redirect/Get on the site.
Kotlin · reset the WebView to network loading
webView.settings.cacheMode = WebSettings.LOAD_DEFAULT
webView.settings.blockNetworkLoads = false   // throws SecurityException without INTERNET

ERR_CACHE_MISS without a form

For ordinary pages Chrome goes Back, or restores a tab, with only LOAD_SKIP_CACHE_VALIDATION: it uses a stored copy when there is one and asks the network when there is not. A plain page therefore cannot produce ERR_CACHE_MISS through normal browsing. The cache-only loads that Chromium’s source and Android’s documentation confirm are these:

  • A restored tab that showed a form result. After a restart, a crash or reopening a closed tab, Chrome loads such a page from the cache only, exactly as on Back. You get the form variant of the error; open the page fresh through the site’s own link.
  • A cache-only client. WebViews in LOAD_CACHE_ONLY mode and apps without network access, as above. This is where the “can’t be loaded from the cache” variant usually comes from.

Forum answers also blame browser extensions and service workers. We found no path in Chromium’s source where either one causes this error and could not reproduce it, so treat them as unconfirmed. If it only happens with extensions on, test in an Incognito window, where extensions are off unless you allowed them, and turn them off one by one.

If the cause is on the server instead, for example the connection dropping, Chrome shows a different code. The ERR_CONNECTION_RESET guide covers dropped connections, and ERR_SSL_PROTOCOL_ERROR a broken HTTPS port.

Symptoms, causes and fixes

Symptom, cause, fix

  1. Symptom: Back after a checkout or login shows Confirm Form Resubmission and ERR_CACHE_MISS

    Cause
    The page was the answer to a POST and was sent with Cache-Control: no-store, often by PHP’s session defaults.
    Fix
    Redirect after the POST with header('Location: /done', true, 303); exit;. Visitors open the page fresh instead of reloading.
  2. Symptom: Duplicate orders, messages or sign-ups in the logs, a few seconds or minutes apart

    Cause
    Visitors reloaded a POST result and confirmed resubmission, or a tester had DevTools' Disable cache on.
    Fix
    Post/Redirect/Get, plus a one-time token in the form that the server accepts once. Untick Disable cache when testing Back.
  3. Symptom: net::ERR_CACHE_MISS on the first page an Android app loads

    Cause
    No android.permission.INTERNET in the manifest, or it sits inside <application>, so network loads are blocked.
    Fix
    Add <uses-permission android:name="android.permission.INTERNET" /> directly under <manifest> and reinstall.
  4. Symptom: WebView shows only pages it has seen before

    Cause
    setCacheMode(LOAD_CACHE_ONLY) or setBlockNetworkLoads(true).
    Fix
    Set LOAD_DEFAULT and blockNetworkLoads = false.
  5. Symptom: Confirm Form Resubmission and ERR_CACHE_MISS right after Chrome reopened its tabs, with no Back pressed

    Cause
    Chrome restored a tab whose page answered a POST and loads it from the cache only; the no-store page is not there.
    Fix
    Open the page fresh through the site's own link. Site owners: Post/Redirect/Get, so no tab ever holds a POST result.
  6. Symptom: This site can't be loaded from the cache, on a page with no form

    Cause
    A client that loads from the cache only, such as a WebView in LOAD_CACHE_ONLY mode or without network access. Extensions are blamed often but unconfirmed.
    Fix
    Check the app's cache mode and network permission. If it only happens with extensions on, test in Incognito and disable them one at a time.

How we reproduced it

A three-page PHP app on a local test server: a form, a result page and a link to a second page. A script drove headless Chrome through form, result, link, Back and Reload, and the server logged every request. The scripted reload goes through the DevTools protocol, which skips the dialog Chrome shows a person before it resends a POST; in a normal window that question comes first. The logs below are shortened to time, status and request. Before the change, the form posted to a result page that started a session:

Before · server log (shortened) and Chrome
[02:56:12] [200]: GET /form.php?to=result-nostore.php
[02:56:12] [200]: POST /result-nostore.php       <- form sent
[02:56:12] [200]: GET /other.html                <- link clicked
                                                  <- Back: no request at all
Chrome:  page.goBack: net::ERR_CACHE_MISS (POST /result-nostore.php)
[02:56:13] [200]: POST /result-nostore.php       <- scripted Reload, no dialog: form sent again
After · Post/Redirect/Get · server log (shortened)
[02:56:14] [200]: GET /form.php?to=prg.php
[02:56:14] [303]: POST /prg.php                  <- form sent, redirect
[02:56:14] [200]: GET /thanks.php
[02:56:14] [200]: GET /other.html                <- link clicked
[02:56:14] [200]: GET /thanks.php                <- Back: normal page
[02:56:15] [200]: GET /thanks.php                <- Reload: GET, no second order

How we tested

OS image
Debian 12
Versions
Chrome for Testing 153.0.8010.12 (headless)PHP 8.2.35 built-in servercurl 7.88.1Playwright 1.56.1
Tested on
Where
Local test server on 127.0.0.1, one Linux machine
Method
Form POST, link, Back, Reload (scripted, which skips Chrome's resubmit dialog); seven Cache-Control variants; each run with and without the back/forward cache; DevTools Disable cache via the DevTools protocol; server log checked for every POST

Not tested by us: Firefox, Safari and a real Android device. The WebView section relies on Android’s WebSettings reference, the Chrome internals on Chromium’s source (net_error_list.h, navigation_request.cc, navigation_controller_impl.cc, localized_error.cc), both read on 2 November 2026. Restored tabs and the reload dialog come from that source, not from our test.

On a VMHeaven Standard KVM you have root on the web server, so the nginx scope, the PHP session settings and the redirect are all yours to change, and the curl check above shows within a minute whether the 303 arrives.

Frequently asked

What does ERR_CACHE_MISS mean?

It is Chromium network error -400, 'The cache does not have the requested entry.' Chrome shows it when it may only load a page from its cache and no copy is there. Almost always you went Back or Forward to a page that answered a form (a POST), or Chrome restored such a tab after a restart, a crash or reopening a closed tab. The site sent that page with Cache-Control: no-store, so Chrome shows Confirm Form Resubmission instead of sending your data again.

How do I fix ERR_CACHE_MISS in Chrome?

If the page came from a search or filter, press reload and confirm in the dialog Chrome shows: it then sends the form again. If it came from a checkout, payment or booking, check your email or account first, then open the page fresh through the site's own link instead of reloading. Clearing the cache, flushing DNS or resetting Chrome does not help, because the page was never stored.

How do I stop Confirm Form Resubmission on my website?

Use Post/Redirect/Get: handle the POST, then answer with a 303 See Other redirect to a normal page, for example header('Location: /orders/123', true, 303); exit; in PHP. Back and Reload then repeat a harmless GET. Removing no-store also stops the error, but it lets browsers keep pages that may hold personal data.

Is it safe to resubmit the form?

Only if repeating the action is harmless. Resubmitting sends the same data to the server again, which can place a second order, payment or message. In our test a reload on the error page produced a second POST in the server log.

Why does my Android WebView show net::ERR_CACHE_MISS?

Usually the app lacks the INTERNET permission, so the WebView blocks network loads and can only read its cache. Add <uses-permission android:name="android.permission.INTERNET" /> directly under <manifest> and reinstall. Also check that the cache mode is not LOAD_CACHE_ONLY and that setBlockNetworkLoads is false.

Related articles

All troubleshooting