← All rules
// Hardening (posture)higheffective runtime

journald forwards to the syslog daemon that is actually running

If a syslog daemon is running, journald must forward to it (ForwardToSyslog=yes). This pavois-native control asserts the other half of the CIS requirement: the norms demand that a syslog daemon be installed and enabled, but nothing checks that it still receives anything. A host where rsyslog is active (running) and /var/log/syslog has not gained a line since boot is compliant on paper and blind in practice.

Checked against the resolved running state (e.g. sshd -T, sysctl, systemctl show), catches drop-ins and Includes a file read would miss. Caveat: runtime ≠ persistence; a value correct now may not survive a reboot.

A pass proves✓ running now✓ on disk✓ survives rebootthe qualified verdict →
Debian 12CIS 1.1.0Debian 13CIS 1.0.0FedoraRHEL 10 / Rocky 10 / AlmaLinux 10RHEL 8 / Rocky 8 / AlmaLinux 8CIS 4.0.0RHEL 9 / Rocky 9 / AlmaLinux 9CIS 2.0.0Ubuntu 22.04CIS 3.0.0Ubuntu 24.04CIS 1.0.0Ubuntu 26.04

What Pavois checks

Two layers. An only_if scopes the control to the transport that actually depends on forwarding: it runs when rsyslog or syslog-ng is running and rsyslog does not load the imjournal module (the Debian and Ubuntu default is imuxsock alone, so without forwarding rsyslog receives nothing). Where rsyslog reads the journal directly through imjournal (the RHEL and Fedora default), forwarding is redundant and the control is skipped, not failed. The check then reads the effective value with systemd-analyze cat-config systemd/journald.conf, keeping the last ForwardToSyslog= line: that merges the vendor file, /etc/systemd/journald.conf and every drop-in, so a no reintroduced downstream is caught. Grepping the main file alone would miss exactly that.

only_if { (service('rsyslog').running? || service('syslog-ng').running?) && !command('grep -rqsE \'^[[:space:]]*(module\\(load="imjournal"|\\$ModLoad[[:space:]]+imjournal)\' /etc/rsyslog.conf /etc/rsyslog.d/ 2>/dev/null').exit_status.zero? }
describe command('systemd-analyze cat-config systemd/journald.conf 2>/dev/null | grep -iE "^[[:space:]]*ForwardToSyslog[[:space:]]*=" | tail -1 | grep -qiE "=[[:space:]]*(no|false|0)" && echo ko || echo ok') do
  its('stdout.strip') { should eq 'ok' }
end

How to verify it is applied

Read the resolved value, then prove the pipe end to end:

systemd-analyze cat-config systemd/journald.conf | grep -i '^ForwardToSyslog'
logger -t pavois-test 'forwarding check'
tail -n 5 /var/log/syslog        # /var/log/messages on RHEL

The test line must appear in the file within seconds. That end-to-end check, not the config value, is what the incident behind this control would have caught.

Inspect & investigate

The tell-tale is the freshness of the file-based logs: compare stat -c %y /var/log/syslog (or /var/log/messages) with the machine's uptime. An mtime frozen at boot time, on a host whose systemctl status rsyslog is green, is the exact fingerprint of broken forwarding. journalctl -u systemd-journald shows the daemon restarting once the drop-in is applied.

Remediation

Pavois writes a dedicated drop-in /etc/systemd/journald.conf.d/99-pavois-forward.conf containing only:

[Journal]
ForwardToSyslog=yes

It then removes the legacy aggregated drop-in 99-pavois.conf that used to pin ForwardToSyslog=no, and restarts systemd-journald and rsyslog. The one-setting-per-drop-in shape is the whole point: it is what stops another journald control from silently clobbering this one on the next confkit harden apply.

Pavois applies this with its own harden engine, the plan below, not a shell script:

commandmkdir -p /etc/systemd/journald.conf.d && printf '[Journal]\nForwardToSyslog=yes\n' > /etc/systemd/journald.conf.d/99-pavois-forward.conf && rm -f /etc/systemd/journald.conf.d/99-pavois.conf && systemctl restart systemd-journald 2>/dev/null; systemctl restart rsyslog 2>/dev/null; true
namejournald-forward-to-running-syslog
not_if! test -e /etc/systemd/journald.conf.d/99-pavois.conf && ! systemd-analyze cat-config systemd/journald.conf 2>/dev/null | grep -iE "^[[:space:]]*ForwardToSyslog[[:space:]]*=" | tail -1 | grep -qiE "=[[:space:]]*(no|false|0)"
resourceexec
pavois harden plan local

where the target is local, a user@host SSH alias, or a container , Docs

Impact & precautions

This control was born from a pavois bug: three journald controls wrote the same aggregated file, whose content pinned ForwardToSyslog=no. rsyslog stayed active (running), /var/log/syslog had not received a single line since boot, and every control was green, while the entire chain that reads log files (SIEM shipping, logrotate, post-incident forensics) was dead. Enabling forwarding stores each message twice (in the journal and in the syslog files), so it roughly doubles log disk usage: pair it with growth-journal-bounded and growth-logrotate-active so neither store can grow without a bound. On RHEL and Fedora with imjournal, the control is skipped and nothing changes.

0