Skip to main content

Configuration

All flags follow VictoriaMetrics naming conventions (-flagName=value).

Server​

FlagEnvDefaultDescription
-listenLISTEN_ADDR:3100Listen address
-backendVL_BACKEND_URLhttp://localhost:9428VictoriaLogs backend URL
-proc-rootPROC_ROOT/procProc filesystem root used for CPU/memory/disk/network/PSI metrics (/proc for container scope, /host/proc for host scope)
-host-proc-rootHOST_PROC_ROOT/procSeparate proc filesystem root used for host-scope reads (stat, meminfo, pressure/{cpu,memory,io}). Set to /host/proc when the chart mounts the five individual host /proc files at that path; keeps self-scope reads on /proc while host-scope reads pull from the dedicated mount. Falls back to -proc-root when unset.
-backend-version-strictβ€”falsePromote the existing soft backend version check to a hard startup failure. When true, a /health failure, non-2xx response, or sub-minimum semver causes the proxy to exit during startup instead of logging a warning. Mirrors the upstream vmselect strict-version pattern.
-metadata-default-lookbackβ€”12hDefault lookback bound applied to /labels, /label/{name}/values, and /series when the client omits both start and end. Bounds the backend time window so an unbounded Grafana label-discovery query cannot fan out across all retention. 0 disables (prior unbounded behavior).
-ruler-backendRULER_BACKEND_URLβ€”Optional rules backend for legacy Loki YAML rules routes and Prometheus-style /prometheus/api/v1/rules passthrough
-alerts-backendALERTS_BACKEND_URLβ€”Optional alerts backend for Loki and Prometheus-style alerts endpoints (defaults to -ruler-backend)
-log-levelβ€”infoLog level: debug, info, warn, error
-tls-cert-fileβ€”β€”TLS certificate file for HTTPS
-tls-key-fileβ€”β€”TLS key file for HTTPS
-tls-client-ca-fileβ€”β€”CA file for verifying HTTPS client certificates
-tls-require-client-certβ€”falseRequire and verify HTTPS client certificates
-log-request-sample-rateβ€”0Log one in every N requests for access logging (0 = disabled). Superseded by adaptive sampling (see below).
-log-bufferedβ€”trueWrite logs in the background to avoid slowing down requests under high load
-log-stats-intervalβ€”10sHow often to print a request statistics summary (total, errors, latency, cache rate)
-log-rate-thresholdβ€”10When traffic exceeds this rate (req/s), replace per-request logs with periodic summaries. Errors are always logged.
-cache-disabledβ€”falseDisable the in-memory L1 cache entirely (useful for benchmarking raw translation overhead)

Label Translation​

See Translation Modes Guide for mode-selection profiles and exact underscore vs dotted exposure behavior across labels, field APIs, and structured metadata.

FlagEnvDefaultDescription
-label-styleLABEL_STYLEpassthroughpassthrough or underscores
-metadata-field-modeMETADATA_FIELD_MODEhybridnative, translated, or hybrid for detected_fields and structured metadata exposure
-emit-structured-metadataβ€”trueEnable Loki categorize-labels response encoding: requests with X-Loki-Response-Encoding-Flags: categorize-labels emit 3-tuples [timestamp, line, metadata], while default/no-flag requests stay canonical 2-tuples
-patterns-enabledβ€”trueEnable GET /loki/api/v1/patterns (Grafana Logs Drilldown patterns view). When false, the endpoint returns 404 not_found
-patterns-autodetect-from-queriesβ€”falseWarm /loki/api/v1/patterns cache from successful query and query_range responses (global autodetect mode, opt-in)
-patterns-customβ€”β€”Static custom patterns prepended to every /patterns response. Accepts JSON string array or newline-separated text payload
-patterns-custom-fileβ€”β€”File path for static custom patterns prepended to every /patterns response. Supports JSON string array or newline-separated text with optional # comments
-patterns-persist-pathβ€”β€”Disk path for persisted patterns snapshot JSON file (empty disables persistence)
-patterns-persist-intervalβ€”30sPeriodic flush interval for in-memory patterns snapshot
-patterns-startup-stale-thresholdβ€”60sFreshness threshold used by startup warm logic/peer snapshot cache
-patterns-startup-peer-warm-timeoutβ€”5sStartup timeout for peer warm merge of pattern snapshots
-field-mappingFIELD_MAPPINGβ€”JSON custom field mappings
-stream-fieldsβ€”β€”Comma-separated _stream_fields labels used for stream selector optimization and label-surface hints
-extra-label-fieldsEXTRA_LABEL_FIELDSβ€”Comma-separated additional VL fields to expose on label-facing APIs and alias resolution paths (for example host.id,k8s.cluster.name)

Label Style Modes​

ModeWhen to UseResponseQuery
passthroughVL stores underscore labels (Vector/FluentBit normalize)No translationNo translation
underscoresVL stores OTel dotted labels (OTLP direct)service.name β†’ service_name{service_name="x"} β†’ VL "service.name":"x"

Custom Field Mappings​

./loki-vl-proxy -label-style=underscores \
-field-mapping='[{"vl_field":"my_trace_id","loki_label":"traceID"}]'

Custom Drilldown Patterns​

Examples:

# Inline JSON list
./loki-vl-proxy \
-patterns-custom='["time=\"<_>\" level=info msg=\"finished unary call\"","grpc.code=<_> grpc.method=<_>"]'

# File-backed list (JSON array or newline-separated text)
./loki-vl-proxy \
-patterns-custom-file=/etc/loki-vl-proxy/custom-patterns.json

Extra Label Fields (-extra-label-fields)​

Use this when you need explicit label exposure and alias resolution for fields that are not always discoverable from stream_field_names.

What it affects:

  • GET /loki/api/v1/labels: appends configured fields to the label set.
  • GET /loki/api/v1/label/{name}/values: resolves underscore↔dot aliases for custom fields.
  • GET /loki/api/v1/index/volume and GET /loki/api/v1/index/volume_range: resolves targetLabels aliases (for example host_id -> host.id) before backend grouping.

How values are handled:

  • Accepts a comma-separated list.
  • Entries are normalized to canonical VL field names by translator rules.
  • Duplicates are removed.
  • It extends exposure and aliasing only; it does not create or index fields in VictoriaLogs.

Caching and limits:

  • It reuses existing label-path caches (/labels and /label/{name}/values), so repeated lookups do not repeatedly rescan backend metadata.
  • There is no dedicated hard cap on how many entries you can set in -extra-label-fields; behavior is intentionally open to match VictoriaLogs field discovery.
  • Existing proxy safeguards still apply (endpoint TTLs, backend timeouts, and standard response-size protections).
  • Practical guidance: keep this list focused on fields you want Loki users to filter on frequently; very large lists can add UI noise in Grafana.

Examples:

# Conservative Loki-facing UX + explicit custom fields
./loki-vl-proxy \
-label-style=underscores \
-metadata-field-mode=translated \
-emit-structured-metadata=true \
-extra-label-fields='host.id,k8s.cluster.name,custom.pipeline.processing'
# Equivalent via env
export EXTRA_LABEL_FIELDS='host.id,k8s.cluster.name,custom.pipeline.processing'
./loki-vl-proxy -label-style=underscores -metadata-field-mode=translated

