elastic/elasticsearch · error · IOException
md5 checksum mismatch, expected [{}], actual [{}]
Error message
md5 checksum mismatch, expected [{}], actual [{}] What it means
Thrown by GeoIpDownloader.indexChunks (the non-enterprise legacy path) after indexing all chunks of a database and flushing/refreshing the .geoip database index. The md5 of the streamed bytes is compared against the expected md5 from the provider; a mismatch means the downloaded bytes do not match what the provider advertised. IOException, so it is treated as a retryable download failure.
Source
Thrown at modules/ip-location/src/main/java/org/elasticsearch/ingest/geoip/GeoIpDownloader.java:274
md.update(buf);
IndexRequest indexRequest = new IndexRequest(DATABASES_INDEX).id(name + "_" + chunk + "_" + timestamp)
.create(true)
.source(XContentType.SMILE, "name", name, "chunk", chunk, "data", buf);
client.index(indexRequest).actionGet();
chunk++;
}
// May take some time before automatic flush kicks in:
// (otherwise the translog will contain large documents for some time without good reason)
FlushRequest flushRequest = new FlushRequest(DATABASES_INDEX);
client.admin().indices().flush(flushRequest).actionGet();
// Ensure that the chunk documents are visible:
RefreshRequest refreshRequest = new RefreshRequest(DATABASES_INDEX);
client.admin().indices().refresh(refreshRequest).actionGet();
String actualMd5 = MessageDigests.toHexString(md.digest());
if (Objects.equals(expectedMd5, actualMd5) == false) {
throw new IOException("md5 checksum mismatch, expected [" + expectedMd5 + "], actual [" + actualMd5 + "]");
}
return chunk;
}
// visible for testing
static byte[] getChunk(InputStream is) throws IOException {
byte[] buf = new byte[MAX_CHUNK_SIZE];
int chunkSize = 0;
while (chunkSize < MAX_CHUNK_SIZE) {
int read = is.read(buf, chunkSize, MAX_CHUNK_SIZE - chunkSize);
if (read == -1) {
break;
}
chunkSize += read;
}
if (chunkSize < MAX_CHUNK_SIZE) {
buf = Arrays.copyOf(buf, chunkSize);
}View on GitHub (pinned to db6a809a66)
Solutions
- Allow the next scheduled download to retry; transient mismatches usually clear.
- Verify the network path to the public geoip database host is clean (no intercepting proxy).
- Compare expected vs actual md5 in the logs: a wildly different actual suggests an error body; a near miss suggests truncation.
- Re-trigger the download manually or restart the ingest node if the stale-expected-md5 condition persists.
Defensive patterns
Strategy: retry
Try / catch
try {
downloader.indexChunks(name, is, chunk, expectedMd5, ts);
} catch (IOException e) {
if (e.getMessage().startsWith("md5 checksum mismatch")) {
// discard partial chunks, re-fetch, retry once; then let the scheduler handle it
throw e;
}
throw e;
} Prevention
- Keep the download path clean of intercepting proxies.
- Treat md5 mismatch as transient and rely on scheduled retries.
- Compare expected vs actual md5 in logs to diagnose proxy/truncation.
- Ensure credentials are valid to avoid hashing an error body.
When it happens
Trigger: GeoIpDownloader download flow -> indexChunks computes MessageDigests.toHexString(md.digest()) and compares to expectedMd5; mismatch throws IOException at the Objects.equals check.
Common situations: Truncated or corrupted download; proxy returning a partial/error body; unstable connection to the public geoip endpoint; mismatch between expected md5 (stale) and the freshly served file.
Related errors
- checksum mismatch, expected [{}], actual [{}]
- Unexpected sha256 response from [{}]
- Unexpected md5 response from [{}]
- too many redirects connection to [{}]
- {} not found
AI-assisted analysis of elastic/elasticsearch@db6a809a66 (2026-08-12).
Data as JSON: /api/errors/9ef112291d13f60c.
Report an issue: GitHub.