{"record":{"id":"bcbdb5b6262cc75c","repo":"elastic/elasticsearch","slug":"checksum-mismatch-expected-actual","errorCode":null,"errorMessage":"checksum mismatch, expected [{}], actual [{}]","messagePattern":"checksum mismatch, expected \\[(.+?)\\], actual \\[(.+?)\\]","errorType":"exception","errorClass":"IOException","httpStatus":null,"severity":"error","filePath":"modules/ip-location/src/main/java/org/elasticsearch/ingest/geoip/EnterpriseGeoIpDownloader.java","lineNumber":371,"sourceCode":"                .create(true)\n                .source(XContentType.SMILE, \"name\", name, \"chunk\", chunk, \"data\", buf);\n            client.index(indexRequest).actionGet();\n            chunk++;\n        }\n\n        // May take some time before automatic flush kicks in:\n        // (otherwise the translog will contain large documents for some time without good reason)\n        FlushRequest flushRequest = new FlushRequest(DATABASES_INDEX);\n        client.admin().indices().flush(flushRequest).actionGet();\n        // Ensure that the chunk documents are visible:\n        RefreshRequest refreshRequest = new RefreshRequest(DATABASES_INDEX);\n        client.admin().indices().refresh(refreshRequest).actionGet();\n\n        String actualMd5 = MessageDigests.toHexString(md5.digest());\n        String actualChecksum = digest == null ? actualMd5 : MessageDigests.toHexString(digest.digest());\n        String expectedChecksum = checksum.checksum;\n        if (Objects.equals(expectedChecksum, actualChecksum) == false) {\n            throw new IOException(\"checksum mismatch, expected [\" + expectedChecksum + \"], actual [\" + actualChecksum + \"]\");\n        }\n        return Tuple.tuple(chunk, actualMd5);\n    }\n\n    // visible for testing\n    static byte[] getChunk(InputStream is) throws IOException {\n        byte[] buf = new byte[MAX_CHUNK_SIZE];\n        int chunkSize = 0;\n        while (chunkSize < MAX_CHUNK_SIZE) {\n            int read = is.read(buf, chunkSize, MAX_CHUNK_SIZE - chunkSize);\n            if (read == -1) {\n                break;\n            }\n            chunkSize += read;\n        }\n        if (chunkSize < MAX_CHUNK_SIZE) {\n            buf = Arrays.copyOf(buf, chunkSize);\n        }","sourceCodeStart":353,"sourceCodeEnd":389,"githubUrl":"https://github.com/elastic/elasticsearch/blob/db6a809a667c081ca1dc7500389d26975573215f/modules/ip-location/src/main/java/org/elasticsearch/ingest/geoip/EnterpriseGeoIpDownloader.java#L353-L389","documentation":"Thrown by EnterpriseGeoIpDownloader.indexChunks after a Maxmind or Ipinfo database archive is streamed, chunked, and indexed into the .geoip databases index. The bytes received from the provider are hashed (md5, or sha256 when supplied) and the result is compared against the checksum advertised by the provider; a mismatch means the bytes that arrived are not the bytes the provider vouched for. It is an IOException so the downloader task treats it as a transient/retryable download failure rather than a permanent config error.","triggerScenarios":"updateDatabases() -> downloaderFor(db).download() -> indexChunks(name, inputStream, chunk, checksum, timestamp). The expected checksum comes from Checksum.sha256(...) (Maxmind) or Checksum.md5(...) (Ipinfo). Mismatch fires at the Objects.equals check on line 370 when the streamed bytes' hash differs from checksum.checksum.","commonSituations":"Truncated download caused by a dropped connection or proxy timeout; a transparent proxy returning a login/HTML page with HTTP 200; the provider rotating the file mid-download; clock/sync issues causing a stale cached expected checksum; disk or translog write issues corrupting indexed chunks before the hash is computed (less likely since hash is over the source stream).","solutions":["Wait for the next scheduled download run (the task executor retries on its interval); transient corruption usually self-heals.","Verify outbound network path to the provider (download.maxmind.com or ipinfo.io) is not intercepted by a captive portal or authenticating proxy returning HTTP 200 with an HTML body.","Confirm the license key / token is still valid; an auth-failure body can still be 200 bytes that fail the hash.","Check the cluster logs for the expected vs actual values: a totally wrong actual (e.g. an HTML hash-shaped substring) points to a proxy; a close-but-wrong actual points to truncation.","If persistent, delete the affected database configuration and re-create it to force a fresh checksum negotiation."],"exampleFix":null,"handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"// the downloader task already catches and retries on its schedule;\n// if calling indexChunks directly in a test/tool, wrap it:\ntry {\n    downloader.indexChunks(name, in, chunk, checksum, ts);\n} catch (IOException e) {\n    if (e.getMessage().startsWith(\"checksum mismatch\")) {\n        // discard partial chunks and retry the download from scratch\n        deleteOldChunks(name, chunk);\n        throw e; // let the scheduler retry\n    }\n    throw e;\n}","preventionTips":["Keep the download path free of intercepting proxies that rewrite bodies.","Treat checksum mismatch as transient; let the scheduled task retry rather than manual partial fixes.","Monitor expected vs actual values in logs to distinguish proxy interception from truncation.","Ensure credentials are valid so auth-failure bodies are not hashed as the database."],"tags":["geoip","checksum","network","download","data-integrity"],"backgroundTag":null,"analyzedSha":"db6a809a667c081ca1dc7500389d26975573215f","analyzedAt":"2026-08-12T01:39:14.192Z","schemaVersion":2},"datasetVersion":"2026-08-12T12:31:55.035Z"}