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

  1. Regenerate both internal headers together by calling Store::restore() or by removing X-Body-File/X-Content-Digest and letting write() recompute them
  2. Clear the HTTP cache so all entries are rebuilt consistently
  3. 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

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


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)