# Syslog receiver

Takes what syslog senders send over TCP, plain or under TLS, or over UDP: rsyslog, syslog-ng, network devices and appliances. Each message is a record, exactly as the sender wrote it.

Every worker in the group listens on its port, and a sender, or a balancer in front of the workers, picks one. Add workers to take more.

## Example

Takes syslog on port 6514 over TLS with the [identity](/docs/secrets#identities) `syslog-tls`, from senders in `10.20.0.0/16` and from the one host `192.168.1.20`.

```
{
  "id": "network-syslog",
  "type": "syslogReceiver",
  "port": 6514,
  "allowFrom": [
    "10.20.0.0/16",
    "192.168.1.20"
  ],
  "identity": "syslog-tls"
}
```

![The syslog receiver form on the Sources page: id network-syslog, port 6514, transport tcp, allow from 10.20.0.0/16 and 192.168.1.20.](/syslog-receiver-source-form-light.png)

The same source in the Sources page's add dialog, before you pick its identity.

## Before you start

-   Pick TCP or UDP. See [TCP or UDP](#transport).
-   Pick a port no other receiver of that transport in the group uses.
-   Decide how senders are admitted: the networks they connect from, a client certificate, or both. See [Who it admits](#senders).
-   For TLS, add an [identity](/docs/secrets#identities) with the receiver's certificate and key, and give each sender the authority that signed that certificate.
-   Open the port on every worker, and give senders the address of each, or of a balancer in front of them. See [Open its port](#port).

## TCP or UDP

`transport` is `tcp` unless the receiver gives `udp`. Take TCP wherever the sender can send it. Take UDP for senders that send only UDP.

Over UDP each datagram is one message (RFC 5426). UDP costs this:

-   **No TLS.** Messages cross the network in the clear, and the leader refuses a UDP receiver that names an `identity`.
-   **No backpressure.** Nothing slows a UDP sender. A datagram that arrives while the receiver is full or [blocked](/docs/delivery#hold) is dropped, and counted in [`smeltr_source_dropped_total`](/docs/logs-metrics#metrics).
-   **Spoofable senders.** `allowFrom` is the only thing that admits a UDP sender, and a datagram's source address is whatever its sender writes. See [Who it admits](#senders).

A TCP and a UDP receiver may share a port number, so one group can take syslog on TCP 514 and UDP 514.

## Settings

It also takes the settings [every source](/docs/sources#settings) takes.

`port`  integer required

The port it listens on. Every worker of the group binds it, so no two receivers of one `transport` share one; a TCP and a UDP receiver may.

Examples:

- `6514`
- `1514`

`transport`  string default `tcp`

What senders send over.

`tcp`

Takes connections, under TLS when it names an `identity`. A full receiver stops reading, which slows the sender.

`udp`

Takes datagrams, one message each, for senders that send only UDP. No TLS, and nothing slows a sender: a datagram that arrives while the receiver is full or blocked is dropped and counted in `smeltr_source_dropped_total`. It needs `allowFrom`.

`allowFrom`  list of strings default every address TLS admits

The networks a sender may connect from: CIDR blocks such as `10.0.0.0/8`, or single addresses. A connection from anywhere else is closed before any of it is read, and a datagram from anywhere else is dropped. Without it, a TCP receiver needs an `identity` with authorities, and admits every sender whose certificate chains to one of them. A UDP receiver needs it. A datagram's source address is whatever its sender writes, so over UDP it admits whoever can reach the port and claim an address in these networks.

Examples:

- `10.0.0.0/8`
- `10.20.0.0/16`
- `192.168.1.20`
- `fd00::/8`

### Advanced

`identity`  identity default plain TCP

The [connection identity](/docs/secrets#identities) it serves TLS under (RFC 5425): its certificate and key. With authorities, a sender must present a certificate that chains to one of them. A UDP receiver takes none.

Example:

- `syslog-tls`

`maxRequestBytes`  integer default `10485760`

The largest message it reads, between 1024 and 67108864. A larger one closes the connection; a larger datagram is dropped.

Examples:

- `65536`
- `1048576`

`encoding`  string default `auto`

How a message's bytes become `_raw`'s text: `auto`, `none`, or a WHATWG encoding label other than the replacement ones, such as `utf-8` or `windows-1252`. Syslog over TCP names no charset, and an older sender may write Latin-1.

`auto`

Detects each message's encoding.

`utf-8`

Decodes every message as UTF-8, as any other label decodes as its own encoding. Bytes that do not decode become U+FFFD.

`none`

Decodes nothing: the record has no `_raw`, and its payload is `__source.bytes` alone. A pipeline reads it as text with `decode(__source.bytes, encoding)`.

`lineBreaker`  string default a message per line

Where a message ends. The first capture group is required and will be dropped. E.g. `(\x00)` ends a message at a NUL, and `()CEF:0\|` starts a new one at each CEF record. TCP only.

Examples:

- `(\x00)`
- `()CEF:0\|`

## Who it admits

Anyone a receiver admits can put records into the group's pipelines. Syslog carries no token, so a receiver needs `allowFrom`, an `identity` with authorities, or both. The leader refuses a receiver with neither.

-   **Networks:** `allowFrom` lists the networks a sender may connect from, as CIDR blocks such as `10.0.0.0/8` or as single addresses. The receiver closes a connection from any other address before reading any of it.
-   **Client certificates:** with an [identity](/docs/secrets#identities), the receiver serves TLS (RFC 5425) with the identity's certificate and key. If the identity has authorities, every sender must present a certificate that chains to one of them. With `allowFrom` empty, every sender TLS admits is admitted.

With both, a sender must connect from a listed network and present a certificate.

Over UDP there is no TLS, so `allowFrom` is required and is all that admits a sender. The receiver drops a datagram from any other address. It is weaker than over TCP: a TCP sender must answer from its address to connect, but a datagram's source address is whatever its sender writes, so anyone who can reach the port can claim an address in `allowFrom`. Take UDP only where the network between senders and workers drops datagrams that forge their source, and take TCP with an identity wherever a sender offers it.

`allowFrom` checks the address the connection or datagram comes from. A balancer or address translation in front of the workers makes that the balancer's address, unless it keeps the sender's.

Without an identity, messages cross the network in the clear. Serve TLS wherever the network between senders and workers is not trusted.

## Send from rsyslog

Use rsyslog's [omfwd](https://www.rsyslog.com/doc/configuration/modules/omfwd.html) action with `TCP_Framing="octet-counted"`, so a message holding a newline arrives as one record. This example sends over TLS, checks the receiver's certificate against `smeltr-ca.pem`, and queues on disk while the receiver is out of reach:

```
global(
  DefaultNetstreamDriver="gtls"
  DefaultNetstreamDriverCAFile="/etc/rsyslog.d/smeltr-ca.pem"
)

action(
  type="omfwd"
  target="worker1.example.com"
  port="6514"
  protocol="tcp"
  TCP_Framing="octet-counted"
  StreamDriver="gtls"
  StreamDriverMode="1"
  StreamDriverAuthMode="x509/name"
  StreamDriverPermittedPeers="workers.example.com"
  template="RSYSLOG_SyslogProtocol23Format"
  queue.type="LinkedList"
  queue.filename="smeltr"
  queue.saveOnShutdown="on"
  action.resumeRetryCount="-1"
)
```

For a client certificate, add `DefaultNetstreamDriverCertFile` and `DefaultNetstreamDriverKeyFile` to `global`. Without TLS, leave out the `StreamDriver` settings and send to the plain port:

```
action(
  type="omfwd"
  target="worker1.example.com"
  port="1514"
  protocol="tcp"
  TCP_Framing="octet-counted"
  template="RSYSLOG_SyslogProtocol23Format"
)
```

To a UDP receiver, use `protocol="udp"`:

```
action(
  type="omfwd"
  target="worker1.example.com"
  port="514"
  protocol="udp"
  template="RSYSLOG_SyslogProtocol23Format"
)
```

## Send from syslog-ng

This example uses syslog-ng's [syslog()](https://syslog-ng.github.io/admin-guide/070_Destinations/170_syslog/README) driver over TLS and checks the receiver's certificate against `smeltr-ca.pem`:

```
destination d_smeltr {
  syslog("worker1.example.com"
    transport("tls")
    port(6514)
    tls(ca-file("/etc/syslog-ng/smeltr-ca.pem"))
  );
};

log { source(s_src); destination(d_smeltr); };
```

For a client certificate, add `cert-file()` and `key-file()` to `tls()`. Without TLS, use `transport("tcp")` and the plain port.

To a UDP receiver, use `transport("udp")`:

```
destination d_smeltr {
  network("worker1.example.com"
    transport("udp")
    port(514)
  );
};
```

## Messages it takes

-   RFC 5424 messages, and RFC 3164 messages, the older BSD format.
-   Messages are framed by their octet count, as `123 <34>1 ...`, or end at a newline (RFC 6587). Set `lineBreaker` to end them at its pattern instead.
-   Plain TCP, or TLS (RFC 5425) when the receiver names an identity.
-   Over UDP, one message to a datagram (RFC 5426). A newline, or carriage return and newline, that ends the datagram is dropped. An empty datagram gives no record.
-   A message larger than `maxRequestBytes` closes the connection. The sender connects again and goes on from where its own rules take it. A datagram larger than it is dropped.

An RFC 5424 message, as it arrives in `_raw`:

```
<165>1 2026-10-11T22:14:15.003Z fw01.example.com sshd 4123 AUTH [origin ip="10.20.0.7"] Accepted publickey for deploy
```

An RFC 3164 message:

```
<34>Oct 11 22:14:15 fw01 su: 'su root' failed for deploy on /dev/pts/8
```

## Fields it gives

Each message is a record. Its `_raw` is the message as the sender sent it, its `<PRI>` header included. `__source.remote` holds the address the sender's connection or datagram came from.

| Field | Type | Holds |
| --- | --- | --- |
| `__source.name` | `str` | The name of the source that read the record. |
| `__source.type` | `str` | The kind of source that read it, its `type`, such as `kafka`, `s3List`, `httpApi` or `hecReceiver`. |
| `__source.remote` | `str` | The address of the sender that delivered the record to a receiver. |
| `__source.bytes` | `bytes` | The record's payload as the source read it, before any decoding. The source decodes `_raw` from it. A `bytes` destination writes it unless the destination names another field. Text functions refuse it; `decode(__source.bytes, encoding)` reads it as text. |

## Parse the header

The receiver does not parse the header, since every vendor writes syslog its own way. A [Parser step](/docs/steps/parser) with the [Regex/Grok](/docs/steps/parser#grok-matching) format reads it in the pipeline, where each source's quirks can have a pattern of their own. This step reads an RFC 5424 header with its first pattern and an RFC 3164 header with its second:

```
{
  "id": "syslog-header",
  "type": "parser",
  "input": "_raw",
  "kind": {
    "type": "grok",
    "patterns": [
      "^<%{INT:pri}>%{INT:version} %{TIMESTAMP_ISO8601:time} %{NOTSPACE:host} %{NOTSPACE:app} %{NOTSPACE:procid} %{NOTSPACE:msgid} (?<sd>-|(?:\\[[^\\]]*\\])+)(?: %{GREEDYDATA:message})?$",
      "^<%{INT:pri}>%{SYSLOGTIMESTAMP:time} %{SYSLOGHOST:host} %{DATA:app}(?:\\[%{POSINT:procid}\\])?: %{GREEDYDATA:message}$"
    ]
  },
  "declared": [
    {
      "name": "pri",
      "type": "i64"
    },
    {
      "name": "time",
      "type": "str"
    },
    {
      "name": "host",
      "type": "str"
    },
    {
      "name": "app",
      "type": "str"
    },
    {
      "name": "procid",
      "type": "str"
    },
    {
      "name": "msgid",
      "type": "str"
    },
    {
      "name": "sd",
      "type": "str"
    },
    {
      "name": "message",
      "type": "str"
    }
  ]
}
```

Given these two messages as `_raw`:

```
<165>1 2026-10-11T22:14:15.003Z fw01.example.com sshd 4123 AUTH [origin ip="10.20.0.7"] Accepted publickey for deploy
```

```
<34>Oct 11 22:14:15 fw01 su: 'su root' failed for deploy on /dev/pts/8
```

it gives these fields:

```
{
  "pri": 165,
  "version": "1",
  "time": "2026-10-11T22:14:15.003Z",
  "host": "fw01.example.com",
  "app": "sshd",
  "procid": "4123",
  "msgid": "AUTH",
  "sd": "[origin ip=\"10.20.0.7\"]",
  "message": "Accepted publickey for deploy"
}
```

```
{
  "pri": 34,
  "time": "Oct 11 22:14:15",
  "host": "fw01",
  "app": "su",
  "message": "'su root' failed for deploy on /dev/pts/8"
}
```

The facility is `pri / 8` and the severity `pri % 8`: 165 is facility 20, `local4`, at severity 5, notice. To make `time` a datetime, declare it with a format; see [Parser step](/docs/steps/parser).

## Delivery

Syslog has no acknowledgement. The receiver never tells a sender a message arrived, so a sender counts a message delivered once it has written it to the connection.

-   Delivery is best-effort. What a connection carries when it is cut is lost, and so is what a worker holds and has not yet delivered when it stops.
-   A receiver that is full, or [blocked](/docs/delivery#hold), stops reading from its connections. The sender's TCP window fills, and the sender buffers or drops by its own rules. Give rsyslog a disk-assisted queue, as in the [example above](#rsyslog), or syslog-ng a `disk-buffer()`, so that a pause holds messages rather than losing them.
-   Over UDP nothing slows the sender. A receiver that is full or blocked drops each datagram that arrives, and counts it in [`smeltr_source_dropped_total`](/docs/logs-metrics#metrics). On a Linux or other Unix host, datagrams wait in the host's receive buffer while a worker deploys a new configuration, until that buffer fills.

## Open its port

Every worker in the group listens on the receiver's `port`. Open that port to senders on each worker, for TCP or UDP as its `transport` says. A balancer in front of a UDP receiver must balance UDP and keep each sender's source address, or `allowFrom` sees the balancer's. A sender names one address: a balancer in front of the workers, or a DNS name that resolves to each of them.

-   On Kubernetes, add the port to the worker pods: [Expose a receiver](/docs/enroll-worker/kubernetes#receiver).
-   On a host or in a container, open the host's firewall or publish the container's port: [Enroll a worker on a host](/docs/enroll-worker/host).

## Verify

1.  Point a sender at the receiver, as in [Send from rsyslog](#rsyslog).
2.  On the sender, run `logger -t smeltr-test hello` to log a line you can recognize. To send it straight to a UDP receiver, run `logger --udp --server worker1.example.com --port 514 -t smeltr-test hello`.
3.  Find it at the destination, its `_raw` starting with its PRI header.

## Troubleshooting

**The sender cannot connect**

Check that the port is open on every worker and that the sender's address reaches them. An identity with authorities refuses a connection without a certificate that chains to one of them.

**The sender connects and is closed at once**

Its address is in none of the networks `allowFrom` lists. Behind a balancer or address translation, the address is the balancer's.

**A message with several lines arrives as one record per line**

The sender ends messages with a newline. Set it to frame them by octet count: `TCP_Framing="octet-counted"` in rsyslog, or syslog-ng's `syslog()` driver.

**The connection closes on a long message**

The message is larger than `maxRequestBytes`. Raise it, or have the sender cut its messages shorter.

**Nothing arrives, and the sender's queue grows**

Check the source's status. A blocked receiver reads nothing until its destinations take records again. A full one takes more with more workers.

**Datagrams are missing, and `smeltr_source_dropped_total` grows**

The UDP receiver was full or blocked when they arrived. Check the source's status. A blocked receiver takes nothing until its destinations take records again; a full one takes more with more workers. A sender that can send TCP loses nothing to a full receiver over it.

**Datagrams are missing, and nothing is counted**

The receiver drops a datagram from an address outside `allowFrom`, and one larger than `maxRequestBytes`, before counting anything. A datagram can also be lost on the network, or in the host's receive buffer when it fills.

**Messages are missing after a worker stopped**

Syslog has no acknowledgement, so what was in flight when the connection was cut is lost. See [Delivery](#delivery).

## See also

-   [Sources](/docs/sources)
-   [Parser step](/docs/steps/parser)
-   [Credentials](/docs/secrets)
-   [Delivery](/docs/delivery)
