Skip to content

Latest commit

 

History

History
609 lines (548 loc) · 144 KB

File metadata and controls

609 lines (548 loc) · 144 KB

ENVs

This document describes the environment variables that can be used to configure the ar.io node.

ENV_NAME TYPE DEFAULT_VALUE DESCRIPTION
START_HEIGHT Number or "Infinity" 0 Starting block height for node synchronization (0 = start from the beginning)
STOP_HEIGHT Number or "Infinity" "Infinity" Stop block height for node synchronization (Infinity = keep syncing until stopped)
TRUSTED_NODE_URL String "https://arweave.net" Arweave node to use for fetching data
TRUSTED_NODE_HOST String "arweave.net" Hostname for the trusted Arweave node used by Envoy proxy
TRUSTED_NODE_PORT Number 443 Port for the trusted Arweave node used by Envoy proxy
TRUSTED_GATEWAY_URL String "https://arweave.net" Arweave node to use for proxying requests
TRUSTED_GATEWAYS_URLS String See below A JSON map of gateway URLs to priority (number) or config object ({"priority": N, "trusted": bool}). Simple number values are implicitly trusted. Default: {"https://turbo-gateway.com": 1, "https://arweave.net": {"priority": 2, "trusted": false}}. When trusted is false, data is only cached if a hash is already known and matches; the hash is never written to the DB as authoritative. Untrusted gateways also receive requests without the ar-io-* provenance query params (provenance is still sent via X-AR-IO-* headers) — required for CDN-fronted gateways such as arweave.net, whose CDN returns 502 on those query params.
TRUSTED_GATEWAYS_REQUEST_TIMEOUT_MS String "10000" Connection timeout in milliseconds for trusted gateways (time to receive response headers)
GATEWAY_AGENT_IDLE_SOCKET_TIMEOUT_MS Number 50000 Idle-socket timeout (ms) for the outbound trusted-gateway keep-alive agent. Must be strictly less than the peer gateway's server keep-alive timeout (HTTP_KEEP_ALIVE_TIMEOUT_MS, default 60000) to avoid a keep-alive reuse race where the client reuses a socket the server is simultaneously closing (causes ~8-10s peer-fetch stalls).
GATEWAY_SLOW_SOCKET_ACQUISITION_LOG_THRESHOLD_MS Number 1000 Outbound gateway socket acquisitions (time from a request needing a socket to one being assigned) at or above this threshold log a warn. Surfaces keep-alive pool waits / socket-reuse stalls before the request hits the wire. See metrics gateway_socket_acquisition_seconds / gateway_socket_connect_seconds.
GATEWAY_MAX_SOCKETS_PER_HOST Number or JSON 16 Max concurrent sockets per trusted (internal peer) gateway host. Requests queue once this many sockets to a host are busy; on a busy node the default can leave inter-gateway requests waiting seconds for a slot (high gateway_socket_acquisition_seconds). Trusted peers are low-risk to raise. Accepts a bare integer (all hosts) or a per-host object with an optional default, e.g. {"http://10.84.0.82:4000":128,"default":64}.
GATEWAY_UNTRUSTED_MAX_SOCKETS_PER_HOST Number or JSON = GATEWAY_MAX_SOCKETS_PER_HOST Max concurrent sockets per untrusted gateway host (e.g. arweave.net, CDN-fronted). Set lower than the trusted cap to avoid overwhelming a CDN upstream that can throttle / 502 under high concurrency. Same number-or-per-host-object form; untrusted hosts (e.g. arweave.net) are configured here, not in the trusted var.
GATEWAY_MAX_FREE_SOCKETS_PER_HOST Number or JSON 4 Max idle keep-alive sockets kept warm per gateway host. Raise toward the max-sockets value when raising GATEWAY_MAX_SOCKETS_PER_HOST so idle capacity is reused instead of reopening TCP/TLS connections under bursty load. Same number-or-per-host-object form.
TRUSTED_GATEWAYS_SEND_UNTRUSTED_PARAMS Boolean false Kill-switch / rollback lever. When false (default), the ar-io-* provenance query params are omitted for untrusted gateways (trusted: false) — provenance still travels via X-AR-IO-* headers. Set to true to revert to the legacy behavior of sending those query params to every gateway. Leave default unless a specific upstream requires the params and tolerates them.
STREAM_STALL_TIMEOUT_MS String "30000" Stall timeout in milliseconds for data streams from gateways and peers. Stream is aborted if no data is received for this duration. Prevents stalled transfers from hanging indefinitely while allowing large, actively-streaming transfers to complete.
STREAM_REQUEST_TIMEOUT_MS String "900000" Wall-clock cap (ms) on a single HTTP-response stream from ArIODataSource / GatewaysDataSource. Fires regardless of pause/resume state. Belt-and-suspenders for the backpressure-pause-then-upstream-stall wedge — STREAM_STALL_TIMEOUT_MS is cleared on pause, so an upstream that goes silent while we're paused for backpressure can hang a worker forever. Default 15 minutes is generous enough that legitimately slow transfers (~1.1 MB/s for a 1 GB bundle) complete; tighten for environments where peers are fast or relax for genuinely massive transfers.
DATA_IMPORTER_DOWNLOAD_TIMEOUT_MS String "1200000" Wall-clock cap (ms) on the entire DataImporter.download() operation — covers the pre-stream cascade walk (SequentialDataSource iterating through BACKGROUND_RETRIEVAL_ORDER) AND the stream-consumption phase. Enforced via Promise.race between the timeout and the download IIFE, so download() is guaranteed to return when the timer fires regardless of cascade behavior; AbortController.abort() is also called as best-effort cleanup but isn't relied upon for the guarantee (some axios/socket states don't honor abort). Catches the pre-getData wedge where workers block in await getData() waiting for some source to return a stream — STREAM_REQUEST_TIMEOUT_MS cannot reach this state because no stream exists yet. Default 20 minutes is comfortably above STREAM_REQUEST_TIMEOUT_MS so legitimate slow downloads finish first; this fires only as a backstop when the cascade itself hangs.
GATEWAYS_RANGE_ACCEPT_200_MAX_OFFSET Number 10485760 (10 MiB) Maximum region.offset (in bytes) for which GatewaysDataSource will accept a full HTTP 200 body in response to a Range request and slice it locally. Some upstreams (e.g., nginx with proxy_cache enabled but no slice module) silently strip Range and return the full body; we accept that and slice in-process, but the prefix [0, region.offset) bytes still cross the wire. Above this threshold the bandwidth cost is too high and the response is rejected so the data source chain falls through to the next priority tier. Tune via the gateway_range_ignored_total metric (label outcome=rejected_offset_too_high).
TRUSTED_GATEWAYS_BLOCKED_ORIGINS String "" Comma-separated list of X-AR-IO-Origin header values to block when forwarding to trusted gateways (prevents loops and blocks unwanted sources)
TRUSTED_GATEWAYS_BLOCKED_IPS_AND_CIDRS String "" Comma-separated list of IPs and CIDR ranges to block when forwarding to trusted gateways (prevents forwarding requests from specific client IPs)
TRUSTED_ARNS_GATEWAY_URL String "https://NAME.turbo-gateway.com" ArNS gateway
TRUSTED_ARNS_RESOLVER_HOST_HEADER String - Host header override for ArNS resolver requests; supports __NAME__ placeholder (e.g., __NAME__.ar-io.dev)
TURBO_ENDPOINT String "https://turbo.arweave.dev" Turbo endpoint URL for root transaction ID lookups
TURBO_REQUEST_TIMEOUT_MS Number 10000 Request timeout in milliseconds for Turbo requests
TURBO_REQUEST_RETRY_COUNT Number 3 Number of retries for Turbo requests
BUNDLER_URLS String "https://turbo.ardrive.io/" Comma-separated list of bundler service URLs advertised in /ar-io/info endpoint for client service discovery
GATEWAYS_ROOT_TX_URLS JSON Object {"https://turbo-gateway.com": 1} JSON map of AR.IO gateway URLs and priority weights for root transaction offset discovery via HEAD requests. Lower numbers = higher priority
GATEWAYS_ROOT_TX_REQUEST_TIMEOUT_MS Number 10000 Request timeout in milliseconds for gateway HEAD requests
GATEWAYS_ROOT_TX_RATE_LIMIT_BURST_SIZE Number 5 Rate limit burst size for gateway HEAD requests
GATEWAYS_ROOT_TX_RATE_LIMIT_TOKENS_PER_INTERVAL Number 6 Rate limit tokens per interval for gateway HEAD requests (6 per minute = 1 per 10 seconds)
GATEWAYS_ROOT_TX_RATE_LIMIT_INTERVAL String "minute" Rate limit interval for gateway HEAD requests (second/minute/hour/day)
HYPERBEAM_ENDPOINT String "https://arweave.net" HyperBEAM node URL for offset lookups. Override to use a different HyperBEAM node
HYPERBEAM_REQUEST_TIMEOUT_MS Number 10000 Request timeout in milliseconds for HyperBEAM offset lookups
HYPERBEAM_ROOT_TX_RATE_LIMIT_BURST_SIZE Number 5 Rate limit burst size for HyperBEAM offset lookups
HYPERBEAM_ROOT_TX_RATE_LIMIT_TOKENS_PER_INTERVAL Number 6 Rate limit tokens per interval for HyperBEAM offset lookups (6 per minute = 1 per 10 seconds)
HYPERBEAM_ROOT_TX_RATE_LIMIT_INTERVAL String "minute" Rate limit interval for HyperBEAM offset lookups (second/minute/hour/day)
ROOT_TX_LOOKUP_ORDER String "db,gateways,hyperbeam,cdb,graphql" Comma-separated list of root TX lookup sources in order of priority. Options: 'db' (local database), 'cdb' (CDB64 file index), 'gateways' (AR.IO gateways), 'hyperbeam' (HyperBEAM offset API), 'turbo' (Turbo API), 'graphql' (GraphQL). CDB64 enabled by default since Release 67
GRAPHQL_ROOT_TX_BATCH_ENABLED Boolean false Coalesce concurrent GraphQL root-TX lookups into single transactions(ids: [...]) queries. Each batched request consumes one rate-limiter token instead of one per ID, so a page-load burst of asset lookups no longer starves the bucket. Disabled by default; when off, behavior is unchanged (one query per ID)
GRAPHQL_ROOT_TX_BATCH_WINDOW_MS Number 20 Coalescing window: how long a forming batch waits for more IDs before flushing. A batch also flushes eagerly once it reaches the max batch size. Only used when GRAPHQL_ROOT_TX_BATCH_ENABLED=true
GRAPHQL_ROOT_TX_BATCH_MAX_SIZE Number 100 Default max IDs per batched query. Arweave GraphQL caps first at 100, so this should not exceed 100
GRAPHQL_ROOT_TX_BATCH_MAX_SIZE_BY_URL JSON object {} Optional per-endpoint max batch size overrides, e.g. {"https://arweave-search.goldsky.com": 9} — some gateways cap the number of IDs per query below GRAPHQL_ROOT_TX_BATCH_MAX_SIZE. Endpoints absent from the map use GRAPHQL_ROOT_TX_BATCH_MAX_SIZE
GRAPHQL_ROOT_TX_BATCH_MAX_QUEUE_DEPTH Number 1000 Max IDs that may be waiting (pending + in-flight) before new lookups are shed (resolved as not-found) rather than queued unbounded
GRAPHQL_ROOT_TX_BATCH_TOKEN_MAX_WAIT_MS Number 5000 Max time a batch waits to acquire a rate-limiter token before giving up (its IDs then resolve as not-found rather than blocking indefinitely)
CHUNK_METADATA_ANCHOR_ENABLED Boolean true Enable the chain-anchored chunk metadata fast path for offset → tx + data_root resolution. HEADs a reference peer's /chunk/{offset}/data, parses the X-Arweave-Chunk-* headers, then cross-checks against the chain (/tx/{id}/offset, /tx/{id}) before returning. Slots between the local DB and the existing tx_path / chain-binary-search fallbacks. Reuses the GATEWAYS_ROOT_TX_URLS peer pool when populated, falling back to the first TRUSTED_GATEWAYS_URLS entry. See #681
CHUNK_METADATA_ANCHOR_REQUEST_TIMEOUT_MS Number 5000 HTTP timeout (ms) for the peer HEAD/range-GET against /chunk/{offset}/data. Tight by design — a slow peer should fail fast and let the composite fall through to the existing path
CHUNK_METADATA_ANCHOR_TX_CACHE_SIZE Number 1024 LRU cache size for chain-anchored tx metadata. Each entry is keyed by tx-id and holds the chain-verified bounds; sized for the typical working set of recently-probed txs
CHUNK_METADATA_ANCHOR_TX_CACHE_TTL_SECONDS Number 300 LRU TTL (seconds) for chain-anchored tx metadata. Bounds the window over which a single chain cross-check covers repeated chunk probes against the same tx. Values are immutable for stable txs, so TTL is purely an eviction knob, not a correctness one
CDB64_ROOT_TX_INDEX_WATCH Boolean true Enable runtime file watching for local CDB64 directories. When true, .cdb files added to or removed from watched directories are automatically loaded/unloaded without restart
CDB64_ROOT_TX_INDEX_SOURCES String shipped manifest (see note) Comma-separated list of CDB64 index sources. Supports: local paths, directories, Arweave TX IDs (43-char base64url), bundle data items (txId:offset:size), HTTP URLs, and partitioned manifests. Default ships with ~964M records covering non-AO, non-Redstone data items up to block 1,820,000
CDB64_REMOTE_RETRIEVAL_ORDER String "gateways,chunks" Comma-separated list of data sources for fetching remote CDB64 files. Options: 'gateways' (trusted gateways), 'chunks' (L1 chunk reconstruction), 'tx-data' (Arweave node /tx/:id/data)
CDB64_REMOTE_CACHE_MAX_REGIONS Number 100 Maximum number of byte-range regions to cache per remote CDB64 source
CDB64_REMOTE_CACHE_TTL_MS Number 300000 TTL in milliseconds for cached CDB64 byte-range regions (5 minutes)
CDB64_REMOTE_REQUEST_TIMEOUT_MS Number 30000 Request timeout in milliseconds for remote CDB64 source requests (30 seconds)
CDB64_REMOTE_MAX_CONCURRENT_REQUESTS Number 4 Maximum concurrent HTTP requests across all remote CDB64 sources. Limits request pile-up when reading CDB files from HTTP/S3 endpoints
CDB64_REMOTE_SEMAPHORE_TIMEOUT_MS Number 5000 Maximum time in milliseconds to wait for a request slot before failing. Prevents indefinite blocking when concurrent request limit is reached
ROOT_TX_CACHE_MAX_SIZE Number 100000 Maximum size of the root transaction ID cache
ROOT_TX_CACHE_TTL_MS Number 300000 TTL in milliseconds for root transaction ID cache entries (5 minutes)
ROOT_TX_INDEX_CIRCUIT_BREAKER_TIMEOUT_MS Number 30000 Circuit breaker timeout in milliseconds for root transaction index requests
ROOT_TX_INDEX_CIRCUIT_BREAKER_FAILURE_THRESHOLD Number 50 Circuit breaker failure threshold percentage for root transaction index requests
ROOT_TX_INDEX_CIRCUIT_BREAKER_SUCCESS_THRESHOLD Number 2 Number of successful requests needed to close circuit breaker for root transaction index
WEIGHTED_PEERS_TEMPERATURE_DELTA Number 0.1 Any positive number above 0, best to keep 1 or less. Used to determine the sensivity of which the probability of failing or succeeding peers decreases or increases.
PEER_MAX_CONCURRENT_OUTBOUND Number 10 Maximum concurrent outbound contiguous data requests per AR.IO peer. Saturated peers are skipped rather than queued. Does not apply to trusted gateway forwarding or chunk retrieval.
PEER_CANDIDATE_COUNT Number 5 Number of candidate peers selected for each contiguous data retrieval attempt from AR.IO peers.
PEER_HEDGE_DELAY_MS Number 500 Milliseconds to wait before firing a hedged request to the next candidate peer. Set to 0 for sequential (advance on failure only) behavior.
PEER_MAX_HEDGED_REQUESTS Number 3 Maximum number of concurrent hedged requests per getData() call to AR.IO peers.
PEER_HASH_RING_VIRTUAL_NODES Number 150 Number of virtual nodes per peer on the consistent hash ring, used for cache-locality peer selection.
PEER_HASH_RING_HOME_SET_SIZE Number 3 Number of "home" peers returned by the hash ring for a given data ID. Home peers are prioritized before weighted fallback selection.
INSTANCE_ID String "" Adds an "INSTANCE_ID" field to output logs
LOG_FORMAT String "simple" Sets the format of output logs, accepts "simple" and "json"
SKIP_CACHE Boolean false If true, skips the local cache and always fetches headers from the node
SKIP_DATA_CACHE Boolean false If true, skips the data cache (read-through data cache) and always fetches data from upstream sources
NEGATIVE_CACHE_ENABLED Boolean false If true, enables the negative data cache that short-circuits 404 responses for repeatedly not-found data IDs
NEGATIVE_CACHE_MAX_SIZE Number 100000 Maximum number of entries in the negative cache and miss tracker LRU structures
NEGATIVE_CACHE_TTL_MS Number 7200000 Base TTL in milliseconds for negative cache entries (2 hours). Doubles with each re-promotion (exponential backoff) up to NEGATIVE_CACHE_MAX_TTL_MS
NEGATIVE_CACHE_MISS_THRESHOLD_MS Number 300000 Duration in milliseconds over which misses must occur before first promotion to negative cache (5 minutes). Accepts 0 to disable duration requirement
NEGATIVE_CACHE_MISS_COUNT_THRESHOLD Number 10 Number of misses required before an ID can be promoted to the negative cache. After first promotion, a single miss triggers re-promotion
NEGATIVE_CACHE_MISS_TRACKER_TTL_MS Number 3600000 TTL in milliseconds for miss tracker entries (1 hour). Controls how long partial miss counts are retained before expiring
NEGATIVE_CACHE_MAX_TTL_MS Number 172800000 Maximum TTL in milliseconds for negative cache entries (48 hours). Caps the exponential backoff growth
NEGATIVE_CACHE_PROMOTION_HISTORY_TTL_MS Number 604800000 TTL in milliseconds for promotion history entries (7 days). Controls how long the cache remembers prior promotions for fast re-promotion and backoff
NEGATIVE_CACHE_HEALTH_WINDOW_MS Number 60000 Sliding window in milliseconds for tracking success/failure rates (1 minute). Counters reset after each window
NEGATIVE_CACHE_UNHEALTHY_THRESHOLD Number 0.8 Failure rate (0-1) above which the system is considered unhealthy and promotions are suppressed. 0.8 means >80% failures suppresses promotions
NEGATIVE_CACHE_HEALTH_MIN_SAMPLE_SIZE Number 10 Minimum number of success+failure samples in the health window before the unhealthy threshold is evaluated. Prevents suppression on insufficient data
UNTRUSTED_CACHE_RETRY_RATE Number 0.1 Probability (0-1) of stochastically re-verifying cached data from untrusted sources on cache hit
TRUSTED_CACHE_RETRY_RATE Number 0.0 Probability (0-1) of stochastically re-verifying cached data from trusted sources on cache hit
BACKGROUND_CACHE_RANGE_MAX_SIZE Number 0 Maximum item size (bytes) eligible for background full-item cache after a range cache miss. 0 disables background caching
BACKGROUND_CACHE_RANGE_CONCURRENCY Number 1 Maximum concurrent background full-item cache fetches triggered by range cache misses
PORT Number 4000 ar.io node exposed port number
HTTP_KEEP_ALIVE_TIMEOUT_MS Number 60000 Idle keepalive timeout (ms) for core's HTTP server (server.keepAliveTimeout). Must exceed Envoy's upstream idle_timeout (ENVOY_ARIO_GATEWAY_UPSTREAM_IDLE_TIMEOUT, default 55s) so Envoy recycles pooled connections before core closes them — otherwise Envoy races a request onto a closing connection and clients see an instant 5xx with no upstream response.
HTTP_HEADERS_TIMEOUT_MS Number 65000 Headers timeout (ms) for core's HTTP server (server.headersTimeout). Must be strictly greater than HTTP_KEEP_ALIVE_TIMEOUT_MS (Node requirement).
ENVOY_ARIO_GATEWAY_UPSTREAM_IDLE_TIMEOUT String "55s" Envoy upstream idle_timeout for the ario_gateways (core) cluster. Keep below core's HTTP_KEEP_ALIVE_TIMEOUT_MS (default 60s) so Envoy is always the side that recycles idle connections first. Accepts an Envoy duration string (e.g. 55s).
SIMULATED_REQUEST_FAILURE_RATE Number 0 Number from 0 to 1, representing the probability of a request failing
AR_IO_WALLET String "" Arweave wallet address used for staking and rewards
ADMIN_API_KEY String Generated API key used for admin API requests (if not set, it's generated and logged into the console)
ADMIN_API_KEY_FILE String Generated Alternative way to set the API key used for admin API requests via filepath, it takes precedene over ADMIN_API_KEY if defined
BACKFILL_BUNDLE_RECORDS Boolean false If true, ar.io node will start indexing missing bundles
FILTER_CHANGE_REPROCESS Boolean false If true, all indexed bundles will be reprocessed with the new filters (you can use this when you change the filters)
ON_DEMAND_RETRIEVAL_ORDER String trusted-gateways,ar-io-network,chunks-offset-aware,tx-data Data source retrieval order for on-demand data requests. Note: 'chunks-data-item' is deprecated, use 'chunks-offset-aware' instead
SKIP_FORWARDING_HEADERS String ao-peer-port Comma-separated list of HTTP headers that indicate a compute-origin request (e.g., from HyperBEAM). When present, remote forwarding to AR.IO peers and trusted gateways is skipped to prevent request loops. Local sources (cache, S3, DB) are still served.
SKIP_FORWARDING_USER_AGENTS String (empty) Comma-separated list of User-Agent substrings. Requests whose User-Agent contains any of these substrings (case-insensitive) skip remote forwarding.
SKIP_FORWARDING_EMPTY_USER_AGENT Boolean true When true, requests with missing or empty User-Agent headers skip remote forwarding. Catches HTTP clients like Erlang's gun (used by HyperBEAM) that don't send a User-Agent.
BACKGROUND_RETRIEVAL_ORDER String chunks Data source retrieval order for background data requests (i.e., unbundling)
ENABLE_SAMPLING_DATA_SOURCE Boolean false Enable probabilistic sampling of requests through an experimental data source for A/B testing
SAMPLING_DATA_SOURCE String undefined Data source name to sample (e.g., chunks-offset-aware, trusted-gateways). Required when ENABLE_SAMPLING_DATA_SOURCE is true
SAMPLING_RATE Number 0.1 Fraction of requests to sample (0.0 to 1.0). 0.1 = 10% of requests
SAMPLING_STRATEGY String random Sampling strategy: random (uniform distribution) or deterministic (hash-based, same ID always gets same sampling decision)
ANS104_UNBUNDLE_FILTER String {"never": true} Only bundles compliant with this filter will be unbundled
ANS104_INDEX_FILTER String {"never": true} Only bundles compliant with this filter will be indexed
ANS104_DOWNLOAD_WORKERS String 5 Sets the number of ANS-104 bundles to attempt to download in parallel
ANS104_UNBUNDLE_WORKERS Number 0, or 1 if filters are set Sets the number of workers used to handle unbundling
ANS104_UNBUNDLE_GET_DATA_TIMEOUT_MS Number 30000 Wall-clock timeout (ms) for the Ans104Parser getData fetch before stream processing begins; 0 disables
ANS104_UNBUNDLE_STREAM_TOTAL_TIMEOUT_MS Number 120000 Wall-clock timeout (ms) for streaming an ANS-104 bundle to its temp file; complements STREAM_STALL_TIMEOUT_MS; 0 disables
ANS104_PARSE_JOB_TIMEOUT_MS Number 600000 Wall-clock timeout (ms) on an in-flight parse job inside an Ans104Parser worker thread. Catches the hang path PR #746 explicitly flagged as "separate follow-up" — worker thread alive but never posts a terminal message, every job it touches stuck indefinitely. On fire: terminate the worker (synthesizes non-zero exit), existing 'exit' handler rejects the job + respawns. Dead bundle goes to bundle-repair-worker. Pair with ans104_parser_job_timeouts_total. Default 10 minutes; tighten if your bundles are smaller. Set to 0 to disable.
ANS104_UNBUNDLE_GET_DATA_WALL_CLOCK_TIMEOUT_MS Number 300000 Promise.race wall-clock cap on Ans104Parser.parseBundle's await getData(). Mirrors PR #744's fix for DataImporter.download: the ANS104_UNBUNDLE_GET_DATA_TIMEOUT_MS AbortController.abort() resolves cleanly in ~99% of cases, but ~0.4% of cascade attempts ignore the abort and leave the await hung indefinitely (130+ minute hangs observed in production). When the cap fires, the parseBundle promise rejects and the worker is freed; the underlying cascade promise is abandoned (sockets/peer slots leak until the cascade unwedges — acceptable since the alternative is a permanent worker stall). Default 5 minutes (20× the abort timer) so it fires only for true zombies; "slow but eventually resolves" cases still win on the abort path. Pair with ans104_parser_get_data_wall_clock_fires_total. Set to 0 to disable.
DATA_ITEM_FLUSH_COUNT_THRESHOLD Number 1000 Sets the number of new data items indexed before flushing to to stable data items
MAX_FLUSH_INTERVAL_SECONDS Number 600 Sets the maximum time interval in seconds before flushing to stable data items
WRITE_ANS104_DATA_ITEM_DB_SIGNATURES Boolean false If true, the data item signatures will be written to the database.
WRITE_TRANSACTION_DB_SIGNATURES Boolean true If true, the transactions signatures will be written to the database.
OPTIMISTIC_TX_INDEXING_ENABLED Boolean false When true, enables POST /ar-io/admin/queue-optimistic-tx (admin-key gated), which lets a trusted poster index a signed L1 transaction optimistically so it is GraphQL-resolvable (with block: null) before it mines. Every tx is signature-verified; the row is inserted with a NULL height and promoted in place when it mines. Default false. The data-verification serving guard (always on) ensures such a tx is never served as verified until its root tx is stable
OPTIMISTIC_TX_CLEANUP_WAIT_SECONDS Number 7200 Grace period (seconds) before a never-mined optimistic (NULL-height) transaction is reclaimed from new_transactions by the stale-new-data GC. Must exceed worst-case mine+index latency. Default 2h matches prior hardcoded behavior
OPTIMISTIC_TX_MAX_BATCH_SIZE Number 100 Maximum number of transaction headers accepted in a single POST /ar-io/admin/queue-optimistic-tx request. Each accepted tx triggers a sequential signature verification, so this bounds the worst-case work one admin request can schedule (the 10 MB body limit alone would permit thousands of small headers). Requests over the cap are rejected with 400
ENABLE_DATA_DB_WAL_CLEANUP Boolean false If true, the data database WAL cleanup worker will be enabled
ENABLE_BACKGROUND_DATA_VERIFICATION Boolean false If true, unverified data will be verified in background
ENABLE_DATA_ITEM_ROOT_TX_SEARCH Boolean true If true, enables searching external APIs (GraphQL/Turbo) to find root transaction when local attributes are incomplete for offset-aware data sources
ENABLE_PASSTHROUGH_WITHOUT_OFFSETS Boolean true If true, allows data retrieval without offset information in offset-aware data sources (falls back to less efficient methods)
MAX_DATA_ITEM_QUEUE_SIZE Number 100000 Sets the maximum number of data items to queue for indexing before skipping indexing new data items
DATA_ITEM_INDEXER_QUEUE_SIZE Number 500000 Hard cap on DataItemIndexer's queue. Non-prioritized items pushed at the cap are dropped (counted in data_items_dropped_total{queue_name="dataItemIndexer"}); recovery is via bundle-repair-worker. Set to 0 to disable the cap.
DATA_ITEM_INDEXER_WORKER_COUNT Number 1 Fastq concurrency for DataItemIndexer.indexDataItem. Default 1 awaits each saveDataItem serially. Raising lets the main thread pipeline JS prep (event emit, metric inc, log) for the next item while the prior saveDataItem is in flight to the SQLite worker thread. Upper-bound on speedup is gated by the SQLite worker (single-threaded per database). Useful when the failed-bundle backlog (large bundles, 50-500 items each) is the dominant load.
QUEUE_DATA_ITEM_MAX_BATCH_SIZE Number 5000 Maximum number of data item headers accepted per POST /ar-io/admin/queue-data-item request. Larger batches are rejected with 400 (instead of a crude body-size 413) so the client splits the request. The body limit is also raised to 10 MB to admit legitimate bundle-sized batches.
QUEUE_DATA_ITEM_BACKPRESSURE_DEPTH Number 100000 POST /ar-io/admin/queue-data-item returns 503 + Retry-After when the DataItemIndexer queue depth exceeds this. Admin POSTs are prioritized (they bypass the drop cap via unshift), so this sheds load back to the client under sustained overload instead of ballooning the queue / starving the regular unbundling pipeline. Measured basis: the single bundles-writer saveDataItem is p50 ~2 ms but fat-tailed (p99 ~2 s); 100k sits ~10× above legitimate burst peaks and bounds the prioritized queue to ~150 MB.
ANS104_DATA_INDEXER_QUEUE_SIZE Number 500000 Hard cap on Ans104DataIndexer's queue. Items pushed at the cap are dropped (counted in data_items_dropped_total{queue_name="ans104DataIndexer"}); recovery is via bundle-repair-worker. Set to 0 to disable the cap.
GRAPHQL_RESOLVER_DEADLINE_MS Number 12000 Server-side hard deadline (ms) for GraphQL resolvers. Composed with the express request signal — when the client disconnects OR this deadline elapses, downstream attribute fetches and arweaveClient requests are cancelled and zombie work doesn't accumulate on trustedNodeRequestQueue. Set to 0 to disable the deadline (only the client-disconnect signal will fire).
BUNDLE_DATA_ITEM_DRAIN_BATCH Number 100 Number of ANS104_DATA_ITEM_MATCHED items dispatched into the data-item indexers per setImmediate drain cycle. Buffering and batching the matched-item firehose from the unbundler worker thread prevents JS-thread starvation when large bundles produce thousands of cross-thread messages per second. Larger values reduce per-batch overhead at the cost of fewer event-loop turns; smaller values yield to I/O more often. Buffer depth is exposed as queue_length{queue_name="matchedItemBuffer"}. Must be a positive integer.
ARNS_ROOT_HOST String undefined Domain name(s) for ArNS host. Supports comma-separated values for multiple hosts (e.g., arweave.dev,g8way.io). The first host is the "primary" used for gateway identity. ArNS subdomains, apex content, and sandbox redirects work on all configured hosts.
SANDBOX_PROTOCOL String undefined Protocol (http/https) for ArNS sandbox redirects and x402 payment resource URLs. Set to 'https' when behind a reverse proxy/CDN. Used when ARNS_ROOT_HOST is set.
START_WRITERS Boolean true If true, start indexing blocks, tx, ANS104 bundles
RUN_OBSERVER Boolean true If true, runs the Observer alongside the gateway to generate Network compliance reports
MIN_RELEASE_NUMBER String 0 Sets the minimum Gateway release version to check while doing a gateway version assessment
AR_IO_NODE_RELEASE String 0 Sets the current ar.io node version to be set on X-AR-IO-Node-Release header on requests to the reference gateway
OBSERVER_WALLET String "" The public wallet address of the wallet being used to sign report upload transactions and contract interactions for Observer
CHUNKS_DATA_PATH String "./data/chunks" Sets the location for chunked data to be saved. If omitted, chunked data will be stored in the data directory
CONTIGUOUS_DATA_PATH String "./data/contiguous" Sets the location for contiguous data to be saved. If omitted, contiguous data will be stored in the data directory
HEADERS_DATA_PATH String "./data/headers" Sets the location for header data to be saved. If omitted, header data will be stored in the data directory
SQLITE_DATA_PATH String "./data/sqlite" Sets the location for sqlite indexed data to be saved. If omitted, sqlite data will be stored in the data directory
CORE_SQLITE_READ_WORKER_COUNT Number 1 Number of reader worker threads for the core SQLite pool. Reads are serialized within a single worker thread, so raising this adds read concurrency for the core DB (WAL supports concurrent readers). Writers stay single (SQLite allows one writer). Watch standalone_sqlite_method_queue_wait_seconds vs ..._service_seconds to judge whether raising it helps.
DATA_SQLITE_READ_WORKER_COUNT Number 2 Number of reader worker threads for the data SQLite pool.
SQLITE_SLOW_QUERY_LOG_THRESHOLD_MS Number 1000 Operations whose total time (queue wait + service) meets or exceeds this threshold are logged at warn with a queue/service breakdown and a bounded argument summary, to identify which method is responsible during a jam.
DUCKDB_DATA_PATH String "./data/duckdb" Sets the location for duckdb data to be saved. If omitted, duckdb data will be stored in the data directory
TEMP_DATA_PATH String "./data/tmp" Sets the location for temporary data to be saved. If omitted, temporary data will be stored in the data directory
OBSERVER_STATE_PATH String "./data/observer" Sets the location for Observer state data to be saved. Used to persist continuous observation state across restarts
LMDB_DATA_PATH String "./data/LMDB" Sets the location for LMDB data to be saved. If omitted, LMDB data will be stored in the data directory
SECRETS_PATH String "./secrets" Sets the location for sensitive configuration files (e.g., CDP API keys). Mounted read-only in container for security
CHAIN_CACHE_TYPE String "redis" Sets the method for caching chain data, defaults redis if gateway is started with docker-compose, otherwise defaults to LMDB
REDIS_CACHE_URL String (URL) "redis://localhost:6379" URL of Redis database to be used for caching
REDIS_CACHE_TTL_SECONDS Number 28800 TTL value for Redis cache, defaults to 8 hours (28800 seconds)
ENABLE_FS_HEADER_CACHE_CLEANUP Boolean true if starting with docker, otherwise false If true, periodically deletes cached header data
ENABLE_CHUNK_SYMLINK_CLEANUP Boolean true If true, periodically removes dead symlinks from chunk cache directories (symlinks pointing to expired cached data)
CHUNK_SYMLINK_CLEANUP_INTERVAL Number 86400 Interval in seconds between dead symlink cleanup runs (default: 24 hours)
NODE_JS_MAX_OLD_SPACE_SIZE Number 2048 or 8192, depending on number of workers Sets the memory limit, in Megabytes, for NodeJs. Default value is 2048 if using less than 2 unbundle workers, otherwise 8192
SUBMIT_CONTRACT_INTERACTIONS Boolean true If true, Observer will submit its generated reports to the ar.io Network by signing the save_observations ix with OBSERVER_KEYPAIR_PATH ?? SOLANA_KEYPAIR_PATH. Pre-flight-skips when the observer isn't prescribed, already submitted, or past the observation window (no wasted tx fees on bouncing simulations).
REDIS_MAX_MEMORY String 256mb Sets the max memory allocated to Redis
REDIS_DATA_PATH String "./data/redis" Sets the location for Redis data persistence files (dump.rdb, appendonly.aof). Only used if persistence is enabled via EXTRA_REDIS_FLAGS
EXTRA_REDIS_FLAGS String --save "" --appendonly no Additional CLI flags passed to Redis server. Default disables persistence for performance. Set to "--save 300 10 --appendonly yes --appendfsync everysec" to enable hybrid persistence (recommended for x402 paid tokens)
ARWEAVE_PEER_CHUNK_GET_MAX_PEER_ATTEMPT_COUNT Number 5 Maximum number of Arweave peers to try sequentially when fetching a chunk via GET before giving up
ARWEAVE_PEER_CHUNK_GET_PEER_SELECTION_COUNT Number 10 Number of candidate peers to select from each pool (bucket and general) for chunk GET requests
ARWEAVE_CHUNK_GET_GEOMETRY_TIMEOUT_MS Number 5000 Per-request timeout (ms) for TX geometry resolution (getTxOffset/getTxField) used during chunk retrieval
ARWEAVE_CHUNK_GET_GEOMETRY_RETRY_COUNT Number 2 Number of retries for TX geometry resolution requests during chunk retrieval
LEGACY_AWS_S3_CHUNK_DATA_BUCKET String undefined S3 bucket name for legacy chunk data source. Required when 'legacy-s3' is in CHUNK_DATA_RETRIEVAL_ORDER
LEGACY_AWS_S3_CHUNK_DATA_PREFIX String undefined Optional prefix for chunk data in the legacy S3 bucket. If omitted, the root of the bucket will be /{dataRoot}/{relativeOffset}
LEGACY_AWS_S3_ACCESS_KEY_ID String undefined AWS access key for legacy S3 chunk bucket (optional - falls back to AWS_ACCESS_KEY_ID if not set)
LEGACY_AWS_S3_SECRET_ACCESS_KEY String undefined AWS secret key for legacy S3 chunk bucket (optional - falls back to AWS_SECRET_ACCESS_KEY if not set)
LEGACY_AWS_S3_REGION String undefined AWS region for legacy S3 chunk bucket. Required if using separate credentials (LEGACY_AWS_S3_ACCESS_KEY_ID)
LEGACY_AWS_S3_ENDPOINT String undefined Custom endpoint for legacy S3 chunk bucket (optional - for S3-compatible services)
LEGACY_PSQL_CONNECTION_STRING String undefined PostgreSQL connection URL for legacy chunk metadata source (format: postgresql://user:pass@host:port/database). Required when 'legacy-psql' is in CHUNK_METADATA_RETRIEVAL_ORDER
LEGACY_PSQL_PASSWORD_FILE String undefined File path containing PostgreSQL password (alternative to including password in connection string for better security)
LEGACY_PSQL_SSL_REJECT_UNAUTHORIZED Boolean true If false, allows connections to PostgreSQL servers with self-signed certificates (common workaround for cloud providers)
LEGACY_PSQL_MAX_CONNECTIONS Number 10 Maximum number of connections in the PostgreSQL connection pool
LEGACY_PSQL_IDLE_TIMEOUT_SECONDS Number 30 Time in seconds before idle connections are closed in the pool
LEGACY_PSQL_CONNECT_TIMEOUT_SECONDS Number 10 Maximum time in seconds to wait when establishing a new connection
LEGACY_PSQL_MAX_LIFETIME_SECONDS Number 1800 Maximum lifetime in seconds for a connection before it's rotated (30 minutes default)
LEGACY_PSQL_STATEMENT_TIMEOUT_MS Number 5000 Server-side query timeout in milliseconds. Prevents queries from running forever. Critical for preventing system hangs
LEGACY_PSQL_IDLE_IN_TRANSACTION_TIMEOUT_MS Number 10000 Server-side timeout in milliseconds for idle transactions. Cleans up stuck transactions that hold locks
WEBHOOK_TARGET_SERVERS String undefined Target servers for webhooks
WEBHOOK_INDEX_FILTER String {"never": true} Only emit webhooks for transactions and data items compliant with this filter
WEBHOOK_BLOCK_FILTER String {"never": true} Only emit webhooks for blocks compliant with this filter
CONTIGUOUS_DATA_CACHE_CLEANUP_THRESHOLD Number undefined Sets the age threshold in seconds; files older than this are candidates for contiguous data cache cleanup
CONTIGUOUS_DATA_CACHE_CLEANUP_INITIAL_DELAY Number CONTIGUOUS_DATA_CACHE_CLEANUP_THRESHOLD Delay in seconds before the first contiguous data cache cleanup runs (warm-up for the metadata cache). Defaults to the cleanup threshold when unset. Not persisted, so it restarts on every process restart
UV_THREADPOOL_SIZE Number 4 (Node.js default) Node.js libuv threadpool size for filesystem/DNS/crypto operations. Raising it (e.g. to 16) reduces contention for the filesystem cache-cleanup walk under heavy load. Process-wide; affects all workers
ENABLE_MEMPOOL_WATCHER Boolean false If true, the observer will start indexing pending tx from the mempool
MEMPOOL_POLLING_INTERVAL_MS Number 30000 Sets the mempool polling interval in milliseconds
TAG_SELECTIVITY String Refer to config.ts A JSON map of tag names to selectivity weights used to order SQLite tag joins
GET_DEBUG_INFO_CACHE_TTL_MS Number 300000 Cache TTL (ms) for the /ar-io/admin/debug SQLite snapshot. Each call runs unfiltered COUNT(*) scans on the SQLite worker, so caching avoids monopolizing the debug worker when the endpoint is polled frequently. Set to 0 to disable caching.
MAX_EXPECTED_DATA_ITEM_INDEXING_INTERVAL_SECONDS Number undefined Sets the max expected data item indexing interval in seconds
AR_IO_SQLITE_BACKUP_S3_BUCKET_NAME String "" S3-compatible bucket name, used by the Litestream backup service
AR_IO_SQLITE_BACKUP_S3_BUCKET_REGION String "" S3-compatible bucket region, used by the Litestream backup service
AR_IO_SQLITE_BACKUP_S3_BUCKET_ACCESS_KEY String "" S3-compatible bucket access_key credential, used by Litestream backup service, omit if using resource-based IAM role
AR_IO_SQLITE_BACKUP_S3_BUCKET_SECRET_KEY String "" S3-compatible bucket access_secret_key credential, used by Litestream backup service, omit if using resource-based IAM role
AR_IO_SQLITE_BACKUP_S3_BUCKET_PREFIX String "" A prepended prefix for the S3 bucket where SQLite backups are stored.
ARNS_MAX_CONCURRENT_RESOLUTIONS Number Number of ArNS resolvers Maximum number of concurrent resolutions allowed for ARNs.
AWS_ACCESS_KEY_ID String undefined AWS access key ID for accessing AWS services
AWS_SECRET_ACCESS_KEY String undefined AWS secret access key for accessing AWS services
AWS_REGION String undefined AWS region where the resources are located
AWS_ENDPOINT String undefined Custom endpoint for AWS services
TURBO_AWS_REGION String undefined AWS region for the Turbo S3 data source. A dedicated AWS client is created for Turbo only when both TURBO_AWS_REGION and TURBO_AWS_ENDPOINT are set; otherwise the default AWS client (AWS_REGION/AWS_ENDPOINT) is reused.
TURBO_AWS_ENDPOINT String undefined Custom endpoint for the Turbo S3 data source. A dedicated AWS client is created for Turbo only when both TURBO_AWS_REGION and TURBO_AWS_ENDPOINT are set; otherwise the default AWS client (AWS_REGION/AWS_ENDPOINT) is reused.
TURBO_AWS_ACCESS_KEY_ID String undefined Access key ID for the dedicated Turbo AWS client. Falls back to AWS_ACCESS_KEY_ID when unset. Only used when the dedicated Turbo client is created (TURBO_AWS_REGION + TURBO_AWS_ENDPOINT).
TURBO_AWS_SECRET_ACCESS_KEY String undefined Secret access key for the dedicated Turbo AWS client. Falls back to AWS_SECRET_ACCESS_KEY when unset. Only used when the dedicated Turbo client is created (TURBO_AWS_REGION + TURBO_AWS_ENDPOINT).
TURBO_AWS_SESSION_TOKEN String undefined Session token for the dedicated Turbo AWS client. Falls back to AWS_SESSION_TOKEN when unset. Only used when the dedicated Turbo client is created (TURBO_AWS_REGION + TURBO_AWS_ENDPOINT).
AWS_S3_CONTIGUOUS_DATA_BUCKET String undefined AWS S3 bucket name used for storing data
AWS_S3_CONTIGUOUS_DATA_PREFIX String undefined Prefix for the S3 bucket to organize data
CHUNK_POST_MIN_SUCCESS_COUNT String "3" Minimum count of 200 responses for of a given chunk to be considered properly seeded
CHUNK_POST_MIN_PREFERRED_SUCCESS_COUNT String "2" Minimum count of 200 responses from preferred (tip) nodes for a chunk to be considered properly seeded. Set to 0 to disable preferred node requirement
CHUNK_POST_MAX_CONSECUTIVE_FAILURES String "5" Maximum consecutive 4xx responses before stopping chunk broadcast. Only applies when no peers have accepted the chunk. Set to 0 to disable early termination
ARWEAVE_POST_DRY_RUN Boolean false If true, simulates transaction header and chunk submission without posting to Arweave. POST /tx and POST /chunk return 200 OK as if successful; only the final network broadcast is skipped. Works on both port 3000 (Envoy) and port 4000 (direct). By default, transaction signatures and chunk merkle proofs are still validated before success. When disabled, Envoy routes these requests to trusted Arweave nodes instead; GET /tx is always proxied to the trusted node regardless of this setting.
ARWEAVE_POST_DRY_RUN_SKIP_VALIDATION Boolean false If true (and ARWEAVE_POST_DRY_RUN is enabled), skips transaction signature verification and chunk merkle proof validation for faster testing.
ARWEAVE_PEER_DNS_RECORDS String "peers.arweave.xyz" Comma-separated DNS hostnames to resolve for Arweave peer discovery. Set to empty string to disable and fall back to static TRUSTED_NODE_HOST/FALLBACK_NODE_HOST
ARWEAVE_PEER_DNS_PORT Number 1984 Port to use when connecting to discovered Arweave peers
ARWEAVE_NODE_MAX_HEIGHT_LAG Number 5 Maximum number of blocks a peer can be behind the reference height before being excluded
ARWEAVE_NODE_MAX_HEIGHT_LEAD Number 5 Maximum number of blocks a peer can be ahead of the reference height before being excluded
ARWEAVE_HEIGHT_MIN_CONSENSUS_COUNT Number 2 Minimum number of peers that must agree on a height (within MAX_HEIGHT_LAG) for consensus
ARWEAVE_NODE_FULL_SYNC_THRESHOLD Number 100 Maximum gap between blocks and height+1 for a peer to be classified as a full node
ARWEAVE_PEER_HEALTH_CHECK_INTERVAL_MS Number 30000 Interval in milliseconds between peer health check cycles
ENABLE_ARWEAVE_PEER_EDS Boolean true Enable Envoy EDS-based dynamic peer routing (Docker/Envoy only). Set to false to use static trusted node clusters
CHUNK_GET_BASE64_SIZE_BYTES Number 368640 Assumed size in bytes for base64-encoded chunk responses, used for x402 payment and rate limiting calculations (default: 360 KiB)
CHUNK_REQUEST_CONCURRENCY Number 50 Maximum number of concurrent chunk fetch requests across all HTTP requests. Limits load on chunk backends under high concurrency
CHUNK_FIRST_DATA_TIMEOUT_MS Number 10000 Timeout in milliseconds for receiving the first chunk when assembling data from chunks. If exceeded, the request fails and falls through to alternative data sources. 0 disables
CHUNK_SERVE_DEADLINE_MS Number 12000 Wall-clock deadline (ms) for serving a single chunk request (/chunk/:offset, /chunk/:offset/data). The cascade's per-source timeouts are additive with no overall ceiling, so this bounds the whole serve below the upstream proxy cut (~15s), returning promptly (a 404, matching the timeout-as-not-found contract) and releasing the connection. Reason is exposed via the chunk_serve_deadline_exceeded_total metric. Must stay below the proxy read timeout. 0 disables
CHUNK_REBROADCAST_SOURCES String "" Comma-separated list of sources that trigger chunk rebroadcasting. Valid: legacy-s3, ar-io-network, arweave-network. Empty disables rebroadcasting
CHUNK_REBROADCAST_RATE_LIMIT_TOKENS Number 60 Rate limit tokens per interval for chunk rebroadcasting
CHUNK_REBROADCAST_RATE_LIMIT_INTERVAL String "minute" Rate limit interval for chunk rebroadcasting: second, minute, hour, day
CHUNK_REBROADCAST_MAX_CONCURRENT Number 5 Maximum concurrent chunk rebroadcast operations
CHUNK_REBROADCAST_DEDUP_TTL_SECONDS Number 3600 Deduplication cache TTL in seconds (don't rebroadcast same chunk within this period)
CHUNK_REBROADCAST_MIN_SUCCESS_COUNT Number 1 Minimum broadcast success count for chunk to be added to dedup cache
CHUNK_INGEST_CACHE_ENABLED Boolean false When true, POST /chunk verifies each posted chunk against its data_root and write-through caches it locally (optimistic ingest caching). Default false = relay only (current behavior). The cache write is fire-and-forget and never delays the broadcast response
CHUNK_INGEST_CACHE_ALLOWLIST String "" Comma-separated IPs/CIDRs whose posted chunks are eligible for optimistic caching. Empty = open ingest (any merkle-valid chunk is cached). When set, only these posters earn caching; others are still relayed. Gates caching, not posting. Only trustworthy if your edge proxy sanitizes inbound X-Forwarded-For/X-Real-IP — otherwise the source IP is client-spoofable. Moot under the default (open ingest, empty allowlist) since every merkle-valid chunk is cached regardless. Worst case under a closed allowlist: a spoofed-but-cryptographically-valid chunk earns the 24h allowlisted TTL instead of 6h, still bounded by GC + the disk cap
CHUNK_INGEST_CONFIRMATION_TIMEOUT_SECONDS Number 21600 GC leash (seconds) for open-ingest chunks whose data_root never confirms on-chain. Must exceed worst-case mine+index latency
CHUNK_INGEST_ALLOWLIST_CONFIRMATION_TIMEOUT_SECONDS Number 86400 Longer GC leash (seconds) for allowlisted-poster chunks
CHUNK_INGEST_MAX_PENDING_BYTES Number 26843545600 (25 GiB) Hard cap on pending (unconfirmed) ingest-cached bytes. Enforced synchronously at ingest (further caching is rejected with chunk_ingest_cache_total{result="skipped_disk_full"} once the pending total would exceed it — a burst can't overrun the disk between GC sweeps) AND as a GC backstop (oldest-pending evicted first). Non-zero by default so enabling caching can't silently fill the disk. Set 0 to disable
CHUNK_INGEST_GC_INTERVAL_MS Number 300000 Interval (ms) between chunk-ingest GC sweeps
CHUNK_INGEST_GC_BATCH_SIZE Number 1000 Maximum placements evicted per GC sweep pass
BUNDLE_REPAIR_RETRY_INTERVAL_SECONDS String "300" Interval in seconds for retrying bundles
BUNDLE_REPAIR_RETRY_BATCH_SIZE String "5000" Batch size for retrying bundles
BUNDLE_REPAIR_MAX_RETRY_ATTEMPTS String "50" Max times the repair loop retries a single bundle before dropping it from the retry pool. Bounds re-walks of bundles that can never finish unbundling (the future dead-letter-queue hand-off boundary)
BUNDLE_REPAIR_RETRY_COOLDOWN_SECONDS String "900" Minimum seconds between retries of the same bundle, keyed on last_retried_at so it holds even when the unbundler is saturated (unlike the last_queued_at reprocess cooldown)
APEX_TX_ID String undefined If set, serves this transaction ID's data at the root path (/). Cache-Control on the response is bounded by CACHE_APEX_MAX_AGE with must-revalidate so apex rotations propagate to upstream caches predictably
APEX_ARNS_NAME String undefined If set, resolves and serves this ArNS name's content at the root path (/). Supports comma-separated values positionally mapped to ARNS_ROOT_HOST entries (e.g., turbo,ar-io for two hosts). A single value applies to all hosts.
ENABLE_RATE_LIMITER Boolean false If true, enables rate limiting enforcement (returns 429 when limits exceeded). When false, limits are tracked but not enforced
RATE_LIMITER_TYPE String "redis" (docker), "memory" (standalone) Sets the rate limiter implementation type. Use "memory" for local development/single-node, "redis" for production multi-node deployments
RATE_LIMITER_REDIS_ENDPOINT String "redis://redis:6379" Redis endpoint URL for rate limiter (only used when RATE_LIMITER_TYPE is "redis")
RATE_LIMITER_REDIS_USE_TLS Boolean false Whether to use TLS when connecting to Redis for rate limiting
RATE_LIMITER_REDIS_USE_CLUSTER Boolean false Whether to use Redis cluster mode for rate limiting
RATE_LIMITER_RESOURCE_TOKENS_PER_BUCKET Number 1000000 Maximum tokens in the resource bucket (1 token = 1 KiB, where 1 KiB = 1,024 bytes). Default allows ~1 GiB per resource
RATE_LIMITER_RESOURCE_REFILL_PER_SEC Number 100 Tokens to refill per second for resource bucket. Default allows ~100 KiB/s sustained throughput per resource
RATE_LIMITER_IP_TOKENS_PER_BUCKET Number 100000 Maximum tokens in the IP bucket (1 token = 1 KiB, where 1 KiB = 1,024 bytes). Default allows ~100 MiB per IP
RATE_LIMITER_IP_REFILL_PER_SEC Number 20 Tokens to refill per second for IP bucket. Default allows ~20 KiB/s sustained throughput per IP
RATE_LIMITER_IPS_AND_CIDRS_ALLOWLIST String "" Comma-separated list of IPs and CIDR ranges to exempt from rate limiting (e.g., "192.168.1.0/24,10.0.0.1")
RATE_LIMITER_ARNS_ALLOWLIST String "" Comma-separated list of ArNS names to exempt from rate limiting and payment verification (e.g., "my-free-app,public-docs")
CACHE_PRIVATE_SIZE_THRESHOLD Number 104857600 (100 MB) Response size threshold in bytes above which Cache-Control uses 'private' directive. Helps CDNs respect rate limiting and x402 payment requirements for large responses
CACHE_PRIVATE_CONTENT_TYPES String "" Comma-separated list of content types that should use 'private' Cache-Control directive. Supports wildcards (e.g., "image/,video/"). Helps CDNs respect rate limiting and x402 payments for specific content types
CACHE_DEFAULT_MAX_AGE Number 30 Default Cache-Control max-age (seconds) applied by middleware when no handler sets its own header
CACHE_STABLE_MAX_AGE Number 2592000 (30 days) Cache-Control max-age (seconds) for stable (deeply confirmed) data. Warning: values larger than your longest expected ANT TTL amplify upstream-cache poisoning when an ArNS record or manifest is updated — see note below
CACHE_UNSTABLE_TRUSTED_MAX_AGE Number 43200 (12 hours) Cache-Control max-age (seconds) for unstable data from a trusted source. Warning: see CACHE_STABLE_MAX_AGE note below
CACHE_UNSTABLE_MAX_AGE Number 7200 (2 hours) Cache-Control max-age (seconds) for unstable data from an untrusted source. Warning: see CACHE_STABLE_MAX_AGE note below
CACHE_NOT_FOUND_MAX_AGE Number 60 (1 minute) Cache-Control max-age (seconds) for not-found responses, manifest fallback responses, and ArNS custom-404 responses (ARNS_NOT_FOUND_TX_ID / ARNS_NOT_FOUND_ARNS_NAME)
CACHE_APEX_MAX_AGE Number 3600 (1 hour) Cache-Control max-age (seconds) for APEX_TX_ID responses. Bounds the upstream-cache poisoning window when operators rotate APEX_TX_ID (lower than the data-layer ladder so a rotation propagates within the configured window)
ENABLE_X_402_USDC_DATA_EGRESS Boolean false If true, enables x402 USDC payment verification and settlement for data egress
X_402_USDC_NETWORK String "base-sepolia" USDC network to use ("base" for mainnet, "base-sepolia" for testnet)
X_402_USDC_WALLET_ADDRESS String undefined Ethereum wallet address (0x...) to receive USDC payments
X_402_USDC_FACILITATOR_URL String "https://x402.org/facilitator" x402 facilitator endpoint URL. Default is Coinbase's testnet facilitator. Note: When CDP API keys are provided (CDP_API_KEY_ID and CDP_API_KEY_SECRET), the Coinbase facilitator is automatically used, overriding this setting
X_402_USDC_DATA_EGRESS_MIN_PRICE Number 0.001 Minimum price in USDC for data egress (used when content length is unknown)
X_402_USDC_DATA_EGRESS_MAX_PRICE Number 1.00 Maximum price in USDC for data egress (caps per-request cost)
X_402_USDC_PER_BYTE_PRICE Number 0.0000000001 Price in USDC per byte of data egress (default: $0.10 per GB)
X_402_RATE_LIMIT_CAPACITY_MULTIPLIER Number 10 Capacity multiplier for paid tier rate limits (e.g., 10x = paid users get 10x bucket capacity)
X_402_USDC_SETTLE_TIMEOUT_MS Number 5000 Timeout in milliseconds for payment settlement operations
X_402_CDP_CLIENT_KEY String undefined Coinbase Developer Platform client API key (public, safe to expose in browser paywall)
X_402_APP_NAME String "AR.IO Gateway" Application name displayed in payment UI
X_402_APP_LOGO String undefined URL to application logo displayed in payment UI
X_402_SESSION_TOKEN_ENDPOINT String undefined Custom session token endpoint URL for payment authentication
CDP_API_KEY_ID String undefined SENSITIVE SECRET: Coinbase Developer Platform secret API key ID (for Onramp session token generation and Coinbase facilitator integration). When provided with CDP_API_KEY_SECRET, automatically enables Coinbase facilitator. Must not be logged or exposed. Store in secure secrets manager and restrict access (least privilege).
CDP_API_KEY_SECRET String undefined SENSITIVE SECRET: Coinbase Developer Platform secret API key secret (for Onramp session token generation and Coinbase facilitator integration). When provided with CDP_API_KEY_ID, automatically enables Coinbase facilitator. Must not be logged or exposed. Store in secure secrets manager and restrict access (least privilege).
CDP_API_KEY_SECRET_FILE String undefined SENSITIVE SECRET: File path containing CDP secret API key secret. Takes precedence over CDP_API_KEY_SECRET if defined. Must not be logged or exposed.
OTEL_SERVICE_NAME String "ar-io-node" Service name for OpenTelemetry traces. Used to identify this service in telemetry backends
OTEL_TRACING_SAMPLING_RATE_DENOMINATOR Number 1 Head-based sampling rate denominator (1/N spans sampled). Note: In docker-compose, tail sampling via OTEL Collector is used instead for intelligent sampling
OTEL_EXPORTER_OTLP_ENDPOINT String "http://otel-collector:4318" (docker) OTLP endpoint for traces/logs. In docker-compose, defaults to collector for tail sampling. For non-docker deployments, set to your telemetry backend URL
OTEL_EXPORTER_OTLP_HEADERS String undefined Authentication headers for OTLP exporter (e.g., "x-honeycomb-team=your-api-key"). For non-docker deployments only. Use OTEL_COLLECTOR_DESTINATION_HEADERS in docker
OTEL_EXPORTER_OTLP_HEADERS_FILE String undefined File path containing OTLP exporter headers. For non-docker deployments only. Use OTEL_COLLECTOR_DESTINATION_HEADERS_FILE in docker
OTEL_FILE_EXPORT_ENABLED Boolean false (true with yarn service:start) Enable file-based export of OTEL spans for development/debugging. Spans written to OTEL_FILE_EXPORT_PATH
OTEL_FILE_EXPORT_PATH String "logs/otel-spans.jsonl" Path for file-based OTEL span export (JSONL format). Only used when OTEL_FILE_EXPORT_ENABLED is true
OTEL_BATCH_LOG_PROCESSOR_SCHEDULED_DELAY_MS Number 2000 Delay in milliseconds before batch log export
OTEL_BATCH_LOG_PROCESSOR_MAX_EXPORT_BATCH_SIZE Number 10000 Maximum number of log records to export in a single batch
OTEL_COLLECTOR_IMAGE_TAG String "0.119.0" Docker image tag for OpenTelemetry Collector. Only applies to docker-compose deployments
OTEL_COLLECTOR_DESTINATION_ENDPOINT String undefined Final telemetry destination URL where OTEL Collector forwards sampled traces. Examples: Honeycomb (https://api.honeycomb.io), Grafana Cloud, Datadog, New Relic, Elastic APM
OTEL_COLLECTOR_HONEYCOMB_API_KEY String undefined Honeycomb API key for authentication (x-honeycomb-team header). Configure ONE backend API key
OTEL_COLLECTOR_GRAFANA_CLOUD_API_KEY String undefined Grafana Cloud API key for authentication (Authorization: Basic header). Base64 encoded instance_id:api_key. Configure ONE backend API key
OTEL_COLLECTOR_DATADOG_API_KEY String undefined Datadog API key for authentication (DD-API-KEY header). Configure ONE backend API key
OTEL_COLLECTOR_NEW_RELIC_API_KEY String undefined New Relic license key for authentication (api-key header). Configure ONE backend API key
OTEL_COLLECTOR_ELASTIC_API_KEY String undefined Elastic APM secret token for authentication (Authorization: Bearer header). Configure ONE backend API key
OTEL_TAIL_SAMPLING_SUCCESS_RATE Number 1 Percentage (1-100) of successful/fast/unpaid traces to sample. Default: 1% provides baseline metrics with 80-95% cost reduction
OTEL_TAIL_SAMPLING_SLOW_THRESHOLD_MS Number 2000 Latency threshold in milliseconds. Requests exceeding this duration are eligible for sampling (rate controlled by OTEL_TAIL_SAMPLING_SLOW_RATE)
OTEL_TAIL_SAMPLING_ERROR_RATE Number 100 Percentage (1-100) of error traces (5xx status codes) to sample. Default: 100% captures all errors for debugging. Lower values reduce costs but may miss issues
OTEL_TAIL_SAMPLING_SLOW_RATE Number 100 Percentage (1-100) of slow request traces (exceeding OTEL_TAIL_SAMPLING_SLOW_THRESHOLD_MS) to sample. Default: 100% captures all slow requests for performance analysis
OTEL_TAIL_SAMPLING_PAID_TRAFFIC_RATE Number 100 Percentage (1-100) of paid traffic traces (x402 verified payments) to sample. Default: 100% for billing/compliance. Reduce only if paid traffic is high volume
OTEL_TAIL_SAMPLING_PAID_TOKENS_RATE Number 100 Percentage (1-100) of paid rate limit token usage traces to sample. Default: 100% for billing/compliance. Reduce only if paid token usage is high volume
OTEL_TAIL_SAMPLING_NESTED_BUNDLE_RATE Number 5 Percentage (1-100) of nested bundle data item retrieval traces to sample. Captures TurboDynamoDB requests involving parent offsets for visibility into nested bundle paths
OTEL_TAIL_SAMPLING_OFFSET_OVERWRITE_RATE Number 10 Percentage (1-100) of offset overwrite risk traces to sample. Captures traces where both DynamoDB offsets AND raw data paths executed, the scenario that triggered the Release 59 offset bug

Security Note: Variables marked as SENSITIVE SECRET (such as CDP_API_KEY_ID, CDP_API_KEY_SECRET, and CDP_API_KEY_SECRET_FILE) contain confidential credentials that must never be printed to logs, exposed in error messages, or included in any diagnostic output. Always mask or omit these values in logs, store them in a secure secrets manager, and restrict access using the principle of least privilege.

Solana Backend

The gateway reads protocol state from Solana on-chain programs. The OnDemandArNSResolver routes ANT lookups through @ar.io/sdk/solana::SolanaANTReadable.

Variable Type Default Description
SOLANA_RPC_URL String "https://api.mainnet-beta.solana.com" Solana RPC endpoint. Public mainnet-beta is rate-limited; use a dedicated provider (Helius, QuickNode, Triton) for production. For devnet: https://api.devnet.solana.com.
ARIO_CORE_PROGRAM_ID String undefined (SDK falls back to mainnet ID) Override the ario-core program ID. Required for devnet / localnet (current staging-devnet value: 5iU1xZ4ocy7e96kcoEipvnxs8anSoq6JGznq6iS4svKn). Source of truth: DEVNET_PROGRAM_IDS in @ar.io/sdk src/solana/clusters.ts / devnet-config.json in ar-io/solana-ar-io.
ARIO_GAR_PROGRAM_ID String undefined Override the ario-gar program ID. Staging devnet: KpZMWCMeTiyH3dW3ZH9go4TwAg5vxgUXHuFVY8JbLFS.
ARIO_ARNS_PROGRAM_ID String undefined Override the ario-arns program ID. Staging devnet: 2YjqZEYTTKLD3qg4NvYwpo2wVcDQCj2p4iD2WTYymfEC.
ARIO_ANT_PROGRAM_ID String undefined Override the ario-ant program ID. Staging devnet: 9SuQQKKW1mEvdRhXrdpHR5PqBMurY3wh7vbEkxzEsngu.

Solana Wallet Identities

AR.IO gateways have four protocol-level roles that can be wired to one or more wallets. The on-chain roles (operator, observer) must be Solana keypairs; the off-chain upload role can use any of three chains that Turbo accepts. Resolution lives in ar-io-observer/src/wallet-config.ts (covered by wallet-config.test.ts).

operator (= cranker)         = SOLANA_KEYPAIR_PATH                            (required)
observer (save_observations) = OBSERVER_KEYPAIR_PATH      ?? SOLANA_KEYPAIR_PATH
upload (precedence)          = Arweave > Ethereum > Solana(explicit) > Solana(fallback)

Upload precedence (in order, first match wins):

  1. ARWEAVE_UPLOAD_KEY_FILE (file) → ArweaveSigner
  2. ARWEAVE_UPLOAD_JWK (inline env) → ArweaveSigner
  3. ETHEREUM_UPLOAD_PRIVATE_KEY_FILE (file) → EthereumSigner
  4. ETHEREUM_UPLOAD_PRIVATE_KEY (inline env) → EthereumSigner
  5. SOLANA_UPLOAD_KEYPAIR_PATH (explicit) → SolanaSigner
  6. Fallback: OBSERVER_KEYPAIR_PATH ?? SOLANA_KEYPAIR_PATHSolanaSigner

Conflict policy: setting envs from more than one chain group at once (e.g. ARWEAVE_UPLOAD_* plus ETHEREUM_UPLOAD_*) raises a startup error listing every conflicting env. Pick exactly one upload chain.

Sniff validators produce friendly errors when material is dropped into the wrong slot — e.g. a Solana keypair JSON at ARWEAVE_UPLOAD_KEY_FILE, an Arweave JWK at SOLANA_UPLOAD_KEYPAIR_PATH, a 32-byte hex string at a keypair path, etc. Each error names the offending env and suggests the correct one.

Supported configurations (each tested in wallet-config.test.ts):

# Operator Observer Upload Required envs
1 Solana =op =op (Solana bundle) SOLANA_KEYPAIR_PATH
2 Solana =op Arweave JWK SOLANA_KEYPAIR_PATH + ARWEAVE_UPLOAD_KEY_FILE
3 Solana A Solana B Solana C (bundle) SOLANA_KEYPAIR_PATH + OBSERVER_KEYPAIR_PATH + SOLANA_UPLOAD_KEYPAIR_PATH
4 Solana A Solana B Arweave JWK SOLANA_KEYPAIR_PATH + OBSERVER_KEYPAIR_PATH + ARWEAVE_UPLOAD_KEY_FILE
5 Solana A Solana B Ethereum SOLANA_KEYPAIR_PATH + OBSERVER_KEYPAIR_PATH + ETHEREUM_UPLOAD_PRIVATE_KEY_FILE
Variable Type Default Description
SOLANA_KEYPAIR_PATH Path - Operator + cranker keypair. Required in Solana mode. Signs join_network, update_gateway_settings, and every permissionless cranker ix.
OBSERVER_KEYPAIR_PATH Path - Optional separate observer keypair. Signs save_observations. When set, must match the on-chain Gateway.observer_address (settable at join time via --observer-address or later via update_observer_address). Falls back to SOLANA_KEYPAIR_PATH.
SOLANA_UPLOAD_KEYPAIR_PATH Path - Optional separate Solana keypair for report uploads. Produces an arbundles.SolanaSigner. Falls back to OBSERVER_KEYPAIR_PATH ?? SOLANA_KEYPAIR_PATH. Ignored when ARWEAVE_UPLOAD_* or ETHEREUM_UPLOAD_* is set.
ARWEAVE_UPLOAD_KEY_FILE Path - Path to an Arweave JWK used for report uploads (arbundles.ArweaveSigner). Top priority.
ARWEAVE_UPLOAD_JWK String - Inline Arweave JWK JSON. Lower priority than ARWEAVE_UPLOAD_KEY_FILE.
ETHEREUM_UPLOAD_PRIVATE_KEY_FILE Path - Path to a 32-byte hex Ethereum private key (with or without 0x prefix). Produces an arbundles.EthereumSigner. Lower priority than Arweave; higher than Solana.
ETHEREUM_UPLOAD_PRIVATE_KEY String - Inline Ethereum private key (hex). Lower priority than the file form.

save_observations submission flow (Solana mode, SUBMIT_CONTRACT_INTERACTIONS=true):

  1. The OnDemandArNSResolver + continuous observer produce a ObserverReport for the current epoch.
  2. TurboReportSink uploads the report bundle to permaweb under the upload identity, returning the 43-char Arweave TX ID.
  3. SolanaContractReportSink reads the current Epoch account once (pre-flight gate). It skips submission if:
    • The observer's pubkey isn't in epoch.prescribed_observers for this epoch (we weren't picked this round).
    • The has_observed bit at our observer slot is already set (we already submitted).
    • now >= epoch.end_timestamp (the observation window has closed).
  4. Otherwise it builds the save_observations instruction with the failed-gateway bitmap + base64url-decoded report TX ID and submits it signed by the observer keypair (not the operator/cranker).
  5. After the parent epoch is fully distributed, the cranker's close_observation loop reclaims the Observation PDA's rent.

The on-chain Observation.report_tx_id field stores the raw 32-byte hash (lossless decode of the 43-char base64url txid) so consumers can recover the full Arweave txid for permaweb audits.

Cache-Control / upstream cache poisoning

CACHE_STABLE_MAX_AGE, CACHE_UNSTABLE_TRUSTED_MAX_AGE, and CACHE_UNSTABLE_MAX_AGE govern the Cache-Control: max-age returned for data responses. CACHE_STABLE_MAX_AGE additionally emits the immutable directive, which tells browsers and upstream proxies (e.g., nginx) not to revalidate before the max-age expires.

URL → data-id bindings served via ArNS are mutable: ArNS records can be updated, manifests can be republished, and a previously-fallback path may become path-mapped in a future manifest revision. If a CACHE_*_MAX_AGE value exceeds your longest expected ANT TTL, upstream proxies hold these responses long after the underlying mapping has changed, producing stale or broken pages — typically blank apps, missing images, or "domain-yours" placeholders served on a now-active ArNS name.

Note that ARNS_NOT_FOUND_ARNS_NAME defaults to 'unregistered_arns', so every gateway with default config routes failed ArNS resolutions through this branch. As of PE-9072 these responses are explicitly bounded to CACHE_NOT_FOUND_MAX_AGE regardless of CACHE_UNSTABLE_*_MAX_AGE; on gateways running prior versions, lower CACHE_UNSTABLE_TRUSTED_MAX_AGE to the default and sweep upstream caches for placeholder content.

Recommendations:

  • Keep CACHE_STABLE_MAX_AGE, CACHE_UNSTABLE_TRUSTED_MAX_AGE, and CACHE_UNSTABLE_MAX_AGE at their defaults unless you have an operational procedure for evicting poisoned upstream cache entries (e.g., grep nginx cache files for the placeholder's X-AR-IO-Data-Id and remove matches).
  • Manifest fallback responses, ARNS_NOT_FOUND_TX_ID / ARNS_NOT_FOUND_ARNS_NAME responses, and 404s are bounded to CACHE_NOT_FOUND_MAX_AGE (default 60s) with must-revalidate regardless of the operator's other cache settings.
  • APEX_TX_ID responses are bounded to CACHE_APEX_MAX_AGE (default 1h) with must-revalidate so an apex rotation propagates within the configured window.

Observer Configuration

The following environment variables configure the Observer service for network gateway observations.

Reference Gateway Configuration

These settings control which gateways are used as references for ArNS resolution and chunk verification checks.

ENV_NAME TYPE DEFAULT_VALUE DESCRIPTION
REFERENCE_GATEWAY_HOSTS String "turbo-gateway.com" Comma-separated list of reference gateway hosts for ArNS and chunk checks. These gateways are tried in order before network fallback
REFERENCE_GATEWAY_NETWORK_ONLY Boolean false If true, uses only network gateways for reference checks (no explicit hosts). Enables fully decentralized observation
REFERENCE_GATEWAY_NETWORK_FALLBACK Boolean true If true, falls back to network consensus when explicit reference hosts fail or disagree
REFERENCE_GATEWAY_CONSENSUS_SIZE Number 3 Number of network gateways to query when building consensus for ArNS resolution
REFERENCE_GATEWAY_CONSENSUS_THRESHOLD Number 2 Minimum number of agreeing gateways required to establish consensus
REFERENCE_GATEWAY_MIN_PASS_RATE Number 0.8 Minimum historical pass rate (0.0-1.0) for a network gateway to be eligible for consensus queries
REFERENCE_GATEWAY_MIN_CONSECUTIVE_PASSES Number 2 Minimum consecutive passing epochs required for network gateway eligibility
REFERENCE_GATEWAY_MIN_EPOCH_COUNT Number 5 Minimum total epochs observed for a network gateway to be considered for eligibility
REFERENCE_GATEWAY_MAX_NETWORK_POOL Number 10 Maximum number of eligible network gateways to keep in the pool for random selection
REFERENCE_GATEWAY_NETWORK_CACHE_TTL_SECONDS Number 300 TTL in seconds for caching the list of eligible network gateways from the AR.IO contract
REFERENCE_GATEWAY_CONSENSUS_MAX_ATTEMPTS Number 5 Maximum number of gateway queries to attempt when trying to reach consensus threshold

Continuous Observation Configuration

These settings control the continuous observation mode, which spreads observations across the epoch window and uses majority voting for pass/fail determination.

ENV_NAME TYPE DEFAULT_VALUE DESCRIPTION
OBSERVATIONS_PER_GATEWAY Number 3 Number of times each gateway is observed during an epoch. Results are aggregated using majority voting
OBSERVATION_WINDOW_FRACTION Number 0.5 Fraction of the epoch window (0.1-0.9) during which observations are spread. Higher values spread observations over more time
OBSERVATION_CYCLE_INTERVAL_MS Number 60000 Interval in milliseconds between observation cycles. Each cycle processes scheduled observations
MAJORITY_VOTE_THRESHOLD Number 2 Number of passing observations needed for a gateway to pass overall. Should be ≤ OBSERVATIONS_PER_GATEWAY
GATEWAY_ASSESSMENT_CONCURRENCY Number 10 Parallel gateway assessments per observation cycle (network-bound). Higher values speed assessment but add concurrent outbound load
NAME_ASSESSMENT_CONCURRENCY Number 5 Parallel ArNS-name resolution checks per gateway during assessment
ENABLE_LOG_REPORT_SINK Boolean false When true, enables the LogReportSink which logs full per-gateway assessment detail at info level

Offset Observation Configuration

These settings control chunk offset verification observations, which validate that gateways correctly return chunk data with proper offset information.

ENV_NAME TYPE DEFAULT_VALUE DESCRIPTION
OFFSET_OBSERVATION_SAMPLE_RATE Number 0.10 Sample rate (0.0-1.0) for offset observations. Higher values test more chunks but increase load on observed gateways
OFFSET_OBSERVATION_ENABLED Boolean true If true, enables offset observation checks. When false, offset observations are skipped entirely
OFFSET_OBSERVATION_ENFORCEMENT_ENABLED Boolean false If true, offset observation failures affect gateway pass/fail status. When false, failures are logged but don't impact scoring

Tag Response Headers

These settings control whether Arweave transaction/data item tags and verification metadata are included as HTTP response headers when serving data. Note: for L2 data item signature and owner key headers, WRITE_ANS104_DATA_ITEM_DB_SIGNATURES must also be true.

ENV_NAME TYPE DEFAULT_VALUE DESCRIPTION
ARWEAVE_TAG_RESPONSE_HEADERS_ENABLED Boolean true If true, includes transaction/data item tags as X-Arweave-Tag-* and verification headers (X-Arweave-Signature, X-Arweave-Owner, etc.) on /raw/:id and /:id responses
ARWEAVE_TAG_RESPONSE_HEADERS_MAX Number 100 Maximum number of tag headers to include per response. If a transaction has more tags, an X-Arweave-Tags-Truncated: true header is added
ARWEAVE_TAG_RESPONSE_HEADERS_MAX_BYTES Number 8192 Maximum total bytes for all emitted tag and verification headers. Prevents exceeding intermediary header size limits (nginx default 8KB). Verification headers are prioritized over tags
TX_METADATA_RESOLVE_CONCURRENCY Number 1 Maximum number of concurrent background data item metadata resolutions. Limits resource pressure from remote fetches and DB writes when many uncached items are requested simultaneously

GraphQL On-Demand Resolution

When enabled, the transaction(id) GraphQL query can resolve unindexed data items on-demand by extracting metadata directly from ANS-104 bundle binaries. Only applies to single-ID lookups; the plural transactions(...) query is unaffected.

ENV_NAME TYPE DEFAULT_VALUE DESCRIPTION
GRAPHQL_ON_DEMAND_RESOLUTION_ENABLED Boolean true If true, enables on-demand data item resolution as a fallback when transaction(id) returns null from the local database
GRAPHQL_ON_DEMAND_RESOLUTION_TIMEOUT_MS Number 5000 Maximum time in milliseconds to wait for on-demand resolution before returning null. Background resolution continues and persists for future queries
GRAPHQL_ON_DEMAND_RESOLUTION_MAX_CONCURRENT Number 1 Maximum number of concurrent on-demand resolution operations. When at capacity, requests return null immediately
GATEWAYS_GQL_URLS JSON array of URLs [] Fan-out GraphQL upstreams. When non-empty, the /graphql endpoint queries every listed gateway in parallel and merges the results. Assumes all upstreams use the ar-io-node cursor format. Empty disables the layer.
GATEWAYS_GQL_INCLUDE_LOCAL Boolean true When GATEWAYS_GQL_URLS is set, include the local GqlQueryable (SQLite or Composite ClickHouse) as one more source in the merge. Set to false for a pure fan-out proxy.
GATEWAYS_GQL_REQUEST_TIMEOUT_MS Number 9500 Per-upstream HTTP request timeout for fan-out GraphQL queries. Should be slightly less than GATEWAYS_GQL_CIRCUIT_BREAKER_TIMEOUT_MS so axios aborts first
GATEWAYS_GQL_CIRCUIT_BREAKER_TIMEOUT_MS Number 10000 Per-upstream circuit breaker call timeout. An open breaker causes the fan-out to skip that upstream and continue with the rest
GATEWAYS_GQL_CIRCUIT_BREAKER_ERROR_THRESHOLD_PERCENTAGE Number 80 Error rate (0–100) within the rolling window that trips the breaker open
GATEWAYS_GQL_CIRCUIT_BREAKER_ROLLING_COUNT_TIMEOUT_MS Number 60000 Rolling window in milliseconds over which error rate is measured
GATEWAYS_GQL_CIRCUIT_BREAKER_RESET_TIMEOUT_MS Number 30000 How long an open breaker stays open before transitioning to half-open for a trial request

HTTPSIG Response Signing

Signs gateway responses with an Ed25519 key per RFC 9421 (HTTP Message Signatures). Every qualifying response gets Signature and Signature-Input headers that cryptographically bind the gateway's trust claims (verification status, ArNS resolution, data item tags) to a staked on-chain identity.

When OBSERVER_KEYPAIR_PATH or OBSERVER_PRIVATE_KEY is set, the gateway uses the observer's Solana keypair directly as the HTTPSIG signing key. Verifiers derive the Solana address from the public key in the keyId and look it up in the on-chain GAR — no separate attestation document is needed. When neither is set, a standalone Ed25519 key is auto-generated (responses are signed but not verifiable against the on-chain registry). Setting both is rejected as ambiguous.

ENV_NAME TYPE DEFAULT_VALUE DESCRIPTION
HTTPSIG_ENABLED Boolean true Enable RFC 9421 HTTP Message Signature signing on gateway responses
HTTPSIG_KEY_FILE String data/keys/httpsig.pem Path to Ed25519 private key PEM file. Auto-generated on first startup if missing. Ignored when OBSERVER_KEYPAIR_PATH or OBSERVER_PRIVATE_KEY is set
KEYS_DATA_PATH String ./data/keys Host path for the keys volume mount (docker-compose only). Maps to /app/data/keys inside the container
HTTPSIG_BIND_REQUEST Boolean true Include @method;req and @path;req in signatures, binding each response to the specific request that triggered it
HTTPSIG_BODY_DIGEST_BUFFER_MAX_BYTES Number 2097152 (2 MiB) Applies only to uncached data responses (/raw, /tx, ArNS-resolved, manifest-served). When > 0, uncached responses with a known body size at-or-below this byte threshold are buffered to compute SHA-256 and emit Content-Digest, which is in CO_SIGNABLE_HEADERS and therefore covered by the HTTPSIG signature. Cached and HEAD responses always emit the stored digest. Chunk responses (/chunk/:offset, /chunk/:offset/data) compute their digest inline from the in-memory 256 KiB chunk regardless of this setting and are not affected by it. Set to 0 to disable buffered emission for uncached small data responses; cached and chunk responses still emit Content-Digest. Non-numeric or negative values fall back to the default. Independent of this setting, X-AR-IO-Digest is emitted as an unsigned advisory header for any indexed transaction whose canonical hash is on file — including large uncached responses — so clients can verify served bytes against the chain value without an extra round-trip; it is overwritten by the actually-served-body hash when the buffered helper runs.
OBSERVER_KEYPAIR_PATH String - Path to the observer's Solana keypair JSON file (64-byte array). When set, this key is used for HTTPSIG signing and its Solana address is verifiable in the on-chain GAR
OBSERVER_PRIVATE_KEY String - Alternative to OBSERVER_KEYPAIR_PATH: a base58-encoded 64-byte Solana secret key (the format Phantom and other browser wallets export). Convenient for operators who don't already have a keypair JSON file on disk. Setting both OBSERVER_KEYPAIR_PATH and OBSERVER_PRIVATE_KEY is rejected as ambiguous

ClickHouse TTL Rules

Operator-defined TTL rules for data in ClickHouse. Reloaded from disk by clickhouse-auto-import at the top of every import cycle. See Parquet and ClickHouse usage for the rules-file format and behavior.

ENV_NAME TYPE DEFAULT_VALUE DESCRIPTION
CLICKHOUSE_TTL_RULES_PATH String ./config/clickhouse-ttl-rules.yaml Path to the YAML file of tag- and owner-based TTL rules loaded into ClickHouse before each import cycle

ClickHouse Auto-Import Daemon

Controls the clickhouse-auto-import container that drives the Parquet export → ClickHouse import → SQLite prune cycle. Each cycle walks every batch from minStableDataItem up to maxStableDataItem, so cycle length scales with both height range and per-batch wall-clock. See ClickHouse pipeline → Cycle latency tuning for the full latency breakdown and tradeoff discussion.

ENV_NAME TYPE DEFAULT_VALUE DESCRIPTION
CLICKHOUSE_AUTO_IMPORT_IMAGE_TAG String latest Image tag for ghcr.io/ar-io/ar-io-clickhouse-auto-import. Pin to a SHA for reproducible deploys
CLICKHOUSE_AUTO_IMPORT_SLEEP_INTERVAL Number 3600 Seconds to sleep between cycles
CLICKHOUSE_AUTO_IMPORT_HEIGHT_INTERVAL Number 10000 Heights per batch. The dominant per-batch overhead is the parquet-export status poll (~5s) plus a few small HTTP calls, not the linear-in-rows export work — so larger batches amortize fixed cost across more heights. Bumping from 100 → 1000 typically cuts cycle time 5–10×. Larger batches also produce wider parquet partitions on disk; mixing widths is safe (ReplacingMergeTree dedupes ClickHouse-side and the dataset directory has no uniform-width invariant) but expect a one-time disk-space increase the first time a wider cycle re-writes the existing range
CLICKHOUSE_AUTO_IMPORT_MAX_ROWS_PER_FILE Number 1000000 Split parquet partitions into multiple files once they exceed this row count
CLICKHOUSE_AUTO_IMPORT_IDLE_WAIT_TIMEOUT_S Number 600 Max seconds to wait for the shared parquet-export singleton to go idle before starting a new export
CLICKHOUSE_AUTO_IMPORT_EXPORT_MAX_RETRIES Number 3 Retry attempts when the parquet-export contends with another caller
CLICKHOUSE_AUTO_IMPORT_EXPORT_RETRY_DELAY_S Number 5 Seconds to sleep between export retries
PARQUET_EXPORT_DUCKDB_MEMORY_LIMIT String 2GB Memory budget for the core-side parquet-export DuckDB instance. The export sorts each partition (COPY ... ORDER BY) and spills to disk past this limit instead of OOM-killing the worker. Set below the core container's memory ceiling. Lets a single export survive ultra-dense block heights (hundreds of thousands of data items / millions of tag rows)
PARQUET_EXPORT_DUCKDB_MAX_TEMP_DIRECTORY_SIZE String 64GB Upper bound on DuckDB's on-disk spill for a single export. Guards against a runaway sort filling the data volume
PARQUET_EXPORT_DUCKDB_THREADS Number undefined Optional cap on DuckDB worker threads during export. Fewer threads lowers peak sort memory. Unset → DuckDB default (one per core)

ClickHouse / SQLite GraphQL Boundary

When ClickHouse is configured alongside SQLite, GraphQL transaction queries hit both stores and merge the results. These settings let the composite backend skip SQLite for heights already covered by ClickHouse, avoiding duplicate scans. The buffer reserves recent heights (where ClickHouse ingestion may be partial) for SQLite.

The boundary only applies to SQLite's stable tables (stable_transactions, stable_data_items). The new tables (new_transactions, new_data_items) hold unstable and unconfirmed data — including pending rows with NULL height that would be silently dropped by a height >= :minHeight predicate — and are never covered by ClickHouse, so the floor is not applied to them.

ENV_NAME TYPE DEFAULT_VALUE DESCRIPTION
CLICKHOUSE_SQLITE_MIN_HEIGHT_ENABLED Boolean false When true, restrict the SQLite fallback to heights above (ClickHouse max height - buffer)
CLICKHOUSE_SQLITE_MIN_HEIGHT_BUFFER Number 10 Heights reserved for SQLite near the ClickHouse tip, to guard against partially ingested recent blocks
CLICKHOUSE_MAX_HEIGHT_CACHE_TTL_SECONDS Number 60 TTL for the cached ClickHouse max-height lookup used by the boundary optimization
CLICKHOUSE_QUERY_TIMEOUT_SECONDS Number 3 Timeout for ClickHouse queries, applied both as server-side max_execution_time and client-side HTTP request_timeout
CLICKHOUSE_GQL_MAX_ROWS_TO_READ Number 10000000 Per-query max_rows_to_read hint applied to GraphQL queries against the transactions table. Queries that would scan more than this throw Code: 158, preventing accidental full-table scans if a skip index is bypassed
CLICKHOUSE_GQL_OWNER_PROJECTION_ROUTING_ENABLED Boolean false When true, owner-filtered GraphQL transactions queries are routed through owner_projection. The main transactions table is height-ordered, so a sparse owner's rows scatter across millions of granules and finding a page can trip the row cap; the owner-ordered projection seeks straight to the owner's slice. No-id owner queries also gain a reactive height-windowing fallback if they still trip max_rows_to_read. Also routes owners + ids queries through the projection — a multi-id id IN (...) filter lights up most of id_bloom's granules (≈ a half-table scan, the dominant source of TOO_MANY_ROWS), so adding an owner and seeking the owner's slice keeps it under the cap; note owners + ids does NOT get the windowed retry (the walk is height-ordered and id queries carry no cursor predicate), so a whale owner + ids still surfaces the 158. Disabled by default for staged rollout — when off, owner queries plan exactly as before
CLICKHOUSE_GQL_OWNER_PROJECTION_ENTITY_TYPES String drive,folder,snapshot Comma-separated allowlist of Entity-Type tag values eligible for owner_projection routing of tag queries (only consulted when CLICKHOUSE_GQL_OWNER_PROJECTION_ROUTING_ENABLED is true, and not applied to owners + ids queries, which are bounded by the id list). A no-id query qualifies only when it carries an Entity-Type filter whose values are ALL in this list. file is deliberately omitted: an owner can have millions of files, and routing forces a full sort of the matched set per page (read-in-order is disabled to use the projection), re-done on every page — those large-result queries want the dedicated owner-ordered table instead. Bare-owner and owner+other-tag queries are likewise excluded
GQL_L1_ONLY_ROUTING_FILTER String {"never": true} Composable-filter DSL classifying which GraphQL transactions queries may be served exclusively from the L1 (base-layer) SQLite index, bypassing ClickHouse. When an incoming query is provably confined to L1 by this filter, it is answered by the stable_transactions tag-name+value PK seek — which sidesteps ClickHouse's TOO_MANY_ROWS row-scan cap — over the full block history, and ClickHouse is skipped entirely. Uses the same grammar as the indexing filters, but restricted to its monotone subset (tags, attributes, and, or, always, never); not / isNestedBundle / hashPartition are rejected at startup because entailment over non-monotone predicates could drop valid data-item results. The filter is an operator assertion that queries it entails are L1-only (e.g. {"tags":[{"name":"App-Name","value":"SmartWeaveAction"}]}) — any bundled data-item matches are intentionally excluded from routed queries, so only assert this for tags whose corpus is genuinely base-layer. {"always":true} routes every query to L1-only (drops all data-item results globally — a footgun). Default {"never": true} leaves routing off. Only consulted when a ClickHouse GraphQL backend is configured
CLICKHOUSE_GQL_DEDUPE_HEADROOM Number 4 Multiplier applied to pageSize + 1 to size the inner LIMIT of the GQL transactions query. Pagination correctness caveat: this must be at least as large as the table's effective duplicate factor. If a region of the table has more unmerged ReplacingMergeTree versions per PK than this headroom covers, the deduped window can yield fewer than pageSize + 1 unique rows even when more matches exist further on — the result will be a short page with hasNextPage: false and rows past the window will be silently skipped. Regular background merges keep the duplicate factor at 1-2 in practice, so 4 leaves comfortable headroom; raise this if operators observe short pages (e.g. during a heavy ingest that produces many unmerged parts)
CLICKHOUSE_SQLITE_CIRCUIT_BREAKER_TIMEOUT_MS Number 5000 Timeout (ms) for the SQLite leg of the composite GQL transactions query. On timeout the response degrades to ClickHouse-only results; ClickHouse timeouts are governed separately by CLICKHOUSE_QUERY_TIMEOUT_SECONDS and still propagate to the caller
CLICKHOUSE_SQLITE_CIRCUIT_BREAKER_ERROR_THRESHOLD_PERCENTAGE Number 80 Error rate (0–100) within the rolling window that trips the SQLite-leg breaker open
CLICKHOUSE_SQLITE_CIRCUIT_BREAKER_ROLLING_COUNT_TIMEOUT_MS Number 60000 Rolling window (ms) over which the SQLite-leg breaker's error rate is measured
CLICKHOUSE_SQLITE_CIRCUIT_BREAKER_RESET_TIMEOUT_MS Number 30000 How long the SQLite-leg breaker stays open before transitioning to half-open for a trial request

Streaming pipeline (unstable head)

When enabled, indexed blocks, L1 transactions, and ANS-104 data items are streamed into ClickHouse new_blocks / new_transactions so the unstable head (~18 confirmations of recent data) is queryable from CH directly instead of only from SQLite. The stable Parquet pipeline is unchanged — once a row stabilizes it lands in transactions via parquet-export and the unstable copy ages out via TTL. GraphQL merges the two transparently.

Streaming is opt-in. The default-off mode preserves today's behavior exactly. Operators who want CH to be the sole read path can pair CLICKHOUSE_STREAMING_ENABLED=true with CLICKHOUSE_GQL_SKIP_SQLITE_READS=true. SQLite WRITES are unaffected by either flag — the indexer still feeds SQLite for the Parquet export pipeline.

The unstable tables are created idempotently by scripts/clickhouse-import. On streaming-only deployments the streamer fails closed at startup if the tables are missing — operators run the import script once to bootstrap.

ENV_NAME TYPE DEFAULT_VALUE DESCRIPTION
CLICKHOUSE_STREAMING_ENABLED Boolean false Master switch for the streaming pipeline. When true: indexer streams unstable rows into ClickHouse, GraphQL adds a third merge leg over new_transactions, and the SQLite leg drops to a tight-timeout fallback governed by CLICKHOUSE_SQLITE_FALLBACK_CIRCUIT_BREAKER_TIMEOUT_MS. When false (default): behavior is identical to the pre-streaming 2-leg path
CLICKHOUSE_NEW_TX_TTL_MINUTES Number 240 TTL (minutes) for rows in new_blocks and new_transactions. Once a row stabilizes it lands in transactions via the Parquet pipeline; the unstable copy expires after this window. Default of 4h is comfortably past 18 confirmations × ~2 min/block plus a 1h auto-import cycle, so a stalled chain or delayed import won't expire rows before they stabilize. Read by scripts/clickhouse-import at schema-render time, not at runtime
CLICKHOUSE_STREAMER_BATCH_SIZE Number 500 Maximum rows the streamer buffers before issuing a bulk INSERT. A time-based flush (CLICKHOUSE_STREAMER_FLUSH_INTERVAL_MS) ensures rows aren't held indefinitely when ingest is slow
CLICKHOUSE_STREAMER_FLUSH_INTERVAL_MS Number 1000 Time-based flush interval (ms). Caps unstable-head latency at this value plus the bulk-insert RTT
CLICKHOUSE_STREAMER_QUEUE_MAX_SIZE Number 10000 Hard cap on the streamer's in-memory buffer. Once exceeded, the streamer drops oldest rows to keep memory bounded under sustained CH unavailability — drops are logged and surface as a Prometheus metric. Dropped rows land via the stable Parquet pipeline once they stabilize
CLICKHOUSE_GQL_SKIP_SQLITE_READS Boolean false When true, GraphQL queries skip the SQLite leg entirely. Pairs with CLICKHOUSE_STREAMING_ENABLED for operators who want CH to be the sole read path. Has no effect on SQLite WRITES
CLICKHOUSE_SQLITE_FALLBACK_CIRCUIT_BREAKER_TIMEOUT_MS Number 250 Replacement for CLICKHOUSE_SQLITE_CIRCUIT_BREAKER_TIMEOUT_MS when streaming is enabled. In that mode CH-unstable covers the live tip and SQLite is a last-resort fallback for outages — a tight timeout keeps GraphQL responsive when CH itself is healthy. Distinct env var so operators running both modes on different nodes don't have to pick one envelope