Andrew Mercer
on this page

rsyslog is the traditional syslog daemon on Debian/Ubuntu/RHEL. It reads from journald (via imjournal on RHEL, or via the syslog socket on Debian/Ubuntu), filters, formats, and writes to files or forwards to remote systems.

Configuration layout

/etc/rsyslog.conf          # main config: modules, globals, default rules
/etc/rsyslog.d/*.conf      # drop-ins, processed in lexical order where $IncludeConfig / include() appears

Order matters. rsyslog processes rules top to bottom, and drop-ins are pulled in wherever the include directive sits. A rule in 10-firewall.conf runs before the defaults in 50-default.conf. If you want a rule to divert messages away from the default files, it must run first and then stop.

Always validate before restarting:

sudo rsyslogd -N1                   # syntax check, level 1
sudo systemctl restart rsyslog
sudo systemctl status rsyslog
journalctl -u rsyslog -n 50         # rsyslog reports its own config errors here

Two syntaxes: legacy and RainerScript

You'll see both in the wild. Legacy ($Directive, facility.priority /path) still works and is fine for simple selector rules. RainerScript (module(), input(), action(), if … then) is the modern syntax and is required for anything non-trivial (queues, TLS, rulesets). Prefer RainerScript for new configs; don't mix both styles for the same module.

Legacy RainerScript equivalent
$ModLoad imtcp module(load="imtcp")
$InputTCPServerRun 514 input(type="imtcp" port="514")
$UDPServerRun 514 input(type="imudp" port="514")
$template Name,"…" template(name="Name" type="string" string="…")
*.* @@host:514 *.* action(type="omfwd" target="host" port="514" protocol="tcp")
& ~ & stop

Selector rules (facility.priority → action)

# facility.priority                   action
kern.*                                -/var/log/kern.log
auth,authpriv.*                       /var/log/auth.log
*.*;auth,authpriv.none                -/var/log/syslog
mail.err                              /var/log/mail.err
cron.*                                /var/log/cron.log
local4.*                              /var/log/network-gear.log
*.emerg                               :omusrmsg:*

Selector semantics:

Syntax Meaning
mail.err err and more severe
mail.=err exactly err
mail.!err less severe than err only (excludes err and above)
mail.* all severities
mail.none exclude this facility
auth,authpriv.* multiple facilities, same priority
a.*;b.none combine selectors (;) — e.g. "everything except b"

Action prefixes (legacy):

Prefix Meaning
/path write to file
-/path write to file without syncing after each line (modern rsyslog doesn't sync by default anyway)
?Template dynamic file name from template
@host forward over UDP
@@host forward over TCP
:omusrmsg:* message all logged-in users
stop (formerly ~) discard / stop processing this message

Enabling cron logging (Debian/Ubuntu)

Cron messages go to the general syslog file by default. To split them out, uncomment or add the rule. On Ubuntu it lives in /etc/rsyslog.d/50-default.conf; on Debian it's in /etc/rsyslog.conf itself. Cleaner still is a dedicated drop-in:

echo 'cron.*  /var/log/cron.log' | sudo tee /etc/rsyslog.d/40-cron.conf
sudo rsyslogd -N1 && sudo systemctl restart rsyslog

Optionally exclude cron from the general file by changing *.*;auth,authpriv.none to *.*;auth,authpriv.none;cron.none in the default rules. Then add /var/log/cron.log to the rsyslog logrotate stanza (see logrotate).

On RHEL, cron already goes to /var/log/cron. On journal-only systems: journalctl -u cron (Debian) or journalctl -u crond (RHEL).

Filters

Property-based filters (compact, legacy-compatible):

:programname, isequal, "haproxy"      /var/log/haproxy.log
& stop
:msg, contains, "nft-drop: "          /var/log/firewall.log
& stop
:fromhost-ip, isequal, "192.0.2.10"   /var/log/remote/router1.log
& stop

Operators include contains, isequal, startswith, regex, ereregex, and negations like !contains. & applies another action to the same filter match.

Expression-based filters (RainerScript — clearer and more powerful):

if $programname == "haproxy" then {
    action(type="omfile" file="/var/log/haproxy.log")
    stop
}

if $syslogseverity <= 3 and $syslogfacility-text == "daemon" then {
    action(type="omfile" file="/var/log/daemon-errors.log")
}

if re_match($msg, "Out of memory: Killed process [0-9]+") then {
    action(type="omfile" file="/var/log/oom.log")
}

Common message properties: $msg, $rawmsg, $hostname, $fromhost, $fromhost-ip, $programname, $syslogtag, $syslogfacility-text, $syslogseverity, $syslogseverity-text, $timereported, $timegenerated, $inputname.

Templates

Templates control both output format and dynamic file names.

# Dynamic file name per host and facility
template(name="PerHostFacility" type="string"
         string="/var/log/remote/%HOSTNAME%/%syslogfacility-text%.log")

# Line format with RFC 3339 timestamps
template(name="Rfc3339Line" type="string"
         string="%timereported:::date-rfc3339% %HOSTNAME% %syslogtag%%msg:::sp-if-no-1st-sp%%msg:::drop-last-lf%\n")

# JSON lines, ready for Vector/Fluent Bit/Logstash/Loki
template(name="JsonLines" type="list" option.jsonf="on") {
    property(outname="timestamp" name="timereported" dateFormat="rfc3339" format="jsonf")
    property(outname="host"      name="hostname"            format="jsonf")
    property(outname="facility"  name="syslogfacility-text" format="jsonf")
    property(outname="severity"  name="syslogseverity-text" format="jsonf")
    property(outname="program"   name="programname"         format="jsonf")
    property(outname="message"   name="msg"                 format="jsonf")
}

action(type="omfile" file="/var/log/all.json" template="JsonLines")

Built-in templates include RSYSLOG_TraditionalFileFormat, RSYSLOG_FileFormat (RFC 3339 high-precision timestamps), RSYSLOG_SyslogProtocol23Format (RFC 5424) and RSYSLOG_ForwardFormat.

High-precision timestamps

Debian/Ubuntu ship with the low-precision traditional format. Switch files to RFC 3339 timestamps (2026-10-09T22:14:15.003412-04:00) by replacing the legacy directive in /etc/rsyslog.conf:

# Remove or comment out:
# $ActionFileDefaultTemplate RSYSLOG_TraditionalFileFormat

# Modern equivalent (set the default template for all omfile actions):
module(load="builtin:omfile" template="RSYSLOG_FileFormat")

Anything parsing these files (fail2ban, logwatch, custom scripts) needs to understand the new format, so test before rolling out widely.

Rate limiting

rsyslog rate-limits local input to protect itself from runaway processes. If you see imjournal: … messages lost due to rate-limiting or imuxsock … begins to drop messages, raise or disable the limits deliberately:

# RHEL (imjournal)
module(load="imjournal" StateFile="imjournal.state" ratelimit.interval="60" ratelimit.burst="50000")

# Debian/Ubuntu (imuxsock)
module(load="imuxsock" SysSock.RateLimit.Interval="5" SysSock.RateLimit.Burst="10000")

Remember journald has its own limits (RateLimitIntervalSec/RateLimitBurst) upstream of rsyslog. The real fix for a noisy service is usually to make it less noisy.