When to also use -field-mapping:

  • Use -extra-label-fields to extend discovery and alias resolution.
  • Use -field-mapping when you need a non-default alias name (for example custom.pipeline.processing <-> pipeline_proc).

Indexed Label Values Browse Cache (Optional)​

This mode is designed for very high-cardinality labels (for example k8s_pod_name) where returning every value on first browse is expensive.

FlagEnvDefaultDescription
-label-values-indexed-cacheβ€”falseEnable indexed hotset browsing for GET /loki/api/v1/label/{name}/values
-label-values-hot-limitβ€”200Default values returned for empty-query browse when limit is not specified
-label-values-index-max-entriesβ€”200000Maximum indexed values retained per tenant+label in memory
-label-values-index-persist-pathβ€”β€”Disk path for persisted label-values index snapshot (JSON)
-label-values-index-persist-intervalβ€”30sPeriodic snapshot flush interval
-label-values-index-startup-stale-thresholdβ€”60sDisk snapshot freshness threshold before peer warm fallback
-label-values-index-startup-peer-warm-timeoutβ€”5sStartup timeout for peer warm fallback

Behavior when enabled:

  • Empty-query browse (query omitted or query=*) serves a hot subset first.
  • Supports optional pagination-style parameters on the same endpoint: limit and offset.
  • Supports optional in-proxy value filtering with search (alias q), without forcing a full backend refetch when index is warm.
  • Query-scoped requests (query={...}) keep standard behavior and still update index state.
  • Startup warm order: restore disk snapshot first, then warm from peer cache when disk snapshot is stale/missing.
  • Rolling update safety: graceful shutdown writes a final snapshot before exit.
  • Readiness behavior: /ready stays 503 until label-values startup warm is finished.

Example:

./loki-vl-proxy \
-label-values-indexed-cache=true \
-label-values-hot-limit=200 \
-label-values-index-max-entries=200000 \
-label-values-index-persist-path=/cache/label-values-index.json \
-label-values-index-persist-interval=30s \
-label-values-index-startup-stale-threshold=60s \
-label-values-index-startup-peer-warm-timeout=5s

Sizing guidance:

- RAM estimate per label key: `label-values-index-max-entries * ~96 bytes`.
- Disk snapshot estimate per label key: roughly `~60%` of RAM estimate.
- Total footprint scales with `(tenant_count * indexed_label_count)`.

Enable this to keep detected /loki/api/v1/patterns results warm across rolling restarts and fleet members.

Behavior:

  • Pattern cache entries are retained long-term and updated in-place by cache key.
  • Every detected pattern response is appended to the in-memory snapshot map (no periodic TTL-based pruning in snapshot state).
  • Optional global autodetect (-patterns-autodetect-from-queries=true) passively mines successful query and query_range responses and pre-warms matching /patterns cache keys.
  • Snapshot is persisted to disk periodically and on graceful shutdown.
  • Startup restore order: disk snapshot first, then peer merge (newest entry wins per cache key).
  • Readiness stays 503 during startup warm when patterns persistence is configured.

Operational guidance:

  • For restart-safe persistence, run StatefulSet + PVC and set -patterns-persist-path to a writable mounted path.
  • If -patterns-persist-path is set but not writable, proxy startup fails fast with a clear error.
  • If -patterns-persist-path is not set, patterns still work, but persistence is disabled (cold start after restart).

Sizing guidance:

  • Endpoint clamp: max returned patterns per request is 1000.
  • Approximate persisted bytes:
    • snapshot_size ~= sum(pattern_response_payload_bytes_per_cached_query_key)
    • Rule of thumb per pattern entry: ~(120 bytes base + pattern length + ~24 bytes per sample bucket)
  • Real footprint depends on:
    • number of unique (tenant, rawQuery) keys,
    • sample bucket count per pattern (step, query range),
    • pattern text length distribution.

References:

Metadata Field Modes​

ModeWhen to UseField APIs
nativeYou want only raw VictoriaLogs field namesservice.name, k8s.pod.name
translatedYou want strict Loki-style field names onlyservice_name, k8s_pod_name
hybridDefault. You need Loki compatibility plus OTel-native correlationBoth native dotted names and translated aliases

-metadata-field-mode=hybrid keeps the label surface Loki-compatible while making field-oriented APIs like detected_fields and detected_field/{name}/values expose both service.name and service_name when they differ.

Compatibility Profiles​

The three flags below define the compatibility profile:

  • -label-style
  • -metadata-field-mode
  • -emit-structured-metadata
ProfileSettingsBest For
Loki/Grafana conservativelabel-style=underscores, metadata-field-mode=translated, emit-structured-metadata=trueStrict Loki-style field naming plus Explore/Drilldown event metadata
Drilldown/OTel mixed modelabel-style=underscores, metadata-field-mode=hybrid, emit-structured-metadata=trueGrafana + OTel correlation where both dotted and translated field names are useful
Native VL field surfacelabel-style=passthrough, metadata-field-mode=native, emit-structured-metadata=trueConsumers that prefer raw VictoriaLogs field names and structured metadata

For tuple behavior and endpoint-level details, see API Reference. For support scope by product/version track, see Compatibility Matrix.

If you must interoperate with legacy clients that reject metadata objects in categorize-labels mode, explicitly set -emit-structured-metadata=false.

Maximum Loki Compatibility β€” Annotated Configuration​

This is the recommended starting point when your priority is full compatibility with Grafana's Loki datasource and Logs Drilldown. Copy, adjust the backend URL, and run.

./loki-vl-proxy \
# ── Backend ──────────────────────────────────────────────────────────────
-backend=http://victorialogs:9428 \ # VictoriaLogs HTTP API address
\
# ── Label translation: maximum Loki compatibility ─────────────────────────
# OTel dotted labels (service.name, k8s.pod.name) are translated to Loki-style
# underscores (service_name, k8s_pod_name) in every response.
# Queries written with underscores are rewritten to dotted before hitting VL.
-label-style=underscores \
\
# ── Metadata field exposure ────────────────────────────────────────────────
# "translated" exposes only underscore-style labels in detected_fields and
# detected_labels. This gives Grafana a clean, Loki-identical field list
# with no dotted duplicates in the fields panel.
-metadata-field-mode=translated \
\
# ── Structured metadata (3-tuple responses) ────────────────────────────────
# Required for Grafana 10+ Loki datasource "categorize-labels" mode.
# Enables per-line label context in log details panel without extra queries.
# Default is true; listed here explicitly so the intent is visible.
-emit-structured-metadata=true \
\
# ── Patterns (Logs Drilldown) ──────────────────────────────────────────────
# Enables GET /loki/api/v1/patterns used by the Patterns tab in Logs Drilldown.
# Default is true. Set false only if you do not use the Drilldown plugin.
-patterns-enabled=true

Equivalent as environment variables (for Docker Compose or Kubernetes):

