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.