Skip to main content
RunBook Academy

← All runbooks in Observability

critical riskdata loss risk~90 min

Runbook: Recover Loki

1 · Prerequisites

Confirm every item is in place before any state change.

  • Loki Architecture Overview
  • Loki Configuration
  • Loki Storage
  • Loki Loss
  • Loki Validation
  • Loki Backup
  • Admin access to the Loki deployment (Kubernetes or systemd) and to the object store bucket
  • The Loki configuration in version control, with the commit that is currently deployed identified
  • Credentials for the object store that are known to work, tested before the incident rather than during it
  • A second operator available. Several steps here are irreversible and are not safe to take alone at 03:00

2 · Pre-checks

Read-only diagnostic commands. If any of these don't match expected output, stop and investigate further.

  • · The loss shape is classified as INGESTER, OBJECT STORE or INDEX/COMPACTOR before any action. The three have different first moves, and the first move for one of them destroys the evidence for the other two.
  • · The compactor is confirmed STOPPED if any part of the object store is being restored, replaced or repointed. It is the only Loki component that deletes, and it will happily apply retention to a half-restored bucket.
  • · The suspect window is written down with a start time in UTC, and it is established whether the platform is losing data NOW or lost data in the past. Those are different urgencies and different procedures.
  • · It is established whether the failure is on the write path, the read path, or both. Ingest still working with queries failing rules out the ingester and points at the read path or the index.
  • · The object store is confirmed reachable with the credentials Loki is using - not with your own admin credentials, which are almost certainly broader.
  • · The current running configuration has been captured from the /config endpoint before any change, so there is something to diff against afterwards.
  • · The RPO is stated out loud: how much log data the organisation has already agreed it can lose. Recovery decisions below trade time against data, and that trade needs a number before it needs an operator.
  • · A second operator is present and has agreed to the plan. Moving a WAL segment aside and restarting a compactor are both one-way doors.

3 · Procedure

Execute each step in order. Verify the expected output of a step before moving to the next.

  1. 1Classify the loss shape. Read /ready on each component, /distributor/ring for ingester states, and the ingester logs. Missing or unreadable WAL is INGESTER. Missing chunks or index objects is OBJECT STORE. Partial results over a specific window with everything else healthy is INDEX/COMPACTOR. Do not proceed until one of the three is named.
  2. 2STOP THE COMPACTOR if the object store is involved in any way. Scale the compactor to zero replicas, or stop its unit. It is the only component in Loki that deletes data, and a compactor running against a partially restored bucket applies retention to gaps and makes the loss permanent. This step precedes every bucket action below and is not optional.
  3. 3Capture the evidence before restarting anything. Save the ingester logs, the /config output, the ring state and the compactor status. An ingester restarted before its log is read has taken the CRC error, the replay progress and the flush state with it.
  4. 4INGESTER - let the WAL replay finish before concluding anything. A restarting ingester is not ready until replay completes, and replay time scales with the unflushed window: a busy tenant with a long chunk_idle_period takes minutes, a four-hour WAL takes hours. /ready returning not-ready during replay is correct behaviour, not a fault.
  5. 5INGESTER - if replay fails on a corrupt segment, MOVE the segment aside rather than deleting it. The ingester logs a CRC error and refuses to start. Moving the segment lets the ingester start with a truncated WAL; every series in that segment is lost, and the moved file is the only chance of ever accounting for what was in it.
  6. 6OBJECT STORE - verify the restored bucket contains the window you need BEFORE pointing Loki at it. List objects under the tenant prefix for the affected period. A bucket restored from a stale snapshot is back but incomplete, and the RPO is the snapshot age, not zero.
  7. 7OBJECT STORE - confirm credentials, region and prefix against what Loki is configured to use. A 301 PermanentRedirect on every request is a wrong region. Access denied on a subset of operations is an IAM policy that covers reads but not the compactor actions, which is a failure that only appears hours later.
  8. 8INDEX/COMPACTOR - clean up a half-built index prefix before restarting the compactor. A compactor killed mid-cycle leaves a partial prefix and refuses to start against it. Move the partial prefix aside; do not delete it until queries over that window are proven correct.
  9. 9Restore configuration from version control, not from memory. Deploy the known-good commit rather than hand-editing the running config. Then diff /config against that commit and confirm they agree - a hand-edit made during an incident is the drift that causes the next one.
  10. 10Bring components up in dependency order: object store reachable, then ingesters, then queriers, then the compactor LAST. The compactor is the last thing to start and the first thing to stop, for the same reason in both directions.
  11. 11Confirm the ring converged before trusting anything else. /distributor/ring must show every expected ingester ACTIVE. Distributors writing against a stale ring accept pushes and deliver them nowhere, which looks exactly like a healthy recovery for about ten minutes.
  12. 12Prove the platform end to end with a synthetic push and a query for it back. Push one line with a known marker, then query it back. That single round trip proves the distributor, the ingester, the flush path and the read path together; no combination of readiness checks proves the same thing.
  13. 13Establish the gap boundaries and record them. Which window is missing, which tenants, and whether it was lost or merely delayed. A recovered Loki with an undocumented gap will be queried during the next incident by someone who assumes the absence of lines means the absence of events.
  14. 14Restore the compactor and watch its first cycle. The first compaction pass after an outage is the most dangerous one, because it is the first time retention is applied to whatever shape the bucket is now in. Watch it complete before closing the incident.

