TCP
Synopsis
Creates a target that forwards log data to a remote TCP endpoint. Supports delimiter and octet-count framing, TLS encryption, connection pooling with keep-alive, batch delivery, and retry logic.
Schema
- name: <string>
description: <string>
type: tcp
pipelines: <pipeline[]>
status: <boolean>
properties:
address: <string>
port: <numeric>
framing: <string>
line_delimiter: <string>
timeout: <numeric>
batch_size: <numeric>
reconnect_interval: <numeric>
max_retries: <numeric>
retry_delay: <numeric>
field_format: <string>
tls:
status: <boolean>
verify: <boolean>
server_name: <string>
cert_name: <string>
key_name: <string>
min_tls_version: <string>
max_tls_version: <string>
debug:
status: <boolean>
dont_send_logs: <boolean>
Configuration
| Field | Required | Default | Description |
|---|---|---|---|
name | Y | Target name | |
description | N | - | Optional description |
type | Y | Must be tcp | |
pipelines | N | - | Optional post-processor pipelines |
status | N | true | Enable/disable the target |
Connection
| Field | Required | Default | Description |
|---|---|---|---|
address | Y | Remote server IP address or hostname | |
port | Y | Remote server port (1-65535) | |
timeout | N | 30 | Connection timeout in seconds |
reconnect_interval | N | 10 | Accepted, but has no effect on delivery in the current release. Retries are paced by retry_delay |
TCP
| Field | Required | Default | Description |
|---|---|---|---|
framing | N | "delimiter" | Framing mode: delimiter or octet |
line_delimiter | N | "\n" | Line separator appended to each message in delimiter framing |
batch_size | N | 1000 | Maximum events buffered before flush |
Retry
| Field | Required | Default | Description |
|---|---|---|---|
max_retries | N | 3 | Retries after a failed batch (total attempts = max_retries + 1). 0 is read as unset and gives the default of 3 |
retry_delay | N | 1 | Starting point in seconds for the wait before a retry. 0 is read as unset and gives the default of 1 |
TLS
| Field | Required | Default | Description |
|---|---|---|---|
tls.status | N | false | Enable TLS encryption |
TLS material is resolved through the shared client builder, so these keys mean the same thing on every
target that uses it. They are nested under a tls: block.
| Field | Required | Default | Description |
|---|---|---|---|
tls.verify | N | true | Verify the server certificate. On by default — set it to false only to accept an untrusted certificate, and only where you control the network path |
tls.server_name | N | - | SNI hostname override for the handshake. Use it when the certificate's name does not match the address you connect to |
tls.ca_name | N | - | CA bundle used to verify the server certificate. When unset the host trust store is used; when set it replaces the host trust store rather than adding to it |
tls.cert_name | N* | - | Client certificate, for mutual TLS |
tls.key_name | N* | - | Client private key, for mutual TLS |
tls.passphrase | N | - | Passphrase for an encrypted client private key |
tls.min_tls_version | N | tls1.2 | Lowest protocol version accepted (tls1.0, tls1.1, tls1.2, tls1.3) |
tls.max_tls_version | N | tls1.3 | Highest protocol version accepted (tls1.0, tls1.1, tls1.2, tls1.3) |
* cert_name and key_name are individually optional but must be supplied together — a certificate
without its key, or a key without its certificate, is a configuration error.
When providing tls.cert_name, you must also provide tls.key_name and vice versa.
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 TCP target buffers events in memory and delivers them in batches over a persistent TCP connection.
Framing modes determine how individual messages are delimited in the TCP stream:
- Delimiter (default) appends
line_delimiter(default\n) to each message before concatenation. Compatible with most line-based log receivers. - Octet prepends the byte length of each message per RFC 5425 (e.g.,
42 <message>). Used with syslog-over-TLS receivers that expect length-prefixed framing.
Connection pooling maintains a per-worker TCP connection. Each worker's connection is tested for liveness before each write and automatically recreated on failure. TCP keep-alive is enabled with a 30-second interval.
Batch delivery accumulates events up to batch_size, then concatenates all framed messages into a single write. If delivery fails, the target retries up to max_retries times. Each wait is chosen at random up to a bound that starts from retry_delay, doubles with every attempt, and is capped at 30 seconds. reconnect_interval does not change this in the current release.
Examples
Basic
Forwarding logs to a remote TCP endpoint with default settings... | |
With Batching
Configuring batch size, timeout, and retries for high-volume forwarding... | |
Octet Framing
Using RFC 5425 octet-count framing for syslog-over-TCP receivers... | |
Secure (TLS)
Forwarding logs over TLS with client certificate authentication... | |
With Field Normalization
Normalizing fields to Elastic Common Schema before forwarding... | |
Troubleshooting
This section covers the errors you are most likely to see with the tcp target, what causes each one, and how to fix it.
Where to look:
- Director logs. Target errors are tagged with the target name and carry
"Section":"SenderPool". The part afterReason:or after the last colon is the actual cause. - The target's connection status in the web interface. It shows the same reason as the log line, prefixed with
connection failed for <target name>:.
See Target Delivery Errors for how Director logs and retries target failures.
The receiver is not contacted while the target starts. A closed port, an untrusted certificate, or a client certificate the receiver refuses first appears when a batch is delivered. Until then the connection status shows connected. If data does not arrive, look for execute() failed lines carrying the target name, not only for Failed to reinitialize.
What the receiver needs
| Prerequisite | When it applies | Error when it is missing |
|---|---|---|
A process accepting TCP connections on address and port, reachable from every Director host | Always | failed to connect to ...: connect: connection refused |
| A network path that passes the connection instead of dropping it | Always | failed to connect to ...: i/o timeout |
Resolution of address on the Director host | address is a hostname | invalid address: lookup ...: no such host when the configuration is read, lookup ...: no such host later |
| A receiver certificate that chains to a trusted authority and carries the address you connect to | tls.status: true with the default tls.verify: true | x509: certificate signed by unknown authority, x509: certificate is valid for ..., not ... |
| A client certificate and key issued by an authority the receiver trusts | The receiver asks for a client certificate | remote error: tls: bad certificate |
Read access to the tls.ca_name, tls.cert_name and tls.key_name files, kept under the service root | TLS and mutual TLS | could not be resolved (env/vault token, inline PEM, or a path under the service root) |
| Framing the receiver parses the same way | Always | None. The receiver merges or discards records silently |
"failed to send after 3 retries" with "connection refused"
[Error] [director] [target-<target id>] [tcp_forwarder] Sender worker 1 execute() failed for tcp_forwarder: target broken: failed to finalize target cache: failed to send after 3 retries: failed to connect to collector.example.com:514: dial tcp <receiver ip>:514: connect: connection refused
Cause: no process accepted the connection on that port. The receiver is stopped, it listens on a different port, or it is bound only to its loopback interface. A firewall that rejects the connection instead of dropping it reads the same way. On Windows the wrapped text comes from the operating system and typically says that the target machine actively refused the connection.
Fix: confirm the receiver listens on port over TCP, and that it is bound to an address the Director host can reach. Open a connection from the Director host to the same address and port to reproduce the failure outside Director. Correct address and port if they name the wrong endpoint.
Retried until fixed, and nothing is dropped. The batch is retried up to max_retries times, then the payload is handed back and redelivered about every 5 seconds until a write succeeds. Data waits in the queue meanwhile.
Each attempt is also logged on its own as [TCP] send-error ... attempt=N err=..., and the last one as [TCP] send-failed ... lastErr=.... The number in after 3 retries is your max_retries.
"i/o timeout" while connecting or while writing
[Error] [director] [target-<target id>] [tcp_high_volume] Sender worker 2 execute() failed for tcp_high_volume: target broken: failed to finalize target cache: failed to send after 5 retries: failed to connect to collector.example.com:5000: dial tcp <receiver ip>:5000: i/o timeout
[Error] [director] [target-<target id>] [tcp_forwarder] Sender worker 4 execute() failed for tcp_forwarder: target broken: failed to finalize target cache: failed to send after 3 retries: failed to write data: write tcp <director ip>:<port>-><receiver ip>:514: i/o timeout
Cause: the first form means the connection attempt got no answer at all. Something drops the packets on the way, usually a network firewall or a security group rule, or the host is off. The second form means the connection was open but the receiver stopped reading, so the batch could not be written in time. A receiver that is out of disk, stalled, or slower than the incoming rate produces it.
timeout bounds both the connection attempt and the write of each batch. With the default of 30 seconds and the default max_retries, a payload can spend about two minutes failing before the error is logged.
Fix: for the connect form, correct the routing and firewall rules between the two hosts. For the write form, check the receiver's health and throughput. Lower timeout when you want failures reported sooner. Raise it only when the receiver is known to be slow rather than broken.
Retried until fixed, and nothing is dropped. A write that stopped part way through is retried on a fresh connection, so the receiver can see the part it already accepted a second time. Expect duplicates at the receiver after this error, not loss.
"connection not alive: EOF" after a quiet period
[Warning] [director] [target-<target id>] [tcp_forwarder] [TCP] send-error file=<payload file> target=tcp_forwarder batch=1000 pending=1000 attempt=0 err=connection not alive: EOF
Cause: the connection was closed while the target had nothing to send, and the close is noticed at the next batch. The receiver restarted, or an idle timeout closed the session on the receiver, on a load balancer, or in a firewall session table. EOF means the other end closed cleanly. read: connection reset by peer means it was reset instead, and Windows typically words the same condition as an existing connection forcibly closed by the remote host.
Fix: none when the next attempt succeeds. The connection is tested before every write, so the batch is still intact and goes out on a new connection. If the warning returns at a regular interval, raise the idle timeout on whatever closes the session, or keep the connection busier by not scheduling long interval values. If it repeats and no attempt ever succeeds, treat it as a refusal by the receiver and read the receiver's own log.
No data is lost while a later attempt succeeds. If every attempt fails, the payload is redelivered as in the entry above.
"x509: certificate signed by unknown authority"
[Error] [director] [target-<target id>] [tcp_tls] Sender worker 1 execute() failed for tcp_tls: target broken: failed to finalize target cache: failed to send after 3 retries: failed to connect to collector.example.com:6514: tls: failed to verify certificate: x509: certificate signed by unknown authority
Cause: the receiver's certificate does not chain to an authority Director trusts. A private or self-signed authority is the usual reason. tls.ca_name replaces the host trust store rather than adding to it, so a bundle that holds only your private authority makes publicly issued certificates fail, and the wrong bundle makes every connection fail.
Fix: set tls.ca_name to the bundle holding the issuing authority, including any intermediates. The file must be PEM, starting with -----BEGIN CERTIFICATE-----, and must sit under the service root. A path outside the service root is refused when the target starts, with could not be resolved (env/vault token, inline PEM, or a path under the service root), and a file in another encoding with contains no valid PEM certificate(s). Use tls.verify: false only on a network you control, and note that it turns off the name check as well.
The related x509: certificate has expired or is not yet valid means the receiver's certificate is out of date, or the clock on the Director host is wrong. Both are retried until fixed, and nothing is dropped.
"x509: certificate is valid for ..., not ..."
[Error] [director] [target-<target id>] [tcp_tls] Sender worker 1 execute() failed for tcp_tls: target broken: failed to finalize target cache: failed to send after 3 retries: failed to connect to <receiver ip>:6514: tls: failed to verify certificate: x509: certificate is valid for collector.example.com, not <receiver ip>
Cause: the address you connect to is not one the certificate carries. Connecting by IP address is the common case, because most receiver certificates list names only. The variant certificate is valid for 1 IP SANs, but none matched ... means the certificate does list addresses, but not this one.
Fix: set tls.server_name to a name the certificate carries. That name is both sent to the receiver and checked against the certificate, so address may stay an IP address. Using the certificate's name as address works too, when it resolves from the Director host. Retried until fixed, and nothing is dropped.
"tls: first record does not look like a TLS handshake"
[Error] [director] [target-<target id>] [tcp_tls] Sender worker 3 execute() failed for tcp_tls: target broken: failed to finalize target cache: failed to send after 3 retries: failed to connect to collector.example.com:514: tls: first record does not look like a TLS handshake
Cause: tls.status is true but the port speaks plain TCP. Port 514 where the receiver expects 6514 is the usual mix-up.
Fix: point port at the encrypted port, or set tls.status: false for a plaintext receiver. Retried until fixed. The opposite mistake is harder to read: plaintext sent to a TLS port is accepted and then closed, which shows up as repeated connection not alive: EOF or failed to write data: ... broken pipe warnings instead.
Errors that start with "remote error: tls:"
[Error] [director] [target-<target id>] [tcp_tls] Sender worker 1 execute() failed for tcp_tls: target broken: failed to finalize target cache: failed to send after 3 retries: failed to connect to collector.example.com:6514: remote error: tls: bad certificate
Cause: the receiver rejected the handshake, so the text after remote error: tls: is the receiver's verdict rather than Director's. bad certificate and unknown certificate authority mean it asked for a client certificate and refused the one it was given, or none was configured. protocol version not supported and handshake failure mean it accepts no version or cipher in the range offered, which runs from tls1.2 to tls1.3 by default.
Fix: for the certificate texts, set tls.cert_name and tls.key_name to a certificate and key issued by an authority the receiver trusts. The two fields are required together. For the version texts, upgrade the receiver, or lower tls.min_tls_version when that is not possible. If a client certificate was replaced in place and the new file is unreadable or not valid PEM, the previous one stays in use with no message, and the error appears only later, when the old certificate expires. Check the new file before replacing the old one.
Retried until fixed, and nothing is dropped while the handshake keeps failing.
Zero does not mean "no limit" or "no retries"
timeout, batch_size, max_retries, retry_delay and reconnect_interval read 0 as "not set" and use their default instead. The value you might expect to switch a limit off does the opposite:
| Setting | What 0 looks like it does | What it does |
|---|---|---|
max_retries | Deliver once, never retry | 3 retries, so 4 attempts |
retry_delay | Retry with no wait | Waits as though set to 1 |
timeout | Wait indefinitely | 30 seconds |
batch_size | No batch limit | 1000 events |
Use 1 or more for all of them. max_retries: 0 is accepted when the configuration is read and then behaves as 3, so a single attempt per batch cannot be configured. Negative values are rejected instead, with batch_size must be greater than 0, got -5 or max_retries must be zero or greater, got -1, and the target does not start until they are corrected.
reconnect_interval is accepted but does not change delivery in the current release. The wait before a retry comes from retry_delay alone. It is chosen at random up to a bound that doubles with every attempt and is capped at 30 seconds.
The target looks healthy but nothing arrives
Check these in order.
debug.dont_send_logsis enabled. Events are processed and then discarded rather than sent, and the payload is marked delivered. Withdebug.statusalso enabled, startup logsLog sending is disabled for this target (tcp_forwarder). Logs will be processed by the pipeline but will not be sent to the target.Withdebug.statusoff, nothing is logged at all.- The framing does not match the receiver.
framing: delimitersent to a receiver that expects length-prefixed records, orframing: octetsent to a line-based one, typically leaves the receiver splitting, merging or discarding records on its own. Nothing is reported back. Matchframingto what the receiver parses. line_delimiterwas quoted in a way that changed it. A single-quoted newline in YAML is appended as two literal characters, a backslash and ann. Write it double-quoted, asline_delimiter: "\n". An explicit empty value concatenates records with no separator at all. Remove the field to keep the default.- Records carry their own line breaks. With delimiter framing the message is written as it is, so a record holding a carriage return or a line feed typically arrives as several records. Escape or strip line breaks in the pipeline, or use
framing: octet. - The port does not speak plain TCP. A receiver that answers with a banner, or an encrypted port addressed without
tls.status: true, still looks alive. Writes succeed for a while and then fail withbroken pipeorEOF, while the sent counters keep climbing. - The receiver went away mid-write. The connection is tested before each write, but nothing acknowledges the records themselves. Records written into a connection that is closing are counted as sent. Confirm arrival at the receiver rather than relying on the target's counters.