symfony/http-kernel · error · RuntimeException
X-Body-File and X-Content-Digest do not match.
Error message
X-Body-File and X-Content-Digest do not match.
What it means
When a response being written to the HttpCache store already has an X-Body-File header, Store::write() verifies that the body file path matches the path recomputed from the X-Content-Digest header. A mismatch means the digest and body-file headers disagree, so the response cannot safely be re-stored and the RuntimeException is thrown to prevent writing metadata pointing at the wrong body.
Solutions
- Regenerate both internal headers together by calling Store::restore() or by removing X-Body-File/X-Content-Digest and letting write() recompute them
- Clear the HTTP cache so all entries are rebuilt consistently
- Fix custom code that copies X-Content-Digest from one entry with X-Body-File of another
Example fix
// before
$response->headers->set('X-Content-Digest', $oldDigest);
$response->headers->set('X-Body-File', $newPath);
// after
$response->headers->remove('X-Content-Digest');
$response->headers->remove('X-Body-File');
$store->write($request, $response); // store generates a consistent digest+path pair Defensive patterns
Strategy: validation
Validate before calling
if ($response->headers->has('X-Body-File') && $response->headers->has('X-Content-Digest')
&& $digest !== $response->headers->get('X-Content-Digest')) {
$response->headers->remove('X-Body-File');
$response->headers->remove('X-Content-Digest');
}
$store->write($request, $response); Try / catch
try {
$store->write($request, $response);
} catch (\RuntimeException $e) {
// fall back to a clean response without internal cache headers
$response->headers->remove('X-Body-File');
$response->headers->remove('X-Content-Digest');
$store->write($request, $response);
} Prevention
- Keep digest and body-file headers as an atomic pair
- Do not copy responses across cache directories
- Clear the cache instead of patching entries
When it happens
Trigger: Store::write() with a response whose X-Content-Digest maps (via getPath()) to a different file path than the X-Body-File header value — typically a restored response whose internal headers were edited or mixed between cache entries.
Common situations: Copying cached responses between cache directories so digests no longer match paths; merging headers from two different cached responses; tampering with X-Content-Digest or X-Body-File in tests or custom stores.
Understand the failure class
Background: Checksum mismatch errors: "checksum verification failed", "digest mismatch", "expected vs actual checksum" — what they mean and how to fix them — this error's family across 41 libraries.
Related errors
- A restored response must have the X-Content-Digest header.
- Error when rendering
- Unable to process an ESI tag without a "src" attribute.
- Unable to process an SSI tag without a "virtual" attribute.
- Unable to create the store directory
AI-assisted analysis of symfony/http-kernel@aa3a39d728 (2026-09-13).
Data as JSON: /api/errors/9731eaa61b01bc37.
Report an issue: GitHub.
Appendix: source
Thrown at HttpCache/Store.php:191
* Existing entries are read and any that match the response are removed. This
* method calls write with the new list of cache entries.
*
* @throws \RuntimeException
*/
public function write(Request $request, Response $response): string
{
$key = $this->getCacheKey($request);
$storedEnv = $this->persistRequest($request);
if ($response->headers->has('X-Body-File')) {
// Assume the response came from disk, but at least perform some safeguard checks
if (!$response->headers->has('X-Content-Digest')) {
throw new \RuntimeException('A restored response must have the X-Content-Digest header.');
}
$digest = $response->headers->get('X-Content-Digest');
if ($this->getPath($digest) !== $response->headers->get('X-Body-File')) {
throw new \RuntimeException('X-Body-File and X-Content-Digest do not match.');
}
// Everything seems ok, omit writing content to disk
} else {
// Responses that cannot provide their content, like BinaryFileResponse or
// StreamedResponse, have no entity to store, so no entry is written
if (false === $content = $response->getContent()) {
return $key;
}
$digest = $this->generateContentDigest($response);
$response->headers->set('X-Content-Digest', $digest);
if (!$this->save($digest, $content, false)) {
throw new \RuntimeException('Unable to store the entity.');
}
if (!$response->headers->has('Transfer-Encoding')) {
$response->headers->set('Content-Length', \strlen($content));View on GitHub (pinned to aa3a39d728)