# Configuration reference

Every observability, audit log and slow-query log environment variable, plus recommended configurations for local, production and multi-tenant deployments.

Every environment variable that controls the observability surface. Variables marked _(Enterprise)_ exist only when the Enterprise binary is running; everything else is available in all editions.

_(since v3.1.0)_

The audit log, slow-query log and cluster configuration surfaces are new in SurrealDB 3.1.

## How to read this page

- **Default** is the value used when the variable is unset. Cells marked ` - ` are required when the surrounding feature is enabled.
- **Edition** identifies which builds register the variable. Variables marked _(Enterprise)_ are no-ops on a Community binary.
- All variables can be set via environment, a `.env` file loaded by the deployment, or the orchestrator's secret store.

## Core knobs

These knobs control the two access paths (Prometheus pull and OTLP push) and the global telemetry switches. They are available in every edition.

<table>
    <thead>
        <tr>
            <th scope="col">Variable</th>
            <th scope="col">Default</th>
            <th scope="col">Edition</th>
            <th scope="col">Purpose</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td scope="row" data-label="Variable"><code>SURREAL_METRICS_ENABLED</code></td>
            <td scope="row" data-label="Default"><code>true</code></td>
            <td scope="row" data-label="Edition">_(Community)_</td>
            <td scope="row" data-label="Purpose">Mount the <code>/metrics</code> endpoint. When <code>false</code> the route returns <code>404</code>.</td>
        </tr>
        <tr>
            <td scope="row" data-label="Variable"><code>SURREAL_TELEMETRY_PROVIDER</code></td>
            <td scope="row" data-label="Default">unset</td>
            <td scope="row" data-label="Edition">_(Community)_</td>
            <td scope="row" data-label="Purpose">Set to <code>otlp</code> to enable the OTLP push pipeline (metrics, logs and traces). Any other value leaves OTLP off.</td>
        </tr>
        <tr>
            <td scope="row" data-label="Variable"><code>SURREAL_TELEMETRY_DISABLE_METRICS</code></td>
            <td scope="row" data-label="Default"><code>false</code></td>
            <td scope="row" data-label="Edition">_(Community)_</td>
            <td scope="row" data-label="Purpose">Skip the OTLP metrics reader specifically, leaving logs and traces unaffected.</td>
        </tr>
        <tr>
            <td scope="row" data-label="Variable"><code>SURREAL_TELEMETRY_DISABLE_TRACING</code></td>
            <td scope="row" data-label="Default"><code>false</code></td>
            <td scope="row" data-label="Edition">_(Community)_</td>
            <td scope="row" data-label="Purpose">Skip the OTLP trace exporter specifically, leaving metrics and logs unaffected.</td>
        </tr>
        <tr>
            <td scope="row" data-label="Variable"><code>SURREAL_SLOW_QUERY_METRIC_THRESHOLD_MS</code></td>
            <td scope="row" data-label="Default"><code>1000</code></td>
            <td scope="row" data-label="Edition">_(Community)_</td>
            <td scope="row" data-label="Purpose">Threshold above which a completed statement is counted on <code>surrealdb_slow_query_total</code>. Set to <code>0</code> to disable the counter.</td>
        </tr>
        <tr>
            <td scope="row" data-label="Variable"><code>SURREAL_PROCESS_METRICS_REFRESH_INTERVAL</code></td>
            <td scope="row" data-label="Default"><code>5</code></td>
            <td scope="row" data-label="Edition">_(Community)_</td>
            <td scope="row" data-label="Purpose">Cadence in seconds for refreshing the cached process snapshot that backs <code>surrealdb_process_memory_bytes</code> and <code>surrealdb_process_cpu_percent</code>. Floored at 1.</td>
        </tr>
        <tr>
            <td scope="row" data-label="Variable"><code>OTEL_EXPORTER_OTLP_ENDPOINT</code></td>
            <td scope="row" data-label="Default"><code>http://localhost:4317</code></td>
            <td scope="row" data-label="Edition">_(Community)_</td>
            <td scope="row" data-label="Purpose">OTLP gRPC endpoint. Standard OpenTelemetry variable.</td>
        </tr>
        <tr>
            <td scope="row" data-label="Variable"><code>OTEL_METRIC_EXPORT_INTERVAL</code></td>
            <td scope="row" data-label="Default"><code>60000</code> (ms)</td>
            <td scope="row" data-label="Edition">_(Community)_</td>
            <td scope="row" data-label="Purpose">OTLP push cadence. Standard OpenTelemetry variable.</td>
        </tr>
    </tbody>
