Splunk S2S
Synopsis
Creates a target that sends events to a Splunk indexer over Splunk's native Splunk-to-Splunk (S2S) protocol on port 9997 — the wire protocol a Splunk universal forwarder speaks, as opposed to the HTTP Event Collector used by the splunk and splunksecurity targets. Supports weighted load balancing across an indexer tier, protocol v3/v4 framing, delivery acknowledgement, and compression.
Schema
- name: <string>
description: <string>
type: splunks2s
pipelines: <pipeline[]>
status: <boolean>
properties:
address: <string>
port: <numeric>
servers: <string[]>
destinations:
- address: <string>
port: <numeric>
weight: <numeric>
load_balance_seconds: <numeric>
load_balance_bytes: <numeric>
load_balance_stats_seconds: <numeric>
dns_resolve_seconds: <numeric>
destination_down_seconds: <numeric>
protocol: <string>
index: <string>
source: <string>
source_type: <string>
host: <string>
extract_metadata: <boolean>
ack: <boolean>
ack_timeout: <numeric>
compression: <boolean>
server_name: <string>
mgmt_port: <string>
batch_size: <numeric>
timeout: <numeric>
max_retries: <numeric>
retry_delay: <numeric>
max_event_bytes: <numeric>
nested_field_serialization: <string>
throttle_bytes_per_second: <numeric>
field_format: <string>
debug:
status: <boolean>
dont_send_logs: <boolean>
tls:
status: <boolean>
verify: <boolean>
server_name: <string>
ca_name: <string>
cert_name: <string>
key_name: <string>
passphrase: <string>
min_tls_version: <string>
max_tls_version: <string>
Configuration
The following are the fields used to define the target:
| Field | Required | Default | Description |
|---|---|---|---|
name | Y | Target name | |
description | N | - | Optional description |
type | Y | Must be splunks2s | |
pipelines | N | - | Optional post-processor pipelines |
status | N | true | Enable/disable the target |
Destinations
| Field | Required | Default | Description |
|---|---|---|---|
address | Y* | - | Single indexer address (hostname or IP), paired with port |
port | N | 9997 | Port for address, and the default port for any destinations entry that omits one |
servers | N | - | Array of "host:port" strings, one indexer per entry |
destinations | N | - | Weighted destination list. Each entry: address, port (default 9997), weight (default 1) |
* = address, servers, and destinations are combinable and additive; at least one is required.
Load Balancing
| Field | Required | Default | Description |
|---|---|---|---|
load_balance_seconds | N | 30 | Seconds a worker holds one connection before reconsidering the destination. 0 pins the connection |
load_balance_bytes | N | 0 | Bytes written on one connection before it is rotated. 0 rotates on time only |
load_balance_stats_seconds | N | 300 | Window over which per-destination volume is remembered when apportioning load; half of it carries forward each rollover |
dns_resolve_seconds | N | 600 | How often a configured hostname is re-resolved |
destination_down_seconds | N | 30 | How long a destination that refused a connection is passed over before being retried |
Protocol
| Field | Required | Default | Description |
|---|---|---|---|
protocol | N | "v3" | Wire framing: v3 (legacy cooked, default), v4 (compact framing), auto (negotiate v4, fall back to v3) |
ack | N | false | Wait for indexer acknowledgement before reporting a batch delivered. Requires protocol: v4 or auto |
ack_timeout | N | 60 | Seconds to wait for acknowledgement before a batch counts as undelivered. Only meaningful with ack: true |
compression | N | false | zlib-compress the connection |
server_name | N | product name | Identity presented to the indexer during the handshake |
mgmt_port | N | "8089" | Management port advertised during the handshake |
Event Metadata
| Field | Required | Default | Description |
|---|---|---|---|
index | N | - | Default Splunk index stamped on every event |
source | N | - | Default source |
source_type | N | - | Default sourcetype |
host | N | - | Default host |
extract_metadata | N | true | Lift _raw/host/source/sourcetype/index/time out of a Splunk-shaped JSON envelope. Explicitly configured metadata above still wins |
nested_field_serialization | N | "none" | How an object/array-valued envelope field is handled: none (drop it) or json (forward its JSON text) |
Batching and Delivery
| Field | Required | Default | Description |
|---|---|---|---|
batch_size | N | 1000 | Events buffered before a flush |
timeout | N | 30 | Connection and write timeout in seconds |
max_retries | N | 3 | Maximum delivery attempts per batch |
retry_delay | N | 1 | Seconds between retry attempts |
max_event_bytes | N | 66060288 (~63 MB) | Ceiling on one event's payload; events past this are truncated rather than sent |
throttle_bytes_per_second | N | 0 | Ceiling on outbound bytes per second per sender worker, measured post-compression. 0 disables it |
TLS
| Field | Required | Default | Description |
|---|---|---|---|
tls.status | N | false | Enable TLS |
tls.verify | N | true | Verify the server certificate |
tls.server_name | N | - | SNI / expected server hostname |
tls.ca_name | N | - | Custom CA to trust. Empty uses the OS trust store |
tls.cert_name | N* | - | Client certificate for mutual TLS |
tls.key_name | N* | - | Client key for mutual TLS |
tls.passphrase | N | - | Passphrase for an encrypted client key |
tls.min_tls_version | N | tls1.2 | Minimum negotiated TLS version |
tls.max_tls_version | N | tls1.3 | Maximum negotiated TLS version |
* = Conditionally required. tls.cert_name and tls.key_name must be supplied together; supplying only one fails configuration validation.
TLS material fields (cert_name, key_name, ca_name, client_ca_name) accept any of the following:
- File name — resolved relative to the service root directory. Nested paths such as
certs/prod/server.pemare supported. - Absolute path — honored only if it resolves inside the service root. Any path that escapes the root is refused.
- Inline PEM content — used verbatim when the value contains
-----BEGIN. - Environment variable —
${ENV_VAR}. - Vault reference —
$secret{id=...}or$secret{store=...,ref=...}.
Normalization
| Field | Required | Default | Description |
|---|---|---|---|
field_format | N | - | Data normalization format. See applicable Normalization section |
Scheduling
See Scheduling and Pool Behavior for interval and cron fields shared by all targets.
Debug Options
| Field | Required | Default | Description |
|---|---|---|---|
debug.status | N | false | Enable debug logging |
debug.dont_send_logs | N | false | Process logs but don't send to target (testing) |
Details
The Splunk S2S target speaks the same wire protocol a Splunk universal forwarder uses on port 9997, so an indexer accepts the data as cooked, pre-attributed events with no HEC token and no ingest-time parsing. It is a single-type target — splunks2s is its only registered type, unlike splunk/splunksecurity/crowdstrike, which share one HEC implementation.
Indexer Load Balancing
address/port configure a single destination, servers a plain list, and destinations a weighted list — the three forms are combinable and additive, so a weight can be added to one member of an existing servers list without rewriting the rest. A weight is a share of traffic, not an absolute value: keep weights within the same order of magnitude (1/5/4 works; 10/120/9 does not). Weight 0 parks a destination — configured and reachable as a last resort, but never chosen while any weighted destination is up.
A configured hostname is resolved to every address it has, and each resolved address becomes its own destination — so a single name fronting an indexer tier spreads traffic across the whole tier instead of pinning one member. TLS is still verified against the configured hostname, not the resolved address.
Two settings trigger rotation to a new destination:
load_balance_secondsis how long a worker holds one connection before reconsidering, mirroring Splunk's ownautoLBFrequencydefault of30. Without it, the first indexer a worker reaches keeps every batch that worker ever sends — an indexer added to the tier never receives anything, and one that was briefly down never gets its share back.0pins the connection.load_balance_bytesis the volume trigger, and the one that matters for bursty input: a batch that arrives all at once fits inside any sensible time interval, so a time-only rule sends the whole burst to whichever destination the first batch picked. Measured against two indexers with time-only rotation, a 500-event burst went 500/0. Splunk carries the same pair of levers asautoLBFrequencyandautoLBVolume.0rotates on time only.
load_balance_stats_seconds is the window over which per-destination volume is remembered when apportioning load; half of it carries forward on each rollover, so short-term imbalance decays instead of either persisting indefinitely or being forgotten between windows. dns_resolve_seconds controls how often a configured hostname is re-resolved — a resolution that fails keeps the last known address set rather than emptying it. destination_down_seconds is how long a destination that refused a connection is passed over; the cooldown is shared across every worker of the target, so one worker's discovery is not re-learned by the rest.
Protocol Versions
protocol: v3 (the default) sends legacy cooked messages, where each message carries its own _done terminator — the indexer takes the event exactly as sent, so the original timestamp is preserved and a multi-line event stays one event. v4 (or auto, which negotiates v4 and falls back to v3 if the receiver refuses it) sends the compact framing a Universal Forwarder uses, which hands event-boundary detection and timestamp extraction back to the indexer. ack: true requires protocol: v4 or auto — acknowledgement rides on v4 message IDs, so it cannot be combined with an explicitly configured v3.
Event Metadata
index, source, source_type, and host set the attribution stamped on every event; leaving them empty lets the source's own attribution through. When extract_metadata is enabled (the default), the target lifts _raw/host/source/sourcetype/index/time out of a Splunk-shaped JSON envelope, so data received from another Splunk source keeps its original attribution instead of arriving as an opaque JSON blob — any of the fields above that are explicitly set still take priority. Every other envelope member is forwarded on as an indexed field; nested_field_serialization decides what happens when one of those fields is an object or array, which has no representation in Splunk's flat, text-only indexed fields: none (the default) drops it, json forwards its JSON text.
If an event has no host attribution after metadata resolution, the target falls back to the originating device's name rather than letting Splunk misattribute it to the receiving indexer. Setting schema, source, or log_type in a pipeline overrides source_type for that event, the same last-write-wins mechanism the splunk target uses for its sourcetype override.
Compression
compression: true zlib-compresses the connection, as a forwarder's compressed = true setting does. It is worth enabling on a slow or metered link — a 211 KB batch of repetitive log text went out in 3.7 KB in testing — at the cost of CPU on both ends.
Payload Limits
max_event_bytes bounds a single event's payload before it is framed. Splunk's receiver enforces a hard ceiling near 64 MB and does not report it as an error — it resets the TCP connection instead. An event that exceeds max_event_bytes is truncated and counted rather than sent; without a ceiling, an oversized event that keeps getting retried resets the connection every time and wedges the destination in a reconnect loop that looks like a network fault.
Delivery and Retry
max_retries and retry_delay only absorb a brief blip in reaching a destination. Once retries are exhausted, the target returns the send error and the sender pool NAKs the source records for redelivery — which is what actually prevents loss, and which requires persistent_storage to be enabled on the Director.
Throttling
throttle_bytes_per_second caps outbound bytes per second on each sender worker, measured on what is actually written to the wire — so on a compressed connection the ceiling applies to the post-compression byte count. 0 disables it.
Examples
Basic
Sending events to a single indexer over the S2S protocol... | |
Indexer Tier with Failover
Spreading events across an indexer tier with automatic failover... | |
Weighted Destinations
Favoring a larger indexer over two smaller ones with weighted destinations... | |
Acknowledged Delivery
Waiting for indexer acknowledgement on the compact v4 framing... | |
Secure (TLS)
Connecting to an indexer tier over TLS, verified by hostname... | |
High-Volume with Compression
Compressing the connection and capping outbound bandwidth for a bursty, high-volume source... | |