4 · Verification

Confirm the procedure actually fixed the problem.

  • ✓/ready returns ready on every component, including the compactor - which is ready only after it acquires its ring lock, so a naive "any 200 is fine" check passes while the compactor is still not running.
  • ✓/distributor/ring shows every expected ingester in state ACTIVE. A missing or unhealthy instance means the ring has not converged and writes are being lost silently.
  • ✓A synthetic line pushed to /loki/api/v1/push with a unique marker is queryable back within seconds. This is the only check that proves the write and read paths together.
  • ✓A query against a historical window - older than query_ingester_within, so it must be served from the object store rather than from ingester memory - returns lines. Without this, a broken bucket path stays hidden for roughly thirty minutes.
  • ✓The compactor status shows a completed cycle with no tables stuck in a compacting state, and its first post-recovery cycle has been watched to completion rather than assumed.
  • ✓loki_canary_last_success (if the canary is deployed) carries a timestamp within the last minute, and the canary write and read counters are both climbing.
  • ✓The running config from /config matches the version-controlled commit that was deployed, field by field for retention and limits. A hand-edit that survived the incident is a defect, not a fix.
  • ✓The per-tenant overrides are actually loaded, confirmed at /runtime-config rather than inferred from the file on disk. -verify-config does not load the runtime config and will not catch this.
  • ✓The data gap is written down: start, end, tenants, and lost-versus-delayed. "Loki is back" and "no logs were lost" are two different statements and only one of them has been demonstrated.

5 · Rollback

If verification fails, undo the procedure in reverse order.

  • ↶Classification and evidence capture are read-only. Everything from the compactor stop onward changes state, and the ordering above exists so the reversible steps come first.
  • ↶To undo a compactor stop: start it again. This is safe and is the intended end state - but only after the bucket contents have been verified, never as a reflex to make an alert stop.
  • ↶To undo a configuration deploy: redeploy the previous commit and reload, then diff /config again. Never leave the running config and the repository disagreeing at the end of an incident.
  • ↶A WAL segment moved aside cannot be un-moved in any useful sense: the ingester has already started without it and the series it held are gone. Keep the file, record what window it covered, and treat that window as lost.
  • ↶A bucket that has had a compactor run against it mid-restore cannot be rolled back. The deletions are real. This is precisely why the compactor stop is step 2 and not step 9.
  • ↶A restore that overwrote newer objects with older ones is not reversible either. Restore into a separate prefix or bucket and verify before repointing Loki, whenever the object store offers the option.
  • ↶If per-tenant rate limits were raised to absorb a replay flood, lower them again once the backlog drains, and record the change. A limit raised during an incident and never reviewed is how the next capacity surprise is built.

6 · Escalation

When the runbook isn't enough, contact:

  • · Escalate before moving any WAL segment aside. It is a deliberate, irreversible data-loss decision, and it needs a second operator and an owner who can accept the gap.
  • · Escalate to whoever owns the object store the moment the bucket, its credentials, its region or its lifecycle policy is implicated. Loki configuration changes cannot fix a storage-side problem and will obscure it.
  • · Escalate to the platform owner before any restore that could overwrite newer data with older data. The RPO is a business decision, not an on-call one.
  • · Escalate to security if the bucket was deleted, emptied or made public by something nobody can account for. An object store holding every retained log line is a compliance event before it is a durability event.
  • · Escalate to compliance or legal if the lost window covers a system under a retention obligation. The gap has its own clock and its own reporting duty, independent of the technical recovery.
  • · Escalate if recovery time is going to exceed the stated RTO. A partial recovery that restores the read path for recent data while historical restore continues is often the better answer, and that is a decision for the incident owner rather than the operator running the commands.
  • · Escalate to the application teams once the gap boundaries are known. They are the people who will otherwise draw conclusions from an empty panel during their own incident next week.

“Recover Loki” is not one procedure. It is three, and they are distinguished by which of Loki’s three durability layers was lost. The layers fail independently, present almost identically to a user, and have first moves that are actively harmful when applied to the wrong one.

   distributor            <- auth, rate limit, no durable state
        |
        v
   ingester  --->  WAL on local disk     <- unflushed window, minutes
        |
        v
   chunks + TSDB index ---> object store <- canonical, everything older
                                    ^
                                    |
                              compactor  <- the ONLY component
                                             that deletes anything