</table>

## Audit log knobs

_(Enterprise)_

Controls the [Enterprise audit log pipeline](/docs/manage/observability/audit-logging.md). Setting `SURREAL_AUDIT_SINK=none` (the default) leaves the pipeline disabled entirely; no observer is registered and the cost is zero.

<table>
    <thead>
        <tr>
            <th scope="col">Variable</th>
            <th scope="col">Default</th>
            <th scope="col">Purpose</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td scope="row" data-label="Variable"><code>SURREAL_AUDIT_SINK</code></td>
            <td scope="row" data-label="Default"><code>none</code></td>
            <td scope="row" data-label="Purpose">One of <code>none</code>, <code>file</code>. <code>syslog</code> and <code>table</code> are reserved but rejected at startup.</td>
        </tr>
        <tr>
            <td scope="row" data-label="Variable"><code>SURREAL_AUDIT_FILE_PATH</code></td>
            <td scope="row" data-label="Default">-</td>
            <td scope="row" data-label="Purpose">Required when <code>SURREAL_AUDIT_SINK=file</code>. Parent directory must exist; startup fails loudly if it doesn't. File is opened with mode <code>0600</code> on Unix.</td>
        </tr>
        <tr>
            <td scope="row" data-label="Variable"><code>SURREAL_AUDIT_FILE_ROTATE_BYTES</code></td>
            <td scope="row" data-label="Default"><code>268435456</code> (256 MiB)</td>
            <td scope="row" data-label="Purpose">Size threshold that triggers rotation.</td>
        </tr>
        <tr>
            <td scope="row" data-label="Variable"><code>SURREAL_AUDIT_FILE_ROTATE_KEEP</code></td>
            <td scope="row" data-label="Default"><code>8</code></td>
            <td scope="row" data-label="Purpose">Number of rotated files to retain.</td>
        </tr>
        <tr>
            <td scope="row" data-label="Variable"><code>SURREAL_AUDIT_FSYNC_EVERY</code></td>
            <td scope="row" data-label="Default"><code>0</code></td>
            <td scope="row" data-label="Purpose">Mid-stream fsync cadence. <code>0</code> never fsyncs mid-stream; <code>1</code> fsyncs every record; <code>N&gt;1</code> fsyncs every Nth record. Rotation and graceful shutdown always fsync regardless.</td>
        </tr>
        <tr>
            <td scope="row" data-label="Variable"><code>SURREAL_AUDIT_HASH_CHAIN</code></td>
            <td scope="row" data-label="Default"><code>false</code></td>
            <td scope="row" data-label="Purpose">Adds <code>prev_hash</code> / <code>hash</code> SHA-256 fields to every record for tamper-evidence. <strong>Requires <code>SURREAL_AUDIT_FSYNC_EVERY=1</code></strong> - startup fails otherwise.</td>
        </tr>
        <tr>
            <td scope="row" data-label="Variable"><code>SURREAL_AUDIT_INCLUDE_SQL</code></td>
            <td scope="row" data-label="Default"><code>false</code></td>
            <td scope="row" data-label="Purpose">Include the full SQL text on <code>statement</code> records. Off by default; the identity and action context is still emitted.</td>
        </tr>
        <tr>
            <td scope="row" data-label="Variable"><code>SURREAL_AUDIT_QUEUE_CAPACITY</code></td>
            <td scope="row" data-label="Default"><code>4096</code></td>
            <td scope="row" data-label="Purpose">Records the bounded observer-to-worker queue can hold.</td>
        </tr>
        <tr>
            <td scope="row" data-label="Variable"><code>SURREAL_AUDIT_OVERFLOW</code></td>
            <td scope="row" data-label="Default"><code>block</code></td>
            <td scope="row" data-label="Purpose"><code>drop</code> (single non-blocking <code>try_send</code>) or <code>block</code> (bounded busy-yield retry). See <a href="/docs/manage/observability/audit-logging.md#overflow-semantics">overflow semantics</a>. Neither offers a lossless guarantee.</td>
        </tr>
        <tr>
            <td scope="row" data-label="Variable"><code>SURREAL_AUDIT_REDACT_TABLES</code></td>
            <td scope="row" data-label="Default">-</td>
            <td scope="row" data-label="Purpose">Comma-separated identifier tokens replaced with <code>***</code> in captured SQL.</td>
        </tr>
        <tr>
            <td scope="row" data-label="Variable"><code>SURREAL_AUDIT_REDACT_REGEX</code></td>
            <td scope="row" data-label="Default">-</td>
            <td scope="row" data-label="Purpose"><strong>Semicolon-separated</strong> regex patterns applied to captured SQL. Each pattern is compiled at startup; an invalid pattern fails startup.</td>
        </tr>
        <tr>
            <td scope="row" data-label="Variable"><code>SURREAL_AUDIT_REDACT_LITERALS</code></td>
            <td scope="row" data-label="Default"><code>false</code></td>
            <td scope="row" data-label="Purpose">Replace every quoted literal with <code>***</code>. Off by default.</td>
        </tr>
        <tr>
            <td scope="row" data-label="Variable"><code>SURREAL_AUDIT_OTEL_EXPORT</code></td>
            <td scope="row" data-label="Default"><code>false</code></td>
            <td scope="row" data-label="Purpose">Also emit each audit record as an OTel <code>LogRecord</code>. Off by default so compliance-sensitive records stay on the local file sink.</td>
        </tr>
    </tbody>
