Troubleshooting Snapshot Repository Verification Failures
When POST _snapshot/<repo>/_verify fails — or a scheduled SLM run fails against the same repository — the fix depends entirely on which cause raised the RepositoryVerificationException, so diagnosis is a matter of reading the error and mapping it to one of a small set of root causes.
Verification failure is the single most common repository problem, and it is almost always one node’s problem masquerading as the whole repository’s. Because access to a repository is per node — each node signs its own requests with its own keystore credentials, as the managing snapshot repositories reference explains — a failure often means the repository is perfectly reachable from every node except one. This page is the diagnostic companion to registering an S3 snapshot repository: once registration succeeds but verification does not, work through the causes below in order of likelihood, and you will resolve nearly every case without guessing. The whole flow feeds the snapshot and searchable-snapshot subsystem, so a repository that verifies is the precondition for every backup and every mount downstream.
Prerequisites
- A registered S3 (or other object-store) repository whose
_verifyis failing or returning fewer nodes than the deployment has. - Shell access to the nodes to inspect keystores, plus
managecluster privilege to re-verify and reload secure settings. elasticsearch-pyv8.0+ for the diagnostic script, which reads structured error detail from the caught exception.
Implementation
Read the verify output and map the error to a cause
_verify returns the nodes that succeeded. The fastest first read is therefore to compare that list against the deployment’s node list — any node present in _cat/nodes but absent from the verify result is the failing one:
POST _snapshot/prod-s3-repo/_verifyWhen verification fails outright, the response body carries a RepositoryVerificationException whose reason names the cause. The decision flow below maps the reason text to the fix:
The causes, in the order you should suspect them:
- Missing or wrong credentials — the
reasonmentions access denied or a signature mismatch on every node. Either the keystore entries were never added, or the wrong access/secret pair was pasted. Fix thes3.client.default.*entries and reload. - A node that never reloaded — verification succeeds on most nodes and fails on one. That node’s keystore was edited but
POST _nodes/reload_secure_settingsnever activated it, or the node was added to the deployment after the initial rollout and never had the keys added at all. - Network, DNS, or endpoint — the
reasonis a connection timeout or unknown-host error. The node cannot reach the object-store endpoint; check egress and, for an S3-compatible store, that theendpointsetting is present and correct. - IAM permissions — access-denied specifically on write (
s3:PutObject) or list (s3:ListBucket). The credentials authenticate but the IAM policy does not grant the actions the verify blob write needs on the bucket and base_path. - Clock skew — a request-time-too-skewed error. S3 SigV4 rejects requests whose timestamps drift beyond a tolerance; a node with a wrong clock signs requests the store refuses.
- base_path collision — a path-already-in-use or unexpected-metadata error, usually because another cluster is writing to the same bucket and base_path.
- Read-only bucket or repository — writes are refused although reads work, because the repository was registered
readonly: trueor the bucket policy denies writes.
Diagnose from Python and read the structured detail
The v8 client raises ApiError for the wrapped RepositoryVerificationException; the useful detail is in exc.info, which holds the parsed error body including the per-cause reason. Catch ApiError (a sibling of TransportError, not a subclass) and log the structured info rather than the string:
import logging
from elasticsearch import Elasticsearch, ApiError, NotFoundError
logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s")
logger = logging.getLogger("repo_diag")
def diagnose_verification(es: Elasticsearch, repo: str) -> None:
try:
result = es.snapshot.verify_repository(name=repo)
logger.info("Repository '%s' verified on %d node(s) — no failure to diagnose.",
repo, len(result["nodes"]))
except NotFoundError:
logger.error("Repository '%s' is not registered — register it before verifying.", repo)
except ApiError as exc:
# exc.info carries the parsed RepositoryVerificationException body.
info = exc.info or {}
reason = ""
if isinstance(info, dict):
reason = info.get("error", {}).get("reason", "") if isinstance(info.get("error"), dict) else ""
logger.error("Verification failed (%s). reason=%s", exc.status_code, reason)
text = reason.lower()
if "access denied" in text or "signature" in text:
logger.error("→ Credentials/IAM: check s3.client.default.* keystore and bucket IAM policy.")
elif "unknown host" in text or "connect" in text or "timeout" in text:
logger.error("→ Network/endpoint: check egress and the endpoint setting on the failing node.")
elif "skew" in text or "request time" in text:
logger.error("→ Clock skew: sync NTP across all nodes.")
elif "already" in text or "metadata" in text:
logger.error("→ base_path collision: give this cluster a distinct base_path.")
elif "read" in text and "only" in text:
logger.error("→ Read-only: remove readonly:true or grant s3:PutObject.")
if __name__ == "__main__":
es = Elasticsearch(
"https://es-cluster-01:9200",
api_key="YOUR_BASE64_ENCODED_API_KEY",
verify_certs=True,
)
diagnose_verification(es, "prod-s3-repo")For failures that verify hints at but does not fully explain — intermittent write errors, or a store that behaves under load but not at rest — run the repository analysis API, which drives concurrent reads and writes and surfaces consistency problems verification’s single blob cannot:
POST _snapshot/prod-s3-repo/_analyze?blob_count=100&max_blob_size=1gbVerification
After applying a fix, re-run verification and confirm every data node returns — a partial list means the fix landed on some nodes but not the one that was actually failing:
POST _snapshot/prod-s3-repo/_verify{
"nodes": {
"n1x2...": { "name": "es-01" },
"n3y4...": { "name": "es-02" },
"n5z6...": { "name": "es-03" }
}
}Cross-check the returned names against GET _cat/nodes?v&h=name,node.role so no data node is silently absent; only when the two lists match is the repository trustworthy for the next scheduled backup.
Gotchas and edge cases
- A partial pass is still a failure. Verification returning three of four nodes is not “mostly working” — the fourth node will fail every snapshot that touches a shard it hosts, producing
PARTIALsnapshots. Always reconcile the verify result against the full node list, not just check that the call returned200. - A node added after rollout has no keys. Autoscaling or a manual node addition brings up a node whose keystore never received
s3.client.default.*. It joins the deployment fine, serves queries fine, and fails repository verification silently until someone reads the node list. Bake keystore seeding into node provisioning. - Clock skew looks like a credentials problem. A request-time-skew rejection returns an auth-flavoured error even though the credentials are correct, sending people down the wrong path. If credentials are provably right but one node fails, check that node’s clock before re-pasting keys.
- Read-only can be intentional. A DR-mirror deployment deliberately registers the repository
readonly: trueso it can restore but never mutate the primary’s snapshots. A write failure on such a mirror is expected — do not “fix” it by making the mirror writable.
FAQ
Verification passes on most nodes but fails on one — what is wrong?
Almost always that one node is missing a live keystore credential. Either its keystore never received the s3.client.default.* entries (common for a node added after the initial rollout), or the entries were added but POST _nodes/reload_secure_settings was never run on it. Add the keys on that node and reload secure settings, then re-verify and confirm the node now appears in the returned list.
Where do I read the actual reason a verification failed?
In the RepositoryVerificationException body — over HTTP it is the error.reason field of the response, and in the Python v8 client it is inside exc.info on the caught ApiError. The reason text distinguishes an access-denied credentials problem from a connection/DNS network problem, a clock-skew rejection, or a base_path collision, which is what tells you which fix to apply.
My credentials are correct but one node still fails — what else?
Check that node’s clock and its network path. S3 request signing rejects requests whose timestamps drift too far, returning an auth-flavoured error that mimics a credentials problem, so a node with a wrong clock fails verification despite correct keys. A node that cannot reach the object-store endpoint — blocked egress, or a missing endpoint setting for an S3-compatible store — fails with a connection or unknown-host error instead.
Related
- Registering an S3 Snapshot Repository — the registration walkthrough whose verify step this page diagnoses.
- Managing Snapshot Repositories — the full repository anatomy, keystore model, and analysis API.
- Snapshot Lifecycle Management & Searchable Snapshots — why a verifying repository is the precondition for every backup and mount.
← Back to Managing Snapshot Repositories · SLM & Searchable Snapshots