Read that diagram once more before touching anything. The compactor sits at the bottom with delete authority over the canonical store, and almost every way this runbook can turn a recoverable incident into a permanent one runs through it.

Classify first: which layer was lost

ShapeWhat is goneWhat it looks likeFirst move
INGESTERIn-memory streams, and the WAL that would replay themRecent lines missing; historical queries fine; ingester restarting or refusing to startRead the ingester log, then let replay finish
OBJECT STOREChunks and the indexHistorical queries empty; the last ~30 minutes still answer from ingester memoryStop the compactor, then verify the bucket
INDEX / COMPACTORA coherent index for some windowPartial results over one window; everything else fineStop the compactor, then inspect the prefix

The middle row hides a trap worth naming. When the bucket is gone, recent queries keep working, because the querier serves the recent window from ingester memory for query_ingester_within - thirty minutes by default. For that half hour the platform looks healthy to anyone who checks “is Loki up” with a five-minute query. The failure surfaces later, and it surfaces as history, which is exactly when people trust it most.

When this runbook applies, and when it does not

It applies when Loki cannot return the truth: lines that were ingested are unavailable, or lines that should be ingestible are not being accepted, because a durability layer has been lost or damaged.

It does not apply when:

  • Loki is healthy and the logs never arrived. If the distributor never received the bytes, there is nothing to recover here; that is the ingestion path, and it starts on the emitting host.
  • The query is wrong. An empty panel against a healthy cluster is a query problem, and running a recovery procedure against it will make things worse for no reason.
  • The window is outside retention. The compactor deleted it by policy. That is a retention decision to revisit, not an incident to recover.
  • Loki is merely slow. Saturation is a capacity and query-shape problem. Restarting components under load turns a slow platform into an unavailable one.

Blast radius

ActionReversible?What it costs if wrong
Reading /ready, /config, ring and logsn/aNothing
Stopping the compactorYes - start it againRetention pauses; index grows. Cheap
Restarting an ingesterPartlyReplay time; and any in-memory state not in the WAL
Moving a WAL segment asideNoEvery series in that segment, permanently
Pointing Loki at a restored bucketDependsA stale snapshot silently sets your RPO
Running the compactor mid-restoreNoRetention applied to gaps. Permanent deletion
Raising limits to absorb a replay floodYesBack-pressure spiral if the flood is larger than the platform

Two rows are one-way doors. Both are listed in the escalation path because neither should be walked through by one tired operator alone.

Step 1 - Classify, without changing anything

Read-only / Safethe three reads that name the loss shape
LOKI=http://loki.monitoring.svc:3100

curl -s "$LOKI/ready"

curl -s "$LOKI/distributor/ring" | jq '.instances | map({id: .id, state: .state})'

kubectl logs -n loki -l component=ingester --tail=200 | grep -iE 'wal|replay|crc|error' | tail -20
ready

[
{ "id": "loki-ingester-0", "state": "ACTIVE" },
{ "id": "loki-ingester-1", "state": "JOINING" }
]

Illustrative output

An ingester stuck in JOINING is usually replaying its WAL, and that is normal behaviour rather than a fault. /ready is component-aware: the ingester is not ready until replay finishes, the compactor is not ready until it acquires its ring lock, and the querier is not ready until it has loaded index files. A check that accepts any HTTP 200 as healthy will miss all three.

Step 2 - Stop the compactor, before anything touches the bucket

If the object store is implicated in any way - restored, repointed, partially available, or merely suspected - the compactor stops first.

Service impact possiblethe cheapest irreversibility control in this runbook
kubectl scale -n loki deployment/loki-compactor --replicas=0

kubectl get pods -n loki -l component=compactor

Stopping the compactor pauses retention and lets the index grow. That is the entire cost, and it is trivially reversible. Leaving it running against a bucket that is mid-restore lets it apply retention to a window that only looks old because the objects are not back yet.

Step 3 - INGESTER: replay, and the segment you may have to sacrifice

Let replay finish. Replay time scales with the unflushed window, which is set by chunk_idle_period and the flush behaviour: a longer flush interval means a longer WAL, which means a longer recovery. That relationship is worth remembering at configuration-review time, because it is where your RTO is actually decided.

If the ingester refuses to start and logs a CRC error against a WAL segment, you are at the one-way door.

Data-loss riskmove it aside - never delete it
# Second operator present, and the window this segment covers recorded first.
WAL_DIR=/loki/wal
SEGMENT=00000042

mv "$WAL_DIR/$SEGMENT" /var/tmp/loki-wal-quarantine-$SEGMENT