</table>

## Slow-query log knobs

_(Enterprise)_

Controls the [Enterprise slow-query log pipeline](/docs/manage/observability/slow-query-logging.md). Setting `SURREAL_SLOW_QUERY_SINK=none` (the default) leaves the pipeline disabled entirely.

<table>
    <thead>
        <tr>
            <th scope="col">Variable</th>
            <th scope="col">Default</th>
            <th scope="col">Purpose</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td scope="row" data-label="Variable"><code>SURREAL_SLOW_QUERY_SINK</code></td>
            <td scope="row" data-label="Default"><code>none</code></td>
            <td scope="row" data-label="Purpose">Same selector set as <code>SURREAL_AUDIT_SINK</code>.</td>
        </tr>
        <tr>
            <td scope="row" data-label="Variable"><code>SURREAL_SLOW_QUERY_FILE_PATH</code></td>
            <td scope="row" data-label="Default">-</td>
            <td scope="row" data-label="Purpose">Required when the sink is <code>file</code>.</td>
        </tr>
        <tr>
            <td scope="row" data-label="Variable"><code>SURREAL_SLOW_QUERY_FILE_ROTATE_BYTES</code></td>
            <td scope="row" data-label="Default"><code>268435456</code> (256 MiB)</td>
            <td scope="row" data-label="Purpose">Rotation threshold.</td>
        </tr>
        <tr>
            <td scope="row" data-label="Variable"><code>SURREAL_SLOW_QUERY_FILE_ROTATE_KEEP</code></td>
            <td scope="row" data-label="Default"><code>8</code></td>
            <td scope="row" data-label="Purpose">Retained rotation generations.</td>
        </tr>
        <tr>
            <td scope="row" data-label="Variable"><code>SURREAL_SLOW_QUERY_FSYNC_EVERY</code></td>
            <td scope="row" data-label="Default"><code>0</code></td>
            <td scope="row" data-label="Purpose">Same semantics as the audit equivalent.</td>
        </tr>
        <tr>
            <td scope="row" data-label="Variable"><code>SURREAL_SLOW_QUERY_HASH_CHAIN</code></td>
            <td scope="row" data-label="Default"><code>false</code></td>
            <td scope="row" data-label="Purpose">Tamper-evident chain. Requires <code>SURREAL_SLOW_QUERY_FSYNC_EVERY=1</code>.</td>
        </tr>
        <tr>
            <td scope="row" data-label="Variable"><code>SURREAL_SLOW_QUERY_THRESHOLD_MS</code></td>
            <td scope="row" data-label="Default">-</td>
            <td scope="row" data-label="Purpose">Duration threshold above which a statement is captured. Required when the sink is enabled.</td>
        </tr>
        <tr>
            <td scope="row" data-label="Variable"><code>SURREAL_SLOW_QUERY_INCLUDE_SQL</code></td>
            <td scope="row" data-label="Default"><code>true</code></td>
            <td scope="row" data-label="Purpose">Capture SQL text in slow-query records. Setting to <code>false</code> lets throughput-sensitive deployments skip per-statement <code>to_sql()</code> rendering at the cost of losing SQL context on captures.</td>
        </tr>
        <tr>
            <td scope="row" data-label="Variable"><code>SURREAL_SLOW_QUERY_QUEUE_CAPACITY</code></td>
            <td scope="row" data-label="Default"><code>4096</code></td>
            <td scope="row" data-label="Purpose">Records the queue can hold.</td>
        </tr>
        <tr>
            <td scope="row" data-label="Variable"><code>SURREAL_SLOW_QUERY_OVERFLOW</code></td>
            <td scope="row" data-label="Default"><code>drop</code></td>
            <td scope="row" data-label="Purpose"><strong>Default differs from audit</strong> - slow-query records are triage data, so dropping is preferred over busy-yielding the executor.</td>
        </tr>
        <tr>
            <td scope="row" data-label="Variable"><code>SURREAL_SLOW_QUERY_REDACT_TABLES</code></td>
            <td scope="row" data-label="Default">-</td>
            <td scope="row" data-label="Purpose">Same syntax as the audit equivalent.</td>
        </tr>
        <tr>
            <td scope="row" data-label="Variable"><code>SURREAL_SLOW_QUERY_REDACT_REGEX</code></td>
            <td scope="row" data-label="Default">-</td>
            <td scope="row" data-label="Purpose">Same syntax as the audit equivalent.</td>
        </tr>
        <tr>
            <td scope="row" data-label="Variable"><code>SURREAL_SLOW_QUERY_REDACT_LITERALS</code></td>
            <td scope="row" data-label="Default"><code>false</code></td>
            <td scope="row" data-label="Purpose">Same semantics as the audit equivalent.</td>
        </tr>
        <tr>
            <td scope="row" data-label="Variable"><code>SURREAL_SLOW_QUERY_OTEL_EXPORT</code></td>
            <td scope="row" data-label="Default"><code>false</code></td>
            <td scope="row" data-label="Purpose">Emit each slow-query record as an OTel <code>LogRecord</code> at <code>WARN</code> severity.</td>
        </tr>
    </tbody>
