EDR configuration file reference

Configuration is a YAML file passed to the agent with --config. Tokens are referenced by name and resolved at runtime from a token store directory (--token-store)—secrets never live in the config file. For a task-oriented walkthrough, see Get started with EDR.

Top-level fields

FieldTypeRequired
nameStringYes
locationObjectNo
downstreamObjectNo*
downstreamsListNo*
upstreamsListNo*
  • name—node identity name. Used in topology, logs, and UI.
  • location—geographic location (label, lat, lng). Shown on map UI.
  • downstream—where this node sends data to. Shorthand for a one-entry downstreams: list.
  • downstreams—multiple downstream destinations (fan-out). See Replicate to multiple destinations. Declaring both forms at once is a config error.
  • upstreams—who sends data to this node.

* At least one of downstream, downstreams, or upstreams must be present. A node with both is a regional hub: it receives from edges and forwards on.

Downstream (source to destination)

FieldTypeDefault
nameString—
addressString—
auth_tokenString—
modeedr | directedr
scopeObjectInstance (all)
comms.interval_secsInteger10
comms.degraded_afterInteger3
comms.unhealthy_afterInteger6
retry.initial_backoff_secsInteger1
retry.max_backoff_secsInteger30
retry.multiplierInteger2
retry.halt_patience_secsInteger180
share_topologyBooleantrue
idempotent_writesBooleanfalse
historic_fillObjectrequired
prioritiesListabsent
on_state_lossrecover | haltderived
encodinglp | ptlp
concurrent_sendsInteger1
poll_interval_msInteger1000
bandwidth_scheduleListabsent
bandwidth_timezoneString"utc"
  • name—name of the destination node.
  • address—URL of the destination EDR agent (for example, http://hub:9090).
  • auth_token—token store key for authenticating to the destination.
  • mode—edr: agent-to-agent. direct: write directly to an InfluxDB v3 write API.
  • scope—which databases and tables to replicate. See Scope.
  • comms.interval_secs—how often (seconds) to send status reports when idle.
  • comms.degraded_after—missed reports before the destination considers this channel degraded.
  • comms.unhealthy_after—missed reports before the destination considers this channel unhealthy.
  • retry.initial_backoff_secs—initial retry backoff after send failure.
  • retry.max_backoff_secs—maximum retry backoff.
  • retry.multiplier—exponential backoff multiplier.
  • retry.halt_patience_secs—patience window before persistent write errors or unclassified rejections trigger the halted state. See The halted state.
  • share_topology—include this node’s upstream tree in reports to the destination. When false, this node appears as a leaf.
  • idempotent_writes—user asserts no (series_key, timestamp) pair is ever written with differing field values. Unlocks concurrent_sends > 1, multi-ingest replication, priority reordering, and historic fill. See Performance vs. correctness below.
  • historic_fill—declares the historic start point (mode: none | full | since). No default—absent means the agent refuses to start. Modes full and since require idempotent_writes: true. See Historic fill.
  • priorities—priority routing rules (first match wins). Requires idempotent_writes: true. See Priorities.
  • on_state_loss—what to do when a state journal and its previous-good mirror are both corrupt. Default: recover when idempotent_writes: true, else halt. Explicit recover without the idempotency assertion is rejected. See State and recovery.
  • encoding—wire encoding. pt (zstd-compressed PT) is agent-to-agent only, approximately 2.5x bandwidth saving.
  • concurrent_sends—maximum concurrent send tasks. 1 = strict-order delivery (the default). > 1 requires idempotent_writes: true; 0 is rejected.
  • poll_interval_ms—WAL discovery poll interval.
  • bandwidth_schedule—time-of-day send rate control. See Bandwidth scheduling.
  • bandwidth_timezone—timezone the whole bandwidth_schedule is interpreted in. IANA name (for example, "Asia/Kolkata") or "utc".

Performance vs. correctness

EDR’s default is correctness-first: strict-order delivery. One batch is in flight at a time (concurrent_sends: 1), held through retries, so data arrives at the destination in exactly the order it was written at the source.

When the same point (identical series key and timestamp) is written more than once—an overwrite—InfluxDB resolves it last-write-wins by arrival order. Any concurrency that lets batches race can deliver an overwrite before the original it replaces. The original then arrives second and wins, so the destination reverts to the earlier value. Nothing is lost in transit, and every count matches, which makes this harder to detect than data loss.

idempotent_writes: true is your assertion about the workload: no (series key, timestamp) pair is ever written with differing field values. Under that assertion, re-ordering and re-delivery cannot change the final stored values, which is what makes the throughput features below safe. The agent enforces the pairing at startup and on config reload—these are rejected without the assertion:

FeatureWhy it needs the assertion
concurrent_sends > 1batches race to the destination; arrival order != write order
prioritiesdeliberately reorders blocks within and across WAL files
historic_fill: full or sincebackfill overlaps live replication and re-sends data
multiple ingest nodesindependent per-node WAL streams interleave

Choosing a mode:

  • Workload has overwrites, upserts, or corrections (or you can’t rule them out): leave the defaults—idempotent_writes: false, concurrent_sends: 1, historic_fill: { mode: none }. Throughput is bounded by round-trip latency per batch; size batches up rather than adding concurrency.
  • Workload is append-only (each point written once—typical sensor and metrics ingest): set idempotent_writes: true and raise concurrent_sends (for example, 4-16) for parallel delivery; historic fill and priorities become available.

The assertion is about the writers, not about EDR: if any producer can ever rewrite a point with a different value, it is not idempotent— regardless of how rarely it happens.

Wide tables and overwritten points

A table wider than 100 fields stores one logical row as several field-family rows. With strict-order delivery, the destination merges the families in order. If delivery reorders writes to an overwritten point, each family slice resolves last-write-wins separately. One family can retain the newer write while another retains the older write, producing a row that never existed at the source. A query can also temporarily see a partially delivered row before all families arrive.

Append-only workloads avoid this overwrite problem. idempotent_writes: true asserts duplicate-safe writes; it does not make updates safe under reordering. This caveat is separate from the field-name defect fixed in rc.3, which affected earlier builds even without overwrites. See the rc.3 warning.

Upstreams (destination receiving sources)

Each entry in the upstreams list:

  • name—expected source node name (must match the source’s name).
  • auth_token—token store key for verifying the source’s identity. Each upstream needs a unique auth token so the downstream can identify who connected.
  • write_token—token store key for writing the source’s data into local InfluxDB.

Scope

# Replicate everything (default):
scope:
  type: instance

# Specific databases:
scope:
  type: databases
  names: [sensors, metrics]

# Specific tables:
scope:
  type: tables
  refs:
    - database: sensors
      table: temperature

# Everything EXCEPT named databases/tables (exclude wins; exact names only):
scope:
  type: instance
  exclude:
    databases: [internal_edr]
scope.typeMeaning
instanceAll databases on the source instance (the default when scope is absent).
databasesA named list of databases (all tables within each).
tablesSpecific {database, table} pairs.

Scope filtering happens at block evaluation: out-of-scope blocks are dropped before queueing, so they consume no bandwidth.

exclude: subtracts from whatever the scope includes—the natural way to say “replicate everything except local ops noise” without inverting the config into an allowlist. Two trade-offs to understand before choosing this shape:

  • It is fail-open. Under type: instance with exclusions, a database created tomorrow replicates automatically. That is the point—but if you scope for compliance or data-jurisdiction reasons, prefer the fail-closed allowlist (type: databases), where new databases never leave the node until you name them. When a new database enters scope this way, the agent logs it once (new database entered replication scope) so automatic growth is visible, not silent.
  • Exclusions are validated at load. Excluding something the include can’t reach, excluding under type: tables, duplicate entries, or excluding every included database are all rejected with the offender named—a config that would silently do nothing (or everything) fails fast instead.

Config hot-reload picks up scope changes. Narrowing scope (adding an exclusion) takes effect immediately. Widening scope (removing an exclusion, adding a database) replicates new data going forward only— historic data for the newly added entity is not backfilled; re-run historic fill if the older data is owed.

Priorities

Ordered, first-match-wins list. Requires idempotent_writes: true.

priorities:
  - match: { type: tables, database: sensors, tables: [temperature, humidity] }
    recency: "1h"                    # data-time recency window: matches only
    priority: 1                      #   blocks whose max data time is within 1h of now
    share: 8                         # DRR weight for this tier (optional)
  - match: { type: database, name: sensors }
    recency: "24h"
    priority: 2
  - match: { type: all }
    priority: 3
    historic_fill: true              # historic fill traffic uses this tier
  • match.type (all | database | tables)—what the rule matches.
  • recency (Duration)—optional data-time recency window; the rule matches only blocks whose max data time is within the window of now.
  • priority (Integer, 1-255)—lower is more urgent. Unique values become dispatch tiers.
  • share (Integer)—optional deficit round-robin (DRR) weight for this tier.
  • historic_fill (Boolean)—one rule can claim historic fill traffic for its tier. Otherwise historic fill rides the lowest-priority tier.

See Priorities for how tiers are scheduled.

Bandwidth scheduling

downstream:
  # ... name, address, auth_token ...
  bandwidth_timezone: "Europe/London"   # optional; default "utc"
  bandwidth_schedule:
    - name: "Factory shutdown"
      dates: ["2026-08-10", "2026-08-11"]   # YYYY-MM-DD = this exact date only
      mode: silent
    - name: "Christmas"
      dates: ["12-25"]            # MM-DD = recurring every year
      mode: unlimited
    - name: "Maintenance window"
      hours: "02:00-03:00"
      mode: silent
      silent_reports: true
    - name: "Weekday business hours"
      hours: "08:00-18:00"
      days: [mon, tue, wed, thu, fri]   # optional; absent = every day
      mode: limited
      max_bytes_per_sec: 100000
    - name: "Weekday overnight"
      hours: "18:00-08:00"        # wraps midnight
      days: [mon, tue, wed, thu, fri]
      mode: unlimited
    - name: "Weekend"
      hours: "00:00-24:00"        # whole day, written as an explicit range
      days: [sat, sun]
      mode: unlimited
  • name—optional label, shown in the UI and logs.
  • hours—"HH:MM-HH:MM", 24-hour, in the schedule’s timezone (see bandwidth_timezone). Wraps midnight ("22:00-06:00"). "24:00" is accepted as an end-of-day sentinel only, so a whole-day window can be written as "00:00-24:00" instead of the off-by-one "00:00-23:59"—as a start time it’s a config error. Mutually exclusive with dates.
  • days—optional list of mon, tue, wed, thu, fri, sat, and sun restricting which weekdays hours applies to. Absent = every day. A wrapping window (for example, "22:00-06:00", days: [fri]) is anchored to its start day—the post-midnight tail on Saturday still counts as Friday’s window. Combining days with dates is a config error.
  • dates—optional list of calendar-date overrides, matching the whole day: "YYYY-MM-DD" (one-time—this exact date only) or "MM-DD" (no year—recurs every year, for example "12-25"). An entry has either hours (with optional days) or dates, never both—combining them, or setting neither, is a config error.
  • mode—unlimited (full speed), limited (throttled to max_bytes_per_sec), or silent (no data sends).
  • max_bytes_per_sec—required for limited.
  • silent_reports—for silent mode: keep sending status reports (default true). When false, no traffic at all—the downstream marks the channel unhealthy for the duration.

Entries are evaluated in order; the first matching entry wins, so more specific rules (a weekend override, a calendar exception) must come before the general rule they’re meant to carve an exception out of. When bandwidth_schedule is absent, or nothing matches at a given moment, the pipeline runs unlimited. Data is never dropped by scheduling—it queues during limited or silent windows and drains when the window changes.

bandwidth_timezone sets the zone the whole schedule is interpreted in: "utc" (the default) or an IANA name like "Asia/Kolkata", which also handles daylight-saving correctly—unlike hand-offsetting a UTC window, which drifts an hour for half the year.

Bandwidth schedules changed to UTC by default at the 1.0.0 release candidate

Schedules previously ran in process-local time—which was already UTC inside containers, and whatever the host zone happened to be on bare metal. If you relied on host-local hours, set bandwidth_timezone to that zone. Schedule entries also now reject unknown keys, "24:00" as a start time, two-digit years in dates, and an empty days: list—each of these previously mis-parsed silently.

The agent validates the schedule at load time (and on hot reload) and logs advisory warnings—never load errors—for rules that are fully or partially shadowed by an earlier rule, weekly coverage gaps that silently default to unlimited, and duplicate or conflicting calendar dates. These also appear in /edr/v1/metrics (bandwidth_warnings) and the UI.

Direct mode

downstream:
  name: "cloud-influx"
  address: "https://cloud.influxdata.com"
  auth_token: "cloud-api-token"
  mode: direct

When the destination does not run an EDR agent, replicate directly to a plain InfluxDB v3 write endpoint. In direct mode there is no /connect handshake, health checks use the destination’s GET /health, and no topology is propagated. Auth failures and schema conflicts are handled identically to agent-to-agent mode. The pt wire encoding is not available in direct mode.

Configuration reload

The config file is polled for changes. Reloadable without restart: scope, comms expectations, retry parameters. Requiring a restart: node name, upstream list, destination address, destination mode, idempotent_writes, priority rules, token store path, data directory, listen address. The agent logs the reason when a change is rejected for hot-reload.

Multi-ingest node support

When InfluxDB 3 Enterprise runs as a cluster with multiple ingest nodes sharing an object store, each node produces WAL files under its own path prefix. Set idempotent_writes: true and the agent discovers all node prefixes automatically, tracking an independent WAL cursor per node.

The user guarantee behind idempotent_writes: no (series_key, timestamp) pair is ever written with differing field values across any ingest node. Duplicate writes with identical values are fine. If your load balancer hashes by series key, the guarantee holds automatically.

With idempotent_writes: false (default), the agent refuses to start if multiple ingest nodes are detected—this prevents silent data corruption.

Cursors are stored per node in the state file; an upgrade from a single-node deployment migrates the existing cursor automatically.


Was this page helpful?

Thank you for your feedback!