VL_BACKEND_URL=http://victorialogs:9428
LABEL_STYLE=underscores
METADATA_FIELD_MODE=translated

-emit-structured-metadata and -patterns-enabled have no env-variable override; pass them as flags or Helm extraArgs.

Grafana datasource β€” global single-tenant (no tenant mapping needed):

datasources:
- name: Logs
type: loki
url: http://loki-vl-proxy:3100
jsonData:
httpHeaderName1: X-Scope-OrgID
secureJsonData:
# "0", "fake", and "default" all resolve to VL's built-in default tenant (0:0).
# Pick whichever your Loki configuration already uses; the proxy treats them identically.
httpHeaderValue1: "0"

What each flag does and why it matters for Loki compatibility:

FlagValueEffect
-label-styleunderscoresTranslates service.name β†’ service_name so Grafana label filters work
-metadata-field-modetranslatedNo dotted duplicates in the fields panel; matches real Loki output
-emit-structured-metadatatrueEnables 3-tuple [ts, line, metadata] required by Grafana 10+ log details
-patterns-enabledtrueUnlocks the Patterns tab in Logs Drilldown

Switch -metadata-field-mode to hybrid if you also need OTel correlation (traces/metrics linked by service.name); the proxy then exposes both service.name and service_name in field APIs.

Cache (L1 In-Memory)​

FlagEnvDefaultDescription
-cache-ttlβ€”60sDefault cache TTL
-cache-maxβ€”10000Maximum cache entries
-cache-max-bytesβ€”268435456Maximum in-memory L1 cache size in bytes (256 MiB by default)
-compat-cache-enabledβ€”trueEnable the Tier0 compatibility-edge response cache for safe GET read endpoints
-compat-cache-max-percentβ€”10Percent of -cache-max-bytes reserved for Tier0 (0 disables, max 50)

Tier0 Compatibility-Edge Cache​

Tier0 is a separate in-memory cache instance that reuses the same cache implementation as the deeper L1/L2/L3 stack, but stores only final Loki-shaped response bodies.

  • It runs only after tenant validation and route classification.
  • It only applies to safe GET read endpoints such as query, query_range, series, labels, volume, patterns, and Drilldown metadata endpoints.
  • It does not apply to /tail, websocket upgrades, writes, deletes, admin/debug paths, or non-JSON responses.
  • Its budget is derived from -cache-max-bytes, so -compat-cache-max-percent=10 means Tier0 gets 10% of the primary L1 memory budget.
  • Tenant-map and field-mapping reloads invalidate Tier0 immediately.

Per-Endpoint TTLs​

EndpointTTL
labels, label_values60s
series, detected_fields, detected_field_values, detected_labels30s
patterns100y (effectively persistent; update-on-write)
query_range, query10s
index_stats, volume, volume_range10s

Cache (L2 On-Disk)​

FlagEnvDefaultDescription
-disk-cache-pathβ€”β€”Path to bbolt DB file (empty = disabled)
-disk-cache-compressβ€”trueGzip compression for disk cache
-disk-cache-flush-sizeβ€”100Flush write buffer after N entries
-disk-cache-flush-intervalβ€”5sWrite buffer flush interval
-disk-cache-min-ttlβ€”30sMinimum TTL required before an entry is eligible for L2 disk-cache writes
-disk-cache-max-bytesβ€”0Maximum on-disk L2 cache size in bytes (0 = unlimited)

Cold Storage Backend​

Route queries for old data to a separate cold backend (e.g. Victoria Lakehouse or a second VictoriaLogs instance). Queries spanning both hot and cold data are split and merged transparently.