</table>

## Cluster networking, consensus and storage

_(Enterprise)_

> [!NOTE]
> The `SURREAL_DS_*` variables below apply to multi-node clusters, which use distributed storage with replication and consensus - on [SurrealDB Cloud Scale](https://surrealdb.com/pricing/scale) or in self-hosted Enterprise installations.

A short list of the cluster networking, consensus and storage-memory knobs that operators routinely tune in response to a metric signal. The complete `SURREAL_DS_*` reference is part of the Enterprise Kubernetes deployment guide.

<table>
    <thead>
        <tr>
            <th scope="col">Variable</th>
            <th scope="col">Default</th>
            <th scope="col">Tune in response to</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td scope="row" data-label="Variable"><code>SURREAL_DS_NTW_INBOUND_BYTES</code></td>
            <td scope="row" data-label="Default"><code>3 * MAX_MSG_BYTES</code> (768 MiB) per <code>MessageClass</code></td>
            <td scope="row" data-label="Trigger"><code>QUIC inbound bytes ... saturation warning</code> log.</td>
        </tr>
        <tr>
            <td scope="row" data-label="Variable"><code>SURREAL_DS_NTW_INFLIGHT_PROCESSING_CAP</code></td>
            <td scope="row" data-label="Default"><code>max(384, num_cpus*32)</code></td>
            <td scope="row" data-label="Trigger"><code>QUIC inbound handlers ... saturation warning</code> log.</td>
        </tr>
        <tr>
            <td scope="row" data-label="Variable"><code>SURREAL_DS_MAX_READ_OPERATIONS</code></td>
            <td scope="row" data-label="Default"><code>192</code></td>
            <td scope="row" data-label="Trigger">Steady-state RSS pressure or scan-heavy workloads.</td>
        </tr>
        <tr>
            <td scope="row" data-label="Variable"><code>SURREAL_DS_MAX_WRITE_OPERATIONS</code></td>
            <td scope="row" data-label="Default"><code>96</code></td>
            <td scope="row" data-label="Trigger"><code>Write operations limit saturation warning</code> log.</td>
        </tr>
        <tr>
            <td scope="row" data-label="Variable"><code>SURREAL_DS_CONSENSUS_FAST_QUORUM_TIMEOUT_MS</code></td>
            <td scope="row" data-label="Default">implementation default</td>
            <td scope="row" data-label="Trigger">Rising <code>surrealdb_ds_consensus_fast_quorum_timeouts_total</code>.</td>
        </tr>
        <tr>
            <td scope="row" data-label="Variable"><code>SURREAL_DS_RETRY_BASE_MS</code> / <code>SURREAL_DS_RETRY_MAX_MS</code></td>
            <td scope="row" data-label="Default"><code>500</code> / <code>5000</code></td>
            <td scope="row" data-label="Trigger">Rising <code>surrealdb_ds_finalize_prepare_retries_total</code>.</td>
        </tr>
        <tr>
            <td scope="row" data-label="Variable"><code>SURREAL_DS_ROCKSDB_BLOCK_CACHE_SIZE</code>_(since v3.1.1)_</td>
            <td scope="row" data-label="Default"><code>max(memory/2 - 1 GiB, 16 MiB)</code></td>
            <td scope="row" data-label="Trigger">Steady-state RSS pressure. The largest single consumer in a node using the RocksDB durable backend. Full-text index reads are served through it entirely; vector index reads hit it on load and on cache misses. The default is derived from detected memory, so set it explicitly on memory-constrained pods.</td>
        </tr>
        <tr>
            <td scope="row" data-label="Variable"><code>SURREAL_DS_ROCKSDB_MAX_WRITE_BUFFER_NUMBER</code>_(since v3.1.1)_</td>
            <td scope="row" data-label="Default">Derived from detected memory (<code>2</code>-<code>32</code>)</td>
            <td scope="row" data-label="Trigger">RSS pressure during sustained write phases. The memtable ceiling is this value multiplied by <code>SURREAL_DS_ROCKSDB_WRITE_BUFFER_SIZE</code> and by the four heavy column families, so it is the knob to cap first when headroom is tight.</td>
        </tr>
    </tbody>
</table>

> [!NOTE]
> `SURREAL_DS_ROCKSDB_*` applies only when a node's store path selects the RocksDB durable backend. Memtable and block-cache memory are accounted together, and an undersized write budget presents as write latency rather than errors - but the operative limits are the per-column-family memtable count and the L0 compaction trigger, not the combined ceiling. `SURREAL_DS_ROCKSDB_WRITE_BUFFER_SIZE` sets the per-column-family memtable size; the complete reference is in the Enterprise Kubernetes deployment guide.

## Recommended configurations

**Local development**

The minimum needed to scrape metrics from a local server during development:

```bash
surreal start --user root --pass secret
# Anonymous: only the public allowlist
curl http://127.0.0.1:8000/metrics
# Authenticated: the full surface
curl -u root:secret http://127.0.0.1:8000/metrics
```

To push to a local OpenTelemetry collector (for example a Grafana / Tempo / Loki / Prometheus docker-compose stack):

```bash
SURREAL_TELEMETRY_PROVIDER=otlp \
OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4317 \
surreal start --user root --pass secret
```

To disable the endpoint entirely (useful when running with a third-party agent that scrapes via OTLP only):

```bash
SURREAL_METRICS_ENABLED=false surreal start ...
# /metrics → 404
```

**Self-hosted production**

For a self-hosted production deployment that ships metrics over OTLP and writes audit and slow-query records to a local sink for SIEM ingestion:

```bash
# Telemetry
SURREAL_METRICS_ENABLED=true
SURREAL_TELEMETRY_PROVIDER=otlp
OTEL_EXPORTER_OTLP_ENDPOINT=https://collector.internal:4317
OTEL_METRIC_EXPORT_INTERVAL=30000

# Audit log (Enterprise) - durable, hash-chained
SURREAL_AUDIT_SINK=file
SURREAL_AUDIT_FILE_PATH=/var/log/surrealdb/audit.log
SURREAL_AUDIT_FSYNC_EVERY=1
SURREAL_AUDIT_HASH_CHAIN=true
SURREAL_AUDIT_INCLUDE_SQL=true
SURREAL_AUDIT_REDACT_LITERALS=true
SURREAL_AUDIT_OVERFLOW=block

# Slow-query log (Enterprise) - best-effort triage data
SURREAL_SLOW_QUERY_SINK=file
SURREAL_SLOW_QUERY_FILE_PATH=/var/log/surrealdb/slow-query.log
SURREAL_SLOW_QUERY_THRESHOLD_MS=250
```

Pair this with the alert hints in the [metrics reference](/docs/manage/observability/metrics.md#alert-hints) and the alerting rules on `surrealdb_audit_dropped`, `surrealdb_audit_append_errors` and `surrealdb_audit_queue_depth` listed in the [Compliance checklist](#compliance-checklist) below.

**Multi-tenant / cloud-facing**

For pods that serve customer traffic directly, the default posture is **deny scraping at the pod boundary** and serve metrics from an internal sidecar instead. Customer-visible metrics should be filtered at a proxy.

```bash
# Customer-facing pod
SURREAL_METRICS_ENABLED=false

# Internal sidecar pod (same workload, internal-only interface)
SURREAL_METRICS_ENABLED=true
SURREAL_TELEMETRY_PROVIDER=otlp
OTEL_EXPORTER_OTLP_ENDPOINT=https://collector.internal:4317
```

Filter on `namespace` and `database` at the proxy and strip the `user` label before exposing to a customer. See the [Recommended cloud whitelist](/docs/manage/observability/metrics.md#recommended-cloud-whitelist) for the suggested customer-visible subset.

## Compliance checklist

_(Enterprise)_

For deployments that require tamper-evident, durable audit trails the recommended configuration is:

```bash
SURREAL_AUDIT_SINK=file
SURREAL_AUDIT_FILE_PATH=/var/log/surrealdb/audit.log
SURREAL_AUDIT_FSYNC_EVERY=1            # required for hash chain
SURREAL_AUDIT_HASH_CHAIN=true
SURREAL_AUDIT_INCLUDE_SQL=true         # if a regulator requires statement text
SURREAL_AUDIT_REDACT_LITERALS=true     # scrub embedded PII from quoted values
SURREAL_AUDIT_OVERFLOW=block           # already the default; explicit for clarity
```

Plus alerts on:

- `surrealdb_audit_dropped` - any non-zero rate is a lost record.
- `surrealdb_audit_append_errors` - any non-zero rate is a lost record.
- `surrealdb_audit_queue_depth` sustained above ~50% of `SURREAL_AUDIT_QUEUE_CAPACITY` - the sink is falling behind.

For the full pipeline details (record shape, rotation, hash chain, redaction) see the [Audit logging](/docs/manage/observability/audit-logging.md) reference.

## Troubleshooting

| Symptom | Likely cause | Investigation |
| --- | --- | --- |
| `/metrics` returns `404` | `SURREAL_METRICS_ENABLED=false` | Re-enable, or scrape over OTLP instead. |
| `/metrics` returns only six metrics | Anonymous scrape against the public allowlist | Configure basic-auth with root credentials. |
| No `surrealdb_ds_*` metrics on a single-node setup | Enterprise composer did not start a metrics reader | Confirm the Enterprise binary is running and at least one of `SURREAL_METRICS_ENABLED=true` / `SURREAL_TELEMETRY_PROVIDER=otlp` is set. |
| `surrealdb_audit_records` missing entirely | Audit sink set to `none` (the default) | Set `SURREAL_AUDIT_SINK=file` and `SURREAL_AUDIT_FILE_PATH`. |
| Sustained `surrealdb_audit_dropped` rate | Worker can't keep up | Raise `SURREAL_AUDIT_QUEUE_CAPACITY`; check sink disk I/O; consider switching from `block` to `drop` if drops are acceptable. |
| `QUIC inbound bytes ... saturation warning` log | Per-class inbound budget saturated | Raise `SURREAL_DS_NTW_INBOUND_BYTES`. |
| OTLP collector not receiving metrics | `SURREAL_TELEMETRY_PROVIDER` unset, or `SURREAL_TELEMETRY_DISABLE_METRICS=true` | Confirm both, plus that `OTEL_EXPORTER_OTLP_ENDPOINT` is reachable. |
| Hash chain rejected at startup | `SURREAL_AUDIT_HASH_CHAIN=true` without `SURREAL_AUDIT_FSYNC_EVERY=1` | Set both, or disable hash chaining. |