Moving the segment lets the ingester start with a truncated WAL. Every series in that segment is gone. Keep the file: it is the only artefact that can later answer “what was in the gap”, and deleting it converts an accountable loss into an unexplainable one.

Count series before and after the restart, decide whether the gap fits inside the stated RPO, and escalate if it does not.

Step 4 - OBJECT STORE: verify before you repoint

A restored bucket is not a recovered bucket. Verify that the objects covering the affected window are actually present, under the prefix and in the region Loki is configured to use.

Read-only / Safedoes the restored bucket contain the window you need?
BUCKET=prod-loki-chunks
TENANT=prod

aws s3api list-objects-v2 --bucket "$BUCKET" \
--prefix "index/$TENANT/" --max-items 20 \
--query 'KeyCount' --output text

Two failure shapes are worth recognising by their signature. Every request returning 301 PermanentRedirect is a region mismatch, not a permissions problem. Reads succeeding while compactor operations are denied is an IAM policy that was written against the read path only - which passes every check you run today and fails at the next retention cycle.

Restore into a separate prefix or bucket and verify there, whenever the object store offers that option. Restoring in place over newer objects is not reversible.

Step 5 - Bring it back in dependency order

Object store reachable, then ingesters, then queriers, then the compactor last. Deploy configuration from the version-controlled commit rather than hand-editing the running config, then prove the two agree.

Read-only / Safevalidate the config before the binary, and the ring after it
loki -config.file=/etc/loki/loki.yaml -verify-config

curl -s "$LOKI/config" | head -40

curl -s "$LOKI/runtime-config" | jq '.overrides | keys'

Step 6 - Prove it end to end, then find the gap

Readiness proves processes started. Only a round trip proves the platform works.

Read-only / Safepush a known marker, then find it again
MARKER="recovery-$(date -u +%s)"
NOW_NS="$(date +%s)000000000"

curl -sf -X POST "$LOKI/loki/api/v1/push" \
-H 'Content-Type: application/json' \
-d '{"streams":[{"stream":{"job":"smoke"},"values":[["'"$NOW_NS"'","'"$MARKER"'"]]}]}'

sleep 5

export LOKI_ADDR="$LOKI"
logcli query --since=5m --limit=5 --quiet '{job="smoke"}'

Then run one query over a window older than query_ingester_within. Recent queries are served from ingester memory and will succeed even when the object store path is broken; a historical query is the only one that exercises the bucket.

Finally, establish the gap. Start, end, tenants, and lost versus merely delayed. A recovered Loki whose gap is undocumented is worse than an obviously broken one, because the next incident will read that empty window as evidence that nothing happened.

Hold, when the queue is draining

If collectors buffered during the outage and are now replaying, the correct action is frequently to watch rather than to act. Lines are arriving late rather than not at all, and a restart during the drain converts late into lost.

Give the hold the same shape as any other action: an owner by name, a checkpoint (ring state and distributor byte rate re-read every five minutes), and an exit condition (the backlog drains, or rejections start climbing, at which point the replay flood is larger than the platform and limits become a real decision).

Common patterns

What you seeShapeWhere to look
Recent lines missing, history fineINGESTERWAL replay progress in the ingester log
Ingester will not start, CRC errorINGESTERThe named WAL segment - this is the one-way door
History empty, last 30 min fineOBJECT STOREBucket contents, then query_ingester_within
Every bucket request 301OBJECT STORERegion mismatch, not permissions
Reads fine, retention never advancesCOMPACTORIAM policy missing the compactor’s actions
Partial results over one windowINDEXHalf-built index prefix from a killed compaction
Compactor will not start after a killINDEXMove the partial prefix aside, do not delete
Pushes accepted, nothing queryableRING/distributor/ring - a stale ring accepts and discards
Everything ready, overrides ignoredCONFIG/runtime-config, not the file on disk
Recovery looks fine, gap unknownPROCESSNobody established the boundaries

Prevention

Three controls decide how this incident goes before it starts.

The first is the WAL on its own volume, backed up on its own schedule. It is the only place the unflushed window exists, and a volume loss that takes both the WAL and the object store turns a bounded gap into an unbounded one.

The second is a compactor that runs as a singleton with leader election. Two compactors racing corrupt the index, which produces the partial-results shape that is the hardest of the three to diagnose.

The third is rehearsing this procedure against a non-production Loki pointed at a separate bucket. Production recovery is a poor place to discover that the credentials in the manifest have never been used for a write. Every step in this runbook is cheap to practise and expensive to learn during an incident.

References

  1. Loki: storage
  2. Loki: retention and the compactor
  3. Loki configuration reference
  4. Loki HTTP API
  5. Loki Canary
  6. Loki: multi-tenancy
  7. logcli