FlagEnvDefaultDescription
-cold-enabledβ€”falseEnable cold storage backend routing
-cold-backendβ€”β€”Cold storage backend URL (e.g. http://lakehouse:9428)
-cold-boundaryβ€”168hData older than this age is routed to the cold backend (default 7 days)
-cold-overlapβ€”1hOverlap window around the cold boundary β€” queries spanning the boundary include this overlap on both sides to avoid gaps
-cold-manifest-refreshβ€”5mHow often to refresh the cold backend capability manifest
-cold-timeoutβ€”30sTimeout for cold backend requests
./loki-vl-proxy \
-backend=http://victorialogs:9428 \
-cold-enabled \
-cold-backend=http://lakehouse:9428 \
-cold-boundary=336h # 14 days

Query Length Limits​

The proxy can enforce a maximum query time range, matching Loki's max_query_length behavior.

FlagEnvDefaultDescription
-default-max-query-lengthβ€”0Default maximum query time range enforced for all tenants. 0 disables the limit (unlimited, matching Loki's default). Accepts Go duration strings: 30d, 7d, 24h.

Precedence​

When multiple limits are configured, the most specific takes effect:

  1. Per-tenant β€” max_query_length in the tenant limits config (highest priority)
  2. Tenant defaults β€” max_query_length in tenantDefaultLimits
  3. Global flag β€” -default-max-query-length
  4. No limit β€” when all of the above are 0 or unset

Behavior​

  • Applied after LogQL offset extraction (the enforced range reflects any offset modifier in the query)
  • Rejected queries return HTTP 400 with {"status":"error","errorType":"bad_data","error":"query length ... exceeds limit ..."}
  • Matches Loki's max_query_length semantics for client compatibility

Examples​

# Limit all queries to 30 days by default
loki-vl-proxy -default-max-query-length=720h

# Allow a specific tenant to query further back (in tenant limits config)
# max_query_length: "90d"

# No global limit (default β€” operator manages limits via per-tenant config)
loki-vl-proxy -default-max-query-length=0

Query Range Window Cache​

These flags control Loki-compatible query_range split/merge execution with per-window cache reuse.

FlagEnvDefaultDescription
-query-range-windowingβ€”trueEnable window split/merge path for log query_range
-query-range-split-intervalβ€”1hPer-window time span used for split/merge
-query-range-max-parallelβ€”2Static maximum window fetch parallelism (used when adaptive mode is disabled)
-query-range-adaptive-parallelβ€”trueEnable adaptive window fetch parallelism
-query-range-adaptive-min-parallelβ€”2Lower bound for adaptive parallelism
-query-range-adaptive-max-parallelβ€”8Upper bound for adaptive parallelism
-query-range-latency-targetβ€”1.5sTarget backend window fetch latency for safe adaptive increase
-query-range-latency-backoffβ€”3sBackoff threshold; adaptive mode reduces parallelism when EWMA latency exceeds this
-query-range-adaptive-cooldownβ€”30sMinimum interval between adaptive parallelism changes
-query-range-error-backoff-thresholdβ€”0.02Backoff threshold for EWMA backend error ratio (0-1)
-query-range-freshnessβ€”10mNear-now freshness boundary
-query-range-recent-cache-ttlβ€”0sCache TTL for windows newer than now-freshness (0s disables near-now cache)
-query-range-history-cache-ttlβ€”24hCache TTL for historical windows older than now-freshness
-query-range-prefilter-index-statsβ€”trueUse /select/logsql/hits preflight to skip empty split windows before expensive log fanout
-query-range-prefilter-min-windowsβ€”8Minimum split-window count required before prefilter is enabled
-query-range-stream-aware-batchingβ€”trueReduce parallelism for windows estimated as expensive from prefilter hits
-query-range-expensive-hit-thresholdβ€”2000Prefilter hit threshold above which a window is treated as expensive
-query-range-expensive-max-parallelβ€”1Maximum parallel fetches for expensive windows
-query-range-align-windowsβ€”trueAlign split windows to fixed interval boundaries for overlap-cache reuse
-query-range-window-timeoutβ€”20sPer-window backend timeout budget (0 disables)
-query-range-partial-responsesβ€”falseAllow partial query_range responses on retryable backend failures
-query-range-background-warmβ€”trueContinue warming failed windows in background after a partial response
-query-range-background-warm-max-windowsβ€”24Cap background warm fanout after a partial response
-recent-tail-refresh-enabledβ€”trueEnable near-now stale-cache bypass for query_range, index/volume, and index/volume_range
-recent-tail-refresh-windowβ€”2mTreat requests ending within this window from now as near-now
-recent-tail-refresh-max-stalenessβ€”2sMaximum acceptable cache age for near-now requests before forcing a fresh backend fetch

Loki-Aligned Defaults​

  • split-interval=1h mirrors the common Loki split-by-interval setup.
  • freshness=10m keeps the latest time range uncached by default, matching typical Loki freshness posture.
  • bounded max-parallel=2 protects VictoriaLogs from fanout bursts while still reducing latency for long ranges.
  • history-cache-ttl=24h keeps older windows warm for repeated drilldowns and expanded-range queries.
  • adaptive mode (min=2, max=8) raises parallelism only when EWMA latency/error remain healthy and backs off quickly when backend pressure rises.

Cache Sizing Guidance​

Use this approximation for disk planning:

required_bytes ~= unique_window_keys_per_day * avg_compressed_window_bytes * ttl_days

From in-repo sizing tests (internal/cache/sizing_test.go), a practical compressed average is around 56 KiB per entry, which yields:

  • 1 GiB -> about 18k cached windows
  • 10 GiB -> about 189k cached windows
  • 50 GiB -> about 948k cached windows

Set -disk-cache-max-bytes when you want deterministic upper bounds instead of relying only on PVC limits.

Practical Tuning Profiles​

ProfileSuggested SettingsWhen to Use
Conservativeadaptive-max=4, latency-target=2s, latency-backoff=4s, history-ttl=24hShared VictoriaLogs clusters with strict backend protection goals
Balanced (default)adaptive-max=8, latency-target=1.5s, latency-backoff=3s, history-ttl=24hMost Grafana + Drilldown workloads
Aggressiveadaptive-max=12, latency-target=1s, latency-backoff=2s, history-ttl=72h + larger disk cacheLarge repeated long-range analytics where backend headroom is proven

Tuning Visibility Metrics​

Use these metrics to tune safely:

  • loki_vl_proxy_window_adaptive_parallel_current (gauge)
  • loki_vl_proxy_window_adaptive_latency_ewma_seconds (gauge)
  • loki_vl_proxy_window_adaptive_error_ewma (gauge)
  • loki_vl_proxy_window_cache_hit_total, loki_vl_proxy_window_cache_miss_total (counters)
  • loki_vl_proxy_window_fetch_seconds and loki_vl_proxy_window_merge_seconds (histograms)
  • loki_vl_proxy_window_prefilter_attempt_total, loki_vl_proxy_window_prefilter_error_total (counters)
  • loki_vl_proxy_window_prefilter_kept_total, loki_vl_proxy_window_prefilter_skipped_total (counters)
  • loki_vl_proxy_window_prefilter_duration_seconds (histogram)

Recommended loop:

  1. Increase -query-range-adaptive-max-parallel only if latency EWMA stays below target and error EWMA stays near zero during peak load.
  2. Increase -query-range-history-cache-ttl only if disk utilization and cache hit ratio justify it.
  3. Set -disk-cache-max-bytes before increasing TTL windows beyond 24h.

Metadata vs Live Query Freshness​

The proxy keeps faster-changing paths conservative and slower-changing metadata slightly warmer:

  • query and query_range stay on short TTLs so new log lines remain visible quickly
  • labels, label_values, series, patterns, detected_fields, and detected_labels can cache longer because they change more slowly and are more expensive to rebuild
  • Drilldown now prefers native VictoriaLogs metadata (field_names, field_values, streams) where possible, which reduces the amount of raw log rescanning needed on cache misses

Multitenancy​

FlagEnvDefaultDescription
-tenant-labelTENANT_LABEL""VL field name for label-based tenant routing. When set, X-Scope-OrgID values are injected as {<tenant-label>="<orgID>"} into VL queries instead of AccountID/ProjectID headers. Use when all data is under VL default tenant (0:0). Explicit tenant-map entries take priority.
-tenant-mapTENANT_MAP—JSON string→int tenant mapping
-tenant-map-fileTENANT_MAP_FILE""Path to a YAML or JSON file mapping Loki X-Scope-OrgID strings to VictoriaLogs AccountID/ProjectID. Reloaded on SIGHUP and automatically when the file changes (see -tenant-map-reload-interval). Suitable for Kubernetes ConfigMap volumes. File entries override -tenant-map inline entries for the same key.
-tenant-map-reload-interval(flag only)30sHow often to poll -tenant-map-file for mtime changes. Set to 0 to disable polling (SIGHUP-only reload).
-tenant-limits-allow-publishTENANT_LIMITS_ALLOW_PUBLISHbuilt-in allowlistComma-separated limit fields exposed on /config/tenant/v1/limits and /loki/api/v1/drilldown-limits
-tenant-default-limitsTENANT_DEFAULT_LIMITSβ€”JSON map of default published-limit overrides
-tenant-limitsTENANT_LIMITSβ€”JSON map of per-tenant published-limit overrides keyed by X-Scope-OrgID
-auth.enabledβ€”falseRequire X-Scope-OrgID on query requests
-tenant.allow-globalβ€”falseAllow X-Scope-OrgID: * to bypass tenant scoping and use the backend default tenant
-forward-tenant-headerFORWARD_TENANT_HEADERtrueWhen false, suppresses X-Scope-OrgID header forwarding to the backend

Tenant Resolution Order​

  1. No header β€” when -auth.enabled=false, requests without X-Scope-OrgID use VictoriaLogs' backend default tenant, which is AccountID=0 and ProjectID=0
  2. Tenant map lookup β€” if -tenant-map is configured and the org ID matches a key, use the mapped AccountID/ProjectID
  3. Explicit tenant map override β€” if a tenant map contains an exact key such as "0" or "fake", that explicit mapping wins
  4. Default-tenant aliases β€” X-Scope-OrgID values 0, fake, and default map to VictoriaLogs' built-in 0:0 tenant without rewriting headers
  5. Wildcard global bypass β€” X-Scope-OrgID: * is a proxy-specific convenience. It uses the backend default tenant only when -tenant.allow-global=true
  6. Numeric passthrough β€” if the org ID is a number other than the default-tenant alias case (for example "42"), pass it directly as AccountID with ProjectID: 0
  7. Fail closed β€” unmapped non-numeric org IDs are rejected with 403 Forbidden

Multi-Tenant Query Headers​

The proxy accepts Loki-style multi-tenant query headers on read/query endpoints by separating tenant IDs with |, for example X-Scope-OrgID: team-a|team-b.

  • Supported on query-style endpoints such as /query, /query_range, /labels, /label/.../values, /series, /index/*, and Drilldown metadata endpoints
  • Rejected on /tail, delete, and write paths
  • Query results inject a synthetic __tenant_id__ label per tenant, matching Loki's documented query behavior
  • __tenant_id__ matchers in the leading selector narrow the tenant fanout set before backend requests are sent
  • multi-tenant detected_fields and detected_labels use exact merged value unions, so cardinality does not double-count identical values across tenants
  • Wildcard * is not allowed inside a multi-tenant header; use explicit tenant IDs
  • fanout is safety-capped to prevent one request from exploding into an unbounded number of backend queries
  • merged multi-tenant response bodies are also size-capped before they are returned to the client

Tenant Map File Format​

The tenant map file (YAML or JSON) maps each Loki X-Scope-OrgID string to a VictoriaLogs AccountID/ProjectID pair. Both fields are required and must be non-negative integers represented as strings.

YAML (recommended β€” easier to comment and diff):

# /etc/loki-vl-proxy/tenant-map.yaml
#
# Each key is the exact string sent in X-Scope-OrgID by Grafana (or Loki clients).
# account_id maps to VictoriaLogs AccountID header (tenant namespace).
# project_id maps to VictoriaLogs ProjectID header (sub-account partition).
# Both fields must be non-negative integers. Use "0" for the VL default.

# Single VictoriaLogs account, separate projects per team:
team-alpha:
account_id: "1"
project_id: "1"

team-beta:
account_id: "1"
project_id: "2"

# Different VL accounts (e.g. separate VL clusters behind a load balancer):
ops-prod:
account_id: "42"
project_id: "0"

ops-staging:
account_id: "43"
project_id: "0"

# Explicit override for the default-tenant alias "0".
# Without this, X-Scope-OrgID: "0" resolves to VL 0:0 automatically.
# Add it only when you need to redirect "0" to a different VL project:
# "0":
# account_id: "0"
# project_id: "99"

JSON (use when injecting from CI/CD tooling):

{
"team-alpha": { "account_id": "1", "project_id": "1" },
"team-beta": { "account_id": "1", "project_id": "2" },
"ops-prod": { "account_id": "42", "project_id": "0" }
}

Validation rules enforced at load time:

  • account_id and project_id must both be present and non-empty.
  • Both must parse as non-negative integers that fit in a uint32 (0–4294967295).
  • Strings that are not valid integers (for example "prod") are rejected immediately β€” the proxy does not start if the tenant map is invalid.
  • The file extension determines the parser: .json β†’ JSON, anything else β†’ YAML.

Configuration Examples​

# Via flag (inline JSON β€” convenient for single-container deployments)
./loki-vl-proxy \
-tenant-map='{"team-alpha":{"account_id":"1","project_id":"1"},"team-beta":{"account_id":"1","project_id":"2"}}'

# Via environment variable (same JSON format)
export TENANT_MAP='{"ops-prod":{"account_id":"42","project_id":"0"}}'
./loki-vl-proxy -backend=http://victorialogs:9428

# Via file with hot-reload β€” ideal for Kubernetes ConfigMap volumes
./loki-vl-proxy \
-backend=http://victorialogs:9428 \
-tenant-map-file=/etc/loki-vl-proxy/tenant-map.yaml \
-tenant-map-reload-interval=30s

Kubernetes ConfigMap: use -tenant-map-file so the proxy can pick up tenant changes without a restart. The proxy polls the file's modification time every -tenant-map-reload-interval (default 30 s); Kubernetes atomically replaces the ConfigMap symlink, so the new mtime is visible immediately. See k8s-tenant-map-configmap.yaml for a complete Deployment snippet.

Global single-tenant (no map needed): if every Grafana datasource sends the same X-Scope-OrgID value ("0", "fake", or "default") and all data lives in VictoriaLogs' default tenant, you do not need -tenant-map at all. The proxy resolves those three aliases to VL's built-in AccountID=0, ProjectID=0 automatically.

Grafana Datasource per Tenant​

datasources:
- name: Logs (team-alpha)
type: loki
url: http://loki-vl-proxy:3100
jsonData:
httpHeaderName1: X-Scope-OrgID
secureJsonData:
httpHeaderValue1: team-alpha

Grafana Datasource for Explicit Multi-Tenant Reads​

datasources:
- name: Logs (team-a + team-b)
type: loki
url: http://loki-vl-proxy:3100
jsonData:
httpHeaderName1: X-Scope-OrgID
secureJsonData:
httpHeaderValue1: team-a|team-b

Use __tenant_id__ in LogQL when the datasource fans out to more than one tenant:

{app="api-gateway", __tenant_id__="team-b"}
{service_name="checkout", __tenant_id__=~"team-(a|b)"}

Grafana Datasource for Single-Tenant VictoriaLogs​

If the backend only uses VictoriaLogs' default tenant, you can keep Grafana simple:

datasources:
- name: Logs (global tenant)
type: loki
url: http://loki-vl-proxy:3100
jsonData:
httpHeaderName1: X-Scope-OrgID
secureJsonData:
httpHeaderValue1: "0"

X-Scope-OrgID: "0", X-Scope-OrgID: "fake", and X-Scope-OrgID: "default" resolve to VL's default 0:0 tenant in Loki-compatible single-tenant mode. X-Scope-OrgID: "*" remains a proxy-specific wildcard convenience and always requires explicit -tenant.allow-global=true.

Hot Reload​

Tenant mappings and field mappings can be reloaded without restart via SIGHUP:

kill -HUP $(pidof loki-vl-proxy)

Published Tenant Limits Compatibility Surface​

The proxy also exposes tenant-limit compatibility endpoints for Grafana and Logs Drilldown bootstrap:

  • GET /config/tenant/v1/limits returns YAML
  • GET /loki/api/v1/drilldown-limits returns JSON

The base payload is generated in-proxy and then filtered/overridden by:

  • -tenant-limits-allow-publish
  • -tenant-default-limits
  • -tenant-limits

Operational notes:

  • multi-tenant X-Scope-OrgID: a|b is rejected on /config/tenant/v1/limits
  • X-Scope-OrgID omitted on these endpoints uses the default published limits surface
  • these flags only shape the published compatibility payload; they do not enforce backend quotas

OTLP Telemetry​

FlagEnvDefaultDescription
-otlp-endpointOTLP_ENDPOINTβ€”OTLP HTTP endpoint for proxy metrics
-otlp-intervalβ€”30sPush interval
-otlp-compressionOTLP_COMPRESSIONnonenone, gzip, zstd
-otlp-headersOTLP_HEADERSβ€”Comma-separated OTLP HTTP headers in key=value form
-otlp-timeoutβ€”10sHTTP request timeout
-otlp-tls-skip-verifyβ€”falseSkip TLS verification
-otel-service-nameOTEL_SERVICE_NAMEloki-vl-proxyservice.name resource metadata for OTLP metrics/logs (not duplicated per JSON log line)
-otel-service-namespaceOTEL_SERVICE_NAMESPACEβ€”service.namespace resource metadata for OTLP metrics/logs (not duplicated per JSON log line)
-otel-service-instance-idOTEL_SERVICE_INSTANCE_IDβ€”service.instance.id resource metadata for OTLP metrics/logs (not duplicated per JSON log line)
-deployment-environmentDEPLOYMENT_ENVIRONMENTβ€”deployment.environment.name resource metadata for OTLP metrics/logs (not duplicated per JSON log line)

Go Runtime Tuning​

FlagEnvDefaultDescription
-go-mem-limitβ€”0Explicit GOMEMLIMIT in bytes. Overrides -go-mem-limit-percent when set (0 = disabled)
-go-mem-limit-percentβ€”85GOMEMLIMIT as a percentage of the detected container memory limit (from /proc cgroup). Ignored when -go-mem-limit is set
-go-gc-percentβ€”200GOGC target percentage. The proxy default (200) halves GC frequency versus Go's built-in default of 100, trading higher peak RSS for lower GC CPU. Set -1 to disable GC target entirely

See Performance β€” Go Runtime Tuning for guidance on sizing these for your deployment.

HTTP Hardening​

FlagEnvDefaultDescription
-http-read-timeoutβ€”30sServer read timeout
-http-read-header-timeoutβ€”10sServer read header timeout
-http-write-timeoutβ€”120sServer write timeout
-http-idle-timeoutβ€”120sServer idle timeout
-http-max-header-bytesβ€”1MBMaximum header size
-http-max-body-bytesβ€”10MBMaximum request body size
-http-conn-max-ageβ€”10mMaximum lifetime for downstream HTTP/1.x keepalive connections
-http-conn-max-age-jitterβ€”2mJitter applied to downstream connection age rotation
-http-conn-max-requestsβ€”256Maximum requests per keepalive connection
-http-conn-overload-max-ageβ€”90sShorter connection lifetime during backpressure

Grafana Compatibility​

FlagEnvDefaultDescription
-max-linesβ€”1000Default max lines per query
-manual-range-metric-row-limitβ€”1000000Maximum raw log rows fetched per proxy-side range-metric evaluation (rate, count_over_time, etc.); cap prevents memory spikes on high-cardinality queries
-backend-timeoutβ€”120sTimeout for non-streaming VL backend requests
-backend-min-versionβ€”v1.30.0Minimum VictoriaLogs version considered fully supported at startup compatibility gate
-backend-allow-unsupported-versionβ€”falseAllow startup when detected backend version is lower than -backend-min-version (unsafe override)
-backend-version-check-timeoutβ€”5sTimeout for startup backend version compatibility check (/health)
-stream-responseβ€”falseStream via chunked transfer
-response-compressionβ€”autoResponse compression codec: auto, gzip, none
-response-compression-min-bytesβ€”1024Minimum response size before frontend compression starts; smaller responses stay identity
-response-gzipβ€”trueDeprecated compatibility switch; false disables response compression when -response-compression is unset
-derived-fieldsβ€”β€”JSON derived fields for trace linking
-forward-headersβ€”β€”HTTP headers to forward to VL
-forward-authorizationβ€”falseForward client Authorization header to VL backend (adds Authorization to forwarded headers list)
-forward-cookiesβ€”β€”Cookie names to forward to VL
-backend-basic-authβ€”β€”user:password for VL basic auth
-backend-compressionβ€”autoUpstream compression preference: auto, gzip, zstd, none. auto detects loopback backends (localhost/127.x/::1) and disables compression for co-located VL, otherwise advertises zstd, gzip.
-backend-tls-skip-verifyβ€”falseSkip TLS on VL connection
-backend-read-buffer-sizeβ€”65536Per-connection read buffer size (bytes) for VL HTTP responses. Default 64 KB β€” reduces syscall count 7Γ— for typical responses vs Go default 4 KB. Decrease (e.g. 8192) to save memory in high-pod-count environments; increase (e.g. 131072) when responses routinely exceed 64 KB.
-backend-write-buffer-sizeβ€”65536Per-connection write buffer size (bytes) for VL HTTP requests. Default 64 KB. Tune alongside -backend-read-buffer-size for memory-constrained pods.
-tail.allowed-originsβ€”β€”Comma-separated WebSocket Origin allowlist for /loki/api/v1/tail
-tail.modeβ€”autoauto, native, or synthetic for /tail streaming mode

-tail.mode=auto prefers native backend tailing and falls back to synthetic polling when native streaming is unavailable. native disables the fallback, and synthetic forces the polling bridge even when the backend can stream natively.

Compression notes:

  • -response-compression=auto keeps the optimized gzip path enabled for clients that advertise gzip.
  • -response-compression-min-bytes=1024 keeps small control-plane responses uncompressed and the proxy applies higher effective thresholds on metadata-heavy routes.
  • peer-cache /_cache/get fetches follow the same preference order: zstd, then gzip, then identity.
  • -backend-compression=auto auto-detects the backend address at startup: when the host is a loopback address (localhost, 127.x.x.x, ::1) it sends Accept-Encoding: identity, eliminating 25–35% decompression CPU with no bandwidth cost (loopback has no network overhead); when the host is remote it advertises zstd, gzip and the proxy safely decodes either on the way back. Use none to force identity for non-loopback local deployments (e.g. same host via a non-loopback IP).
  • current VictoriaLogs docs describe HTTP response compression in general terms, not a guaranteed zstd select-query path, so in practice many deployments will still observe gzip or identity from stock VictoriaLogs today.
  • Grafana 12.4.2 datasource proxy requests advertised Accept-Encoding: deflate, gzip, not zstd, in local verification, so the proxy keeps the frontend surface on gzip/identity only.
  • The Helm chart now pins -response-compression=gzip and -response-compression-min-bytes=1024 by default.

Backend version gate notes:

  • On startup, proxy probes backend /health and inspects response headers for a VictoriaLogs semver.
  • If detected version is below -backend-min-version, startup is blocked by default.
  • Set -backend-allow-unsupported-version=true to bypass the gate at your own risk.
  • If version cannot be detected from headers, proxy logs a warning and continues startup.

Built-In Protection Defaults​

All protection controls are tunable via CLI flags:

FlagEnvDefaultDescription
-max-concurrentβ€”100Maximum concurrent backend queries allowed globally
-rate-limit-per-secondβ€”50Per-client request rate limit (requests/second)
-rate-limit-burstβ€”100Per-client burst allowance above the rate limit
-cb-fail-thresholdβ€”5Number of backend failures within the sliding window required to open the circuit breaker
-cb-open-durationβ€”10sHow long the circuit breaker stays open before entering half-open state
-cb-window-durationβ€”30sSliding window duration for failure counting; sporadic failures outside the window do not accumulate
-coalescer-disabledβ€”falseDisable request coalescing (singleflight); every concurrent request makes its own backend call β€” useful with -cache-disabled to measure raw translation overhead

Shape per-client and global traffic at Grafana, ingress, or an outer proxy layer for additional control beyond these flags.

Observability and Admin Surfaces​

BREAKING in v1.56.0 β€” admin/debug endpoints and /metrics no longer share the main proxy listener by default.

  • -server.register-instrumentation now defaults to false (was true). The main proxy listener stops serving /metrics unless instrumentation is explicitly re-enabled.
  • Admin and debug endpoints (/admin/*, /debug/pprof/*, /debug/queries) move to a dedicated loopback listener on 127.0.0.1:3101 (configurable via --admin-listen) unless -server.admin-auth-token is set, in which case they stay on the main listener for back-compat.
  • /metrics can be hosted on a dedicated port via --metrics-listen=:9091; the Helm chart sets this by default and points the ServiceMonitor at it.

Binary users who scrape :3100/metrics today must, after upgrade, either:

  • set -server.register-instrumentation=true to keep /metrics on the main listener (legacy behavior), OR
  • set -server.register-instrumentation=true -metrics-listen=:9091 for a dedicated metrics port (recommended).
FlagEnvDefaultDescription
-server.register-instrumentationβ€”falseRegister /metrics and related instrumentation handlers. BREAKING in v1.56.0 β€” default changed from true to false so the main listener no longer exposes /metrics unless opted in. Pair with --metrics-listen to host /metrics on a dedicated port.
--admin-listenβ€”127.0.0.1:3101Address of the dedicated admin/debug listener used when -server.admin-auth-token is empty. Bound to loopback by default so admin surfaces (/admin/*, /debug/pprof/*, /debug/queries) are unreachable from off-host. Set -server.admin-auth-token to keep these surfaces on the main listener instead.
--metrics-listenβ€”β€”Address of the dedicated /metrics listener (e.g. :9091). When set together with -server.register-instrumentation=true, /metrics moves off the main proxy listener so scrape traffic never competes with queries. Empty disables the dedicated listener.
-server.enable-pprofβ€”falseExpose /debug/pprof/*
-server.enable-query-analyticsβ€”falseExpose /debug/queries
-server.admin-auth-tokenβ€”β€”Bearer token accepted on admin/debug endpoints
-server.metrics-max-concurrencyβ€”1Maximum concurrent /metrics scrapes served at once (0 disables the cap)
-metrics.max-tenantsβ€”256Max unique tenant labels retained in /metrics before using __overflow__
-metrics.max-clientsβ€”256Max unique client labels retained in /metrics before using __overflow__
-metrics.export-sensitive-labelsβ€”falseExport per-tenant and per-client identity metrics on /metrics and OTLP
-metrics.trust-proxy-headersβ€”falseTrust user/proxy headers (X-Grafana-User, X-Forwarded-User, X-Webauth-User, X-Auth-Request-User, X-Forwarded-*) for client metrics/log attribution and backend context forwarding
-debug-log-raw-queriesβ€”falseInclude raw LogQL/LogsQL strings and backend query params in debug-level logs. By default each query is logged as sha256:<8hex>+len=<n> to keep PII out of operator log pipelines. Set to true only for ad-hoc investigation; INFO-level access logs are unaffected.

Peer Cache​

FlagEnvDefaultDescription
-peer-selfβ€”β€”This instance address used for peer-cache ownership and fetches
-peer-self-azβ€”β€”This instance's availability zone (e.g. us-east-1a). When set, same-AZ peers are preferred when fetching keys at startup and warmup to reduce cross-AZ traffic. Falls back to any peer with fresh data when no same-AZ peer has the key. Helm chart can inject this automatically from the pod's topology label via the Kubernetes Downward API (see peerCache.topologyLabel).
-peer-discoveryβ€”β€”Peer discovery mode: dns, srv, http, or static
-peer-dnsβ€”β€”Headless service DNS name used when -peer-discovery=dns (e.g., proxy-headless.ns.svc.cluster.local)
-peer-srvβ€”β€”Full SRV record name used when -peer-discovery=srv (e.g., _loki-vl-proxy._tcp.proxy-headless.ns.svc.cluster.local). Port is read from the SRV record β€” no separate port flag needed.
-peer-http-urlβ€”β€”URL returning a JSON peer list when -peer-discovery=http. Supported formats: simple array, {"peers":[...]}, Prometheus HTTP SD, Consul catalog. The URL is polled every DiscoveryInterval.
-peer-staticβ€”β€”Comma-separated peer list used when -peer-discovery=static (e.g., 10.0.0.1:3100,10.0.0.2:3100)
-peer-timeoutβ€”2sTimeout applied to peer-cache owner fetches (/_cache/get) before falling back locally
-peer-auth-tokenβ€”β€”Shared token used on /_cache/get and /_cache/set peer-cache requests. Strongly recommended for fleets so peer auth does not depend only on transient discovery/IP membership during startup. Required by default in v1.56.0 β€” the proxy refuses to start when peer cache is configured (via -peer-discovery or -peer-static) and this token is empty, unless -peer-insecure-ip-allowlist=true is set.
-peer-insecure-ip-allowlistβ€”falseExplicit opt-in for the legacy IP-allowlist-only peer auth (membership in the discovered peer set is the only check). When true, the proxy boots with -peer-auth-token="" and falls back to source-IP membership for /_cache/get and /_cache/set. BREAKING in v1.56.0 β€” without this flag, a configured peer cache with an empty token now fails startup.
-peer-write-throughβ€”truePush eligible non-owner cache writes to the owner peer (/_cache/set) to keep owner shards warm under skewed traffic
-peer-write-through-min-ttlβ€”30sMinimum TTL required to push a write-through copy to the owner peer
-peer-hot-read-ahead-enabledβ€”falseEnable bounded periodic hot-read-ahead prefetch from peer hot indexes
-peer-hot-read-ahead-intervalβ€”30sBase interval for hot-index pull cycles
-peer-hot-read-ahead-jitterβ€”5sRandom jitter added to the read-ahead interval
-peer-hot-read-ahead-top-nβ€”256Number of hot keys requested per peer hot-index pull
-peer-hot-read-ahead-max-keys-per-intervalβ€”64Max prefetched keys per read-ahead cycle
-peer-hot-read-ahead-max-bytes-per-intervalβ€”8388608Max prefetched bytes per read-ahead cycle
-peer-hot-read-ahead-max-concurrencyβ€”4Max concurrent hot-index and prefetch peer requests
-peer-hot-read-ahead-min-ttlβ€”30sMinimum remaining TTL required for a prefetch candidate
-peer-hot-read-ahead-max-object-bytesβ€”262144Max object size eligible for read-ahead prefetch
-peer-hot-read-ahead-tenant-fair-shareβ€”50Per-tenant first-pass selection cap (% of key budget)
-peer-hot-read-ahead-error-backoffβ€”15sBase cooldown after read-ahead/index pull failures
-warmup-max-jitterβ€”0 (disabled)Maximum random delay before label cache warmup starts on startup. Spreads fleet instances across a random window so they don't all hit VL simultaneously. Recommended: 5s for ≀5 pods, 10s for ≀15 pods, 20s for ≀30 pods, 30s for larger fleets.

Peer-cache notes:

  • the Helm chart manages -peer-self, -peer-discovery, and -peer-dns automatically when peerCache.enabled=true; for srv or http modes use extraArgs
  • AZ-aware peer selection (-peer-self-az) prefers same-AZ peers for key fetches; Helm auto-injects it from metadata.labels['topology.kubernetes.io/zone'] via Downward API when peerCache.topologyLabel is set (default). Set podLabels: {topology.kubernetes.io/zone: <zone>} or rely on a platform webhook/node label syncer to populate the pod label.
  • for http discovery with a Prometheus HTTP SD endpoint, per-peer AZ is read automatically from labels.az or labels.availability_zone in the SD response β€” no extra flag required
  • GET /_cache/peers returns the current peer ring as {"peers":[...],"self":"...","count":N} β€” useful to verify discovery is working
  • peer-cache fetches preserve owner TTL and can compress larger /_cache/get responses with zstd or gzip
  • loki_vl_proxy_peer_cache_error_reason_total{reason=...} breaks opaque peer fetch failures into low-cardinality reasons like timeout, transport, status_502, body_read, and decode
  • with -peer-write-through=true (default), non-owner writes with TTL above threshold are pushed to owners and stored locally as short-lived shadows to reduce hot-pod disk skew
  • set -peer-auth-token fleet-wide whenever peer cache is enabled; it avoids transient startup or discovery-flap 403 responses caused by IP-membership-only peer auth
  • when -peer-auth-token is set, all peers must share the same token or peer-cache reuse will fail closed
  • /_cache/has?keys=k1,k2,... is a batch key-presence endpoint (metadata only, no value data transferred) used internally during startup warmup; callers can also use it to pick the freshest peer for a given set of keys before calling /_cache/get
  • startup warmup uses a two-phase peer-first strategy: one /_cache/has per peer to discover who has which label windows, then one /_cache/get per covered window from the freshest peer β€” only the first instance per window hits VL; see Fleet Cache Architecture

Current Tuning For Higher Fleet Reuse​

Use these knobs first:

  • set -warmup-max-jitter for any fleet with β‰₯3 pods to prevent thundering herd on rolling restart (see sizing table in Fleet Cache Architecture)
  • keep -peer-write-through=true (default) to warm owner shards under skewed traffic
  • tune -peer-write-through-min-ttl so only stable/hot entries are replicated
  • keep -response-compression=gzip for explicit Loki/Grafana-safe frontend behavior, or auto if you want the same gzip behavior through the legacy default
  • keep -response-compression-min-bytes around 1-4KiB to avoid wasting CPU on small metadata/control responses
  • keep -backend-compression=auto for smart upstream negotiation: loopback backends get identity (no decompression overhead), remote backends get zstd/gzip
  • keep query-range-windowing enabled with long history TTL and near-now freshness controls for mixed historical/live workloads

Bounded Hot Read-Ahead​

A bounded peer hot-read-ahead mode is implemented and can be enabled with:

-peer-hot-read-ahead-enabled=true

It keeps traffic bounded via key/byte/concurrency caps, jitter, tenant fairness, and error backoff. See Fleet Cache Architecture.

Peer Cache Token Management​

The chart auto-generates a Secret named <release>-peer-auth on first install when peerCache.enabled=true and peerCache.authToken is unset. The Secret holds a single key peer-auth-token populated with a random value (randAlphaNum 48), wired into the proxy Deployment as the -peer-auth-token flag via env-from-secret. This satisfies the v1.56.0 "token required by default" startup gate without operator action.

To rotate or replace the token:

  • Rotate (chart-managed) β€” delete the Secret and helm upgrade will regenerate it: kubectl delete secret <release>-peer-auth && helm upgrade <release> charts/loki-vl-proxy. All pods must restart to pick up the new value, so do this in a maintenance window or under a RollingUpdate strategy.
  • Externally manage β€” set peerCache.authToken to a value (or peerCache.existingSecret pointing at your own Secret) in the values file. The chart will skip auto-generation. Useful for GitOps flows that source secrets from Vault / External Secrets Operator.
  • Disable peer cache β€” set peerCache.enabled=false; no Secret is created and the token gate is skipped because no peer cache is configured.

To restore the pre-v1.56.0 IP-allowlist-only behavior without a token, pass -peer-insecure-ip-allowlist=true via extraArgs. This is not recommended: peer auth then depends only on source-IP membership in the discovered peer set, which is brittle under discovery flap and pod IP churn.

The owner hot-index endpoint (/_cache/hot) is internal peer-cache surface area and should not be exposed publicly.

Grafana Datasource Mapping​

These Grafana Loki datasource settings now have a direct proxy-side mapping:

Grafana SettingProxy Support
URL-listen, optional -tls-cert-file / -tls-key-file
No AuthenticationSupported by default
TLS Client Authentication-tls-client-ca-file, -tls-require-client-cert
Skip TLS certificate validationGrafana-side only
Add self-signed certificateGrafana-side only
HTTP headers (X-Scope-OrgID, auth headers)-forward-headers, -tenant-map, -auth.enabled
Forward Authorization specifically-forward-authorization=true (or -forward-headers=Authorization)
Allowed cookies-forward-cookies
Timeout-backend-timeout for proxy→VL, Grafana datasource timeout for Grafana→proxy
Maximum lines-max-lines

When -metrics.trust-proxy-headers=true, the proxy forwards trusted user headers and proxy-chain headers to the backend, plus derived context headers X-Loki-VL-Client-ID / X-Loki-VL-Client-Source.

Datasource auth credentials are forwarded separately as X-Loki-VL-Auth-User / X-Loki-VL-Auth-Source and are not used as enduser.id client identity.

To attribute requests to real Grafana users instead of datasource service credentials:

  1. Enable trusted proxy headers on Loki-VL-proxy:
-metrics.trust-proxy-headers=true
  1. Configure Grafana server dataproxy to forward the logged-in user header:
[dataproxy]
send_user_header = true
  1. Keep Loki datasource in proxy mode (access: proxy) and point it to Loki-VL-proxy:
datasources:
- name: Loki (VL Proxy)
type: loki
access: proxy
url: http://loki-vl-proxy:3100
  1. If Grafana sits behind an auth/reverse proxy, preserve user/proxy-chain headers end to end (X-Forwarded-User, X-Grafana-User, X-Forwarded-*, Forwarded).

Expected request-log behavior with this setup:

  • enduser.id reflects the trusted Grafana/auth user.
  • enduser.source indicates user-header source (for example grafana_user or forwarded_user).
  • auth.principal reflects datasource auth identity when present (for example basic-auth datasource user).

Alerting datasource integration is still partial: the proxy supports legacy Loki YAML rules reads and Prometheus-style JSON rules/alerts reads against a configured backend, but it does not yet implement the full Loki ruler write API surface.