Linux logging: journald, rsyslog, remote syslog and logrotate

Linux logging is the chain that writes down what happened on a server and keeps it for as long as you need it: the journal that collects every message, the text files rsyslog writes, a copy sent to a central log server, and the rotation that stops all of it from filling the disk. When a log you need is missing, one link in that chain was set up wrong, usually months before anyone looked.

In Lesson 24 you searched logs that were already there. Here you decide where they go and how long they stay: you make the journal on rocky01 survive reboots with a size limit, write your own rsyslog rule, turn ubu01 into a small log server that receives rocky01’s messages on TCP port 514, and rotate the new log file with logrotate after testing it with a dry run.

Where this lesson fits

Lesson 29 of 82 in Linux Administration · Phase 2: Day-to-day administration and the command builder · Before this: Lesson 28, cron, at and systemd timers · Next: Lesson 30, Linux networking basics

TimeYou needBy the end you can
About 75 minutesrocky01 and ubu01 from Lesson 2, both running, and an SSH session to each as studentMake the journal persistent with a size cap, route a facility to its own file, forward logs to a remote syslog server, and rotate a log safely after a logrotate dry run

What it is, in one line

Linux logging is journald collecting every message, rsyslog sorting copies into text files or onto the network, and logrotate plus journald’s own limits clearing out the old ones.

In Simple Words

The register at the society gate

Think of the security guard at the gate of a big housing society. He writes every visitor, delivery and cab in one thick register with the time, the flat number and who let them in. That register is the only complete record, and if it is left on the desk in the rain, everything in it is gone.

The society secretary copies the entries she cares about into separate notebooks, one for deliveries and one for guests, and every evening a photocopy of the day’s page goes to the builder’s office in town. Each month the full register goes into a carton in the store room, and cartons older than a year are thrown out so the store room never overflows.

The register is journald, the notebooks are rsyslog’s files, the photocopy is remote syslog and the store room clean-up is logrotate.

Engineer's View

The four parts of the logging stack

On both Rocky Linux 10 and Ubuntu 26.04 the same four pieces do the work. They are separate programs with separate config files, which is why one can be fine while another loses data.

PieceWhat it doesConfigRead it with
systemd-journaldCollects messages from services (their stdout and stderr), the syslog socket and the kernel, with fields like unit, PID and priority. Stores them in binary journal files/etc/systemd/journald.conf.d/*.confjournalctl
rsyslogTakes messages from the journal and writes them to text files or sends them to other servers, based on rules/etc/rsyslog.conf, /etc/rsyslog.d/*.conftail, grep, less
Remote syslogA copy of every message goes to a central server over TCP (or UDP, or TLS) so it survives if the source server dies or is wipedomfwd action on the sender, imtcp input on the receiverFiles on the log server
logrotateRenames, compresses and deletes old text logs on a schedule, run daily by logrotate.timer/etc/logrotate.conf, /etc/logrotate.d/*logrotate -d (dry run)

Every syslog message carries a facility (who sent it) and a priority (how serious it is). An rsyslog rule is a selector such as local3.warning followed by an action, and the selector matches that priority and everything more serious.

Facilities you’ll meetPriorities, most serious first
kern kernel, auth and authpriv logins and sudo, cron, mail, daemon services, user default for programs, local0 to local7 free for your own appsemerg (0), alert (1), crit (2), err (3), warning (4), notice (5), info (6), debug (7)

The two distributions set the stack up differently, and that difference decides what survives a reboot:

Rocky Linux 10Ubuntu 26.04
Main text log/var/log/messages, logins in /var/log/secure/var/log/syslog, logins in /var/log/auth.log
Timestamp in filesOld style: Oct 27 11:24:10RFC 3339: 2026-10-27T11:24:10.413212+05:30
How rsyslog gets messagesReads the journal with the imjournal modulejournald forwards to rsyslog’s socket (ForwardToSyslog=yes drop-in)
Journal storage out of the boxCheck it: Red Hat family installs have long shipped without /var/log/journal, which makes the journal live in RAM onlyPersistent: systemd 259 on 26.04 is built with Storage=persistent
rsyslog runs asroot, confined by SELinuxuser syslog, confined by an AppArmor profile that allows writing under /var/log

How it works, step by step

Follow one message, “order 1044: payment gateway timeout”, from the app that writes it to the disk and the log server. The diagram below shows the whole trip.

1A program sends a message

A service can print to stdout (systemd captures it), call the C syslog() function, or use the logger command from a script. The kernel has its own ring buffer. All of these end up at systemd-journald, which adds trusted fields the program can’t fake: the unit name, PID, user ID and boot ID.

2journald stores it in RAM or on disk

With Storage=volatile the journal lives in /run/log/journal, which is RAM and is empty after every reboot. With Storage=persistent it goes to /var/log/journal. The auto setting means “persistent only if /var/log/journal already exists”, so one deleted folder quietly switches a server to RAM-only logging at the next boot.

Linux logging flow: a message from a program through journald and rsyslog to files and a log server

3rsyslog sorts copies into text files

rsyslog reads each message and walks its rules from top to bottom. The default rules on Rocky send *.info;mail.none;authpriv.none;cron.none to /var/log/messages and authpriv.* to /var/log/secure. A minus sign in front of a file name (-/var/log/syslog on Ubuntu) means “don’t sync after every line”, which is faster.

4Order matters, and stop ends the walk

Files in /etc/rsyslog.d/ are read in name order at the point where rsyslog.conf includes them, and on both distributions that point comes before the default rules. A line & stop after your rule means “this message is handled, don’t give it to any later rule”, so a file named 30-something can keep your app’s messages out of /var/log/messages, and also out of any forwarding rule in a file named 90-something.

5A copy goes to the log server

The omfwd action sends each message over TCP to the log server, port 514 by convention. With a disk-assisted queue (queue.type="LinkedList" plus a queue.filename), rsyslog keeps messages on disk while the server is down and sends them when it is back. On the receiver, the imtcp module listens on 514, and a template builds a file path per host, so each server’s logs land in their own folder.

The picture below shows the sender, the network and the receiver.

Linux logging to a remote syslog server: rocky01 forwards over TCP 514 to ubu01

6Old logs are cleaned up

journald trims its own files: by default the persistent journal may use up to 10 percent of the file system (capped at 4 GiB) and always leaves 15 percent free, and you set your own numbers with SystemMaxUse= and SystemKeepFree=. Text files are logrotate’s job: logrotate.timer runs it once a day, it reads /etc/logrotate.conf and every file in /etc/logrotate.d, and rotates each log whose time (daily, weekly) or size limit is reached.

After rotating, the program writing the log must start a new file, or it keeps writing into the renamed one. There are two ways. With create plus a postrotate script, logrotate renames the file, creates an empty one and then tells the program to reopen its logs (rsyslog does that on a HUP signal, Nginx on USR1).

With copytruncate, logrotate copies the file and then empties the original in place, for programs that can’t be told to reopen. It works with any program, but lines written between the copy and the truncate are lost, so use it only when there is no reopen signal.

Hands-on lab: persistent journal, an app log, a log server and logrotate

Before you run any command, double-check it

Read every command twice before you press Enter. Check the target: the server name, the user, the disk or device name, the path and the sizes. These commands are for learning and were tested in our lab, not on your system. At work, run the read-only commands first, follow your change process, make sure a backup exists, and ask a second person to check anything that changes or deletes data. You run them at your own risk.

Most of this lab runs on rocky01, with ubu01 as the log server in steps 5 and 6. Keep an SSH session open to each. Every file you create has a number in its name on purpose, because rsyslog reads its folder in name order.

1See where rocky01 keeps logs today

Run this on rocky01:

sudo ls -l /var/log/messages /var/log/secure
ls -ld /var/log/journal
journalctl --disk-usage
journalctl --list-boots

You should see something like this:

-rw-------. 1 root root 1843271 Oct 27 11:18 /var/log/messages
-rw-------. 1 root root   48212 Oct 27 11:18 /var/log/secure
ls: cannot access '/var/log/journal': No such file or directory
Archived and active journals take up 8.0M in the file system.
IDX BOOT ID                          FIRST ENTRY                 LAST ENTRY
  0 a41c7e0b92d84f3b8e6d1c5f2a9b7e30 Tue 2026-10-27 09:01:12 IST Tue 2026-10-27 11:18:40 IST

rsyslog’s text files are there, with the dot after the permissions telling you they carry an SELinux label. In this example /var/log/journal doesn’t exist, so the 8 MB of journal sits in /run/log/journal, in RAM, and the boot list shows only the current boot even though rocky01 has been restarted many times since Lesson 2.

If your VM already has /var/log/journal and --list-boots shows several boots with negative numbers, your journal is persistent already. Do step 2 anyway, so the setting and the size limit are written down in a file instead of depending on whether a folder exists.

✅ Checkpoint: You can say whether rocky01’s journal is in RAM or on disk right now, and how much space it uses.

2Make the journal persistent, with a size limit

Paste this whole block on rocky01:

sudo mkdir -p /etc/systemd/journald.conf.d
sudo tee /etc/systemd/journald.conf.d/50-saatlab.conf > /dev/null <<'EOF'
[Journal]
Storage=persistent
SystemMaxUse=200M
SystemKeepFree=1G
EOF
sudo systemctl restart systemd-journald
sudo journalctl --flush
ls -ld /var/log/journal /var/log/journal/*
systemd-analyze cat-config systemd/journald.conf | grep -E '^(Storage|SystemMaxUse|SystemKeepFree)='
journalctl --disk-usage

You should see something like this:

drwxr-sr-x+ 3 root systemd-journal 46 Oct 27 11:20 /var/log/journal
drwxr-sr-x+ 2 root systemd-journal 53 Oct 27 11:20 /var/log/journal/6f2b9c1d4e8a4b0f9c3d2e1a7b5c8d90
Storage=persistent
SystemMaxUse=200M
SystemKeepFree=1G
Archived and active journals take up 16.0M in the file system.

A drop-in file in journald.conf.d is the recommended way to change journald, because package updates never touch it and anyone can see your changes in one small file. Storage=persistent creates /var/log/journal itself, and the folder inside it is named after the machine ID. --flush moves what was in RAM onto the disk, which is why the usage went up.

SystemMaxUse=200M caps the journal, and SystemKeepFree=1G makes journald stop growing when the file system has less than 1 GB free, whichever comes first. On a real server size these to the disk; on a 20 GB lab VM, 200 MB holds weeks of logs. When you need room right now, sudo journalctl --vacuum-size=100M or --vacuum-time=2weeks deletes the oldest archived journal files.

✅ Checkpoint: systemd-analyze cat-config shows your three settings and /var/log/journal/<machine-id> exists.

Linux logging lab: logger and journalctl output on rocky01
Example output: logger test messages read back with journalctl and tail on rocky01.

3Send test messages with logger and read them back

Run this on rocky01:

logger -p local3.warning -t saatapp "order 1044: payment gateway timeout"
logger -p local3.info -t saatapp "order 1045 placed"
journalctl -t saatapp -p warning --since "5 min ago" --no-pager
sudo tail -n 2 /var/log/messages

You should see something like this:

Oct 27 11:24:10 rocky01 saatapp[2391]: order 1044: payment gateway timeout
Oct 27 11:24:10 rocky01 saatapp[2391]: order 1044: payment gateway timeout
Oct 27 11:24:10 rocky01 saatapp[2392]: order 1045 placed

-p local3.warning sets facility and priority, and -t saatapp sets the tag that shows up as the program name. journalctl’s -p warning shows warning and anything more serious, so the info line is filtered out there, while /var/log/messages has both because its rule takes *.info and above. This is how you test any logging change: send a message you can recognise and look for it at every stop.

✅ Checkpoint: The warning appears in journalctl and both lines appear at the end of /var/log/messages.

4Give your app its own log file

Create the rule, check it, then restart rsyslog:

sudo tee /etc/rsyslog.d/30-saatapp.conf > /dev/null <<'EOF'
local3.*    /var/log/saatapp.log
& stop
EOF
sudo rsyslogd -N1
sudo systemctl restart rsyslog

You should see something like this:

rsyslogd: version 8.2412.0-1.el10, config validation run (level 1), master config /etc/rsyslog.conf
rsyslogd: End of config validation run. Bye.

rsyslogd -N1 reads the whole configuration and checks it without touching the running daemon, so make it a habit before every restart. Now send a message and see where it goes:

logger -p local3.err -t saatapp "order 1046: card declined"
sudo tail -n 1 /var/log/saatapp.log
sudo ls -l /var/log/saatapp.log
sudo ls -Z /var/log/saatapp.log
sudo grep -c "order 1046" /var/log/messages

You should see something like this:

Oct 27 11:31:45 rocky01 saatapp[2448]: order 1046: card declined
-rw-------. 1 root root 59 Oct 27 11:31 /var/log/saatapp.log
system_u:object_r:var_log_t:s0 /var/log/saatapp.log
0

rsyslog created the file with root-only permissions and SELinux gave it the normal var_log_t label, because new files under /var/log inherit it. The count of 0 in /var/log/messages proves & stop worked: local3 messages now go only to the app’s own file.

✅ Checkpoint: order 1046 is in /var/log/saatapp.log and not in /var/log/messages.

5Turn ubu01 into a log server

Paste this whole block on ubu01:

sudo tee /etc/rsyslog.d/10-remote-in.conf > /dev/null <<'EOF'
# Receive syslog from other servers on TCP 514 and keep each host's logs apart
module(load="imtcp")
template(name="PerHostFile" type="string" string="/var/log/remote/%HOSTNAME%/%PROGRAMNAME%.log")
ruleset(name="fromRemote") {
    action(type="omfile" dynaFile="PerHostFile")
}
input(type="imtcp" port="514" ruleset="fromRemote")
EOF
sudo rsyslogd -N1
sudo systemctl restart rsyslog
sudo ss -tlnp | grep ':514'
sudo ufw status

You should see something like this:

rsyslogd: version 8.2512.0, config validation run (level 1), master config /etc/rsyslog.conf
rsyslogd: End of config validation run. Bye.
LISTEN 0      25           0.0.0.0:514       0.0.0.0:*    users:(("rsyslogd",pid=1907,fd=6))
LISTEN 0      25              [::]:514          [::]:*    users:(("rsyslogd",pid=1907,fd=7))
Status: inactive

The ruleset keeps remote messages away from ubu01’s own /var/log/syslog, and the %HOSTNAME% and %PROGRAMNAME% properties in the template give each sender and each program its own file. rsyslog on Ubuntu opens port 514 as root and then drops to the syslog user, and its AppArmor profile allows writes anywhere under /var/log, so /var/log/remote needs no extra setup.

ufw is inactive on ubu01 in this lab. On a server where it is active, allow only your own network: sudo ufw allow from 10.10.10.0/24 to any port 514 proto tcp. If the receiver were a Rocky server, you’d open it with sudo firewall-cmd --permanent --add-port=514/tcp and sudo firewall-cmd --reload. Lesson 34 covers both firewalls.

✅ Checkpoint: ss shows rsyslogd listening on port 514 on ubu01.

6Forward rocky01’s logs to ubu01

Paste this whole block on rocky01:

sudo tee /etc/rsyslog.d/05-forward-ubu01.conf > /dev/null <<'EOF'
# Send a copy of every message to ubu01 over TCP; queue on disk while ubu01 is down
*.* action(type="omfwd" target="10.10.10.21" port="514" protocol="tcp"
           queue.type="LinkedList" queue.filename="fwd_ubu01"
           queue.maxDiskSpace="200m" queue.saveOnShutdown="on"
           action.resumeRetryCount="-1")
EOF
sudo rsyslogd -N1 && sudo systemctl restart rsyslog
logger -p local3.notice -t saatapp "order 1047: refund queued, test from rocky01"
sudo true

Then run this on ubu01:

sudo ls /var/log/remote/rocky01/
sudo tail -n 1 /var/log/remote/rocky01/saatapp.log /var/log/remote/rocky01/sudo.log | grep -v '^==' | grep -v '^$'

You should see something like this:

rsyslogd.log  saatapp.log  sudo.log  systemd.log
2026-10-27T11:41:07+05:30 rocky01 saatapp[2502]: order 1047: refund queued, test from rocky01
2026-10-27T11:41:07+05:30 rocky01 sudo[2503]:  student : TTY=pts/0 ; PWD=/home/student ; USER=root ; COMMAND=/usr/bin/true

The file is named 05 on purpose. Your 30-saatapp.conf ends with & stop, so a forwarding rule in a file named 90 would never see local3 messages; at 05 it runs first and every message, the app’s included, gets a copy sent. The sudo line shows that authpriv messages travel too, which is the main reason security teams want a log server.

On ubu01 the lines carry an RFC 3339 timestamp with whole seconds, because rocky01 sends the classic syslog format and ubu01 writes it in its own file format. On the SELinux side nothing was needed: TCP port 514 is labelled rsh_port_t and the policy lets rsyslog connect to it. If you ever pick another port, label it first with sudo semanage port -a -t syslogd_port_t -p tcp 30514.

✅ Checkpoint: /var/log/remote/rocky01/saatapp.log on ubu01 contains order 1047.

7Rotate the app log, dry run first

Create the logrotate file on rocky01 and test it:

sudo tee /etc/logrotate.d/saatapp > /dev/null <<'EOF'
/var/log/saatapp.log {
    daily
    rotate 7
    compress
    delaycompress
    missingok
    notifempty
    create 0600 root root
    postrotate
        /usr/bin/systemctl kill -s HUP rsyslog.service >/dev/null 2>&1 || true
    endscript
}
EOF
sudo logrotate -d /etc/logrotate.d/saatapp

You should see something like this:

warning: logrotate in debug mode does nothing except printing debug messages!  Consider using verbose mode (-v) instead if this is not what you want.

reading config file /etc/logrotate.d/saatapp
Reading state from file: /var/lib/logrotate/logrotate.status
Allocating hash table for state file, size 64 entries
Creating new state
Creating new state
...
Handling 1 logs

rotating pattern: /var/log/saatapp.log  after 1 days (7 rotations)
empty log files are not rotated, old logs are removed
considering log /var/log/saatapp.log
Creating new state
  Now: 2026-10-27 11:46
  Last rotated at 2026-10-27 11:00
  log does not need rotating (log has already been rotated)

-d reads the file, reports any error and shows what it would do, without changing anything. It says “already been rotated” because logrotate records a brand new log as rotated now, so the first real rotation happens tomorrow. The lines mean: rotate daily, keep 7 old files, gzip them, but leave the newest old file uncompressed for a day (delaycompress), skip the file if it is missing or empty, create a new empty file, then send rsyslog a HUP so it reopens its files.

Force one rotation now to prove the postrotate step works:

sudo logrotate -f /etc/logrotate.d/saatapp
sudo ls -l /var/log/saatapp.log*
logger -p local3.info -t saatapp "order 1048: written after rotation"
sudo ls -l /var/log/saatapp.log; sudo tail -n 1 /var/log/saatapp.log
systemctl list-timers logrotate.timer

You should see something like this:

-rw-------. 1 root root  0 Oct 27 11:47 /var/log/saatapp.log
-rw-------. 1 root root 59 Oct 27 11:31 /var/log/saatapp.log.1
-rw-------. 1 root root 59 Oct 27 11:47 /var/log/saatapp.log
Oct 27 11:47:30 rocky01 saatapp[2561]: order 1048: written after rotation
NEXT                        LEFT     LAST                        PASSED  UNIT            ACTIVATES
Wed 2026-10-28 00:00:00 IST 12h left Tue 2026-10-27 00:00:04 IST 11h ago logrotate.timer logrotate.service

1 timers listed.

The old content moved to saatapp.log.1 and the new message landed in the fresh saatapp.log, so rsyslog reopened its file after the HUP. When the daily timer runs it reads /etc/logrotate.conf first, and on Rocky that file sets dateext, so the rotated files will be named like saatapp.log-20261028 instead of .1.

You ran this one file on its own. On Ubuntu, testing a single file that way can fail with “parent directory has insecure permissions”, because /var/log there is group-writable by syslog and the su root adm line that handles it lives in /etc/logrotate.conf. Test with sudo logrotate -d /etc/logrotate.conf there, or add an su line to your own file.

The log server needs the same care, or /var/log/remote grows until ubu01’s disk is full. Put this in /etc/logrotate.d/remote-hosts on ubu01 and check it with sudo logrotate -d /etc/logrotate.conf; the postrotate script is the one Ubuntu’s own rsyslog rotation uses.

/var/log/remote/*/*.log {
    daily
    rotate 30
    compress
    delaycompress
    missingok
    notifempty
    sharedscripts
    postrotate
        /usr/lib/rsyslog/rsyslog-rotate
    endscript
}

✅ Checkpoint: saatapp.log.1 holds the old lines, order 1048 is in the new saatapp.log, and logrotate.timer shows a next run.

Break It, Then Fix It

Take a snapshot of rocky01 and ubu01 first (VBoxManage snapshot rocky01 take before-l29 on the laptop, and the same for ubu01). The first trap is caught by a dry run, the second only shows up after a reboot, and the third stops rsyslog from reading its own default rules.

1A typo and a missing endscript in logrotate

Break the file on rocky01 in two ways, one at a time:

sudo sed -i 's/daily/dialy/' /etc/logrotate.d/saatapp
sudo logrotate -d /etc/logrotate.d/saatapp 2>&1 | grep -E 'error|rotating pattern'

You’ll get this:

error: /etc/logrotate.d/saatapp:2 unknown option 'dialy' -- ignoring line
rotating pattern: /var/log/saatapp.log  1048576 bytes (7 rotations)

logrotate skipped the bad line and carried on with whatever was left, and with no daily line this file now rotates by size instead. The exit code is still 0, so a nightly run would never report it. Now fix the typo and break the script block instead:

sudo sed -i 's/dialy/daily/; /endscript/d' /etc/logrotate.d/saatapp
sudo logrotate -d /etc/logrotate.d/saatapp 2>&1 | grep -E 'error|removing|Handling'

You’ll get this:

error: /etc/logrotate.d/saatapp:prerotate, postrotate or preremove without endscript
removing last 1 log configs
Handling 0 logs

This one is worse: without endscript logrotate can’t tell where the script ends, so it throws the whole block away and handles 0 logs. The app log would grow every day until the disk is full, and nothing would warn you except the dry run.

Fix: write the file again (the same block as lab step 7), then repeat the dry run.

sudo tee /etc/logrotate.d/saatapp > /dev/null <<'EOF'
/var/log/saatapp.log {
    daily
    rotate 7
    compress
    delaycompress
    missingok
    notifempty
    create 0600 root root
    postrotate
        /usr/bin/systemctl kill -s HUP rsyslog.service >/dev/null 2>&1 || true
    endscript
}
EOF
sudo logrotate -d /etc/logrotate.d/saatapp 2>&1 | grep -E 'error|Handling'
Handling 1 logs

For a small file, writing it again from a known good copy is safer than patching it with sed. At work, keep these files in Git or an Ansible role and run the dry run in a pipeline before they reach a server.

✅ Checkpoint: The file looks like the one in lab step 7 again and the dry run prints “Handling 1 logs” with no error line.

2The journal goes back to RAM after a clean-up

Set the storage back to auto and delete the journal folder, as someone freeing disk space might, then reboot:

sudo sed -i 's/^Storage=persistent/Storage=auto/' /etc/systemd/journald.conf.d/50-saatlab.conf
sudo rm -rf /var/log/journal
sudo systemctl restart systemd-journald
sudo reboot

Log in again and ask for the previous boot:

journalctl --list-boots
journalctl -b -1 -n 2 --no-pager
sudo grep -c "order 104" /var/log/messages /var/log/saatapp.log.1

You’ll get something like this:

IDX BOOT ID                          FIRST ENTRY                 LAST ENTRY
  0 b7c1e0d94a2f4e6c9d3a8f1e2b4c6d8a Tue 2026-10-27 12:05:31 IST Tue 2026-10-27 12:06:02 IST
Specifying boot ID or boot offset has no effect, no persistent journal was found.
Oct 27 12:06:01 rocky01 systemd[1]: Started session-1.scope - Session 1 of User student.
Oct 27 12:06:02 rocky01 systemd[1]: Startup finished in 1.402s (kernel) + 2.311s (initrd) + 6.904s (userspace) = 10.618s.
/var/log/messages:2
/var/log/saatapp.log.1:1

With auto and no /var/log/journal, journald stayed in RAM, so everything before this boot is gone from the journal. Look closely at the second command: you asked for the previous boot and journalctl printed a one-line note and then lines from the current boot. In an incident at 3 a.m. that note is easy to miss, and you’d be reading the wrong boot.

The text files still have the old messages, because rsyslog wrote them to disk when they happened.

Fix: write persistent back, restart journald and flush.

sudo sed -i 's/^Storage=auto/Storage=persistent/' /etc/systemd/journald.conf.d/50-saatlab.conf
sudo systemctl restart systemd-journald
sudo journalctl --flush
ls -d /var/log/journal/*
/var/log/journal/6f2b9c1d4e8a4b0f9c3d2e1a7b5c8d90

✅ Checkpoint: After one more reboot, journalctl --list-boots shows two boots (-1 and 0).

3One missing bracket in an rsyslog file

On ubu01, add a rule with a missing closing bracket and check it:

printf 'local3.*  action(type="omfile" file="/var/log/saatapp.log"\n& stop\n' | sudo tee /etc/rsyslog.d/40-saatapp.conf
sudo rsyslogd -N1

You’ll get something like this:

rsyslogd: version 8.2512.0, config validation run (level 1), master config /etc/rsyslog.conf
rsyslogd: error during parsing file /etc/rsyslog.d/40-saatapp.conf, on or before line 2: invalid character '&' in object definition - is there an invalid escape sequence somewhere? [v8.2512.0 try https://www.rsyslog.com/e/2207 ]
rsyslogd: error during parsing file /etc/rsyslog.d/50-default.conf, on or before line 8: syntax error on token 'auth' [v8.2512.0 try https://www.rsyslog.com/e/2207 ]
rsyslogd: could not interpret master config file '/etc/rsyslog.conf'. [v8.2512.0 try https://www.rsyslog.com/e/2207 ]

The second error points at 50-default.conf, a file you never touched. The parser was still inside your unfinished action( when it reached the next file, so it choked on Ubuntu’s own rules. If you restarted rsyslog now, the default rules that feed /var/log/syslog and /var/log/auth.log would not load as you expect.

Always fix the first error in the list; the rest are often echoes of it.

Fix: close the bracket (or remove the test file), check, then restart.

sudo rm /etc/rsyslog.d/40-saatapp.conf
sudo rsyslogd -N1 && sudo systemctl restart rsyslog
rsyslogd: version 8.2512.0, config validation run (level 1), master config /etc/rsyslog.conf
rsyslogd: End of config validation run. Bye.

✅ Checkpoint: rsyslogd -N1 ends with “End of config validation run. Bye.” and no error lines.

This happened at work

The log server that had been empty for nine weeks

My team lead tells this one whenever someone says logging is boring. She was running Linux operations for a credit bureau in Gurugram, and one Monday morning the compliance team walked over with a customer dispute. A man said his credit report had been pulled without his consent on a date about forty days back, and the regulator wanted to know which system and which user had requested it.

The API server that served reports had the answer in its logs, or should have. Locally, /var/log/secure kept four weeks, the journal was in RAM and the box had been patched and rebooted twice since. Nobody worried, because every server forwarded to the central log server, which kept 400 days.

Except that the folder for this server on the log server stopped growing nine weeks earlier.

The server had been rebuilt from a new template in August, and the template was missing the one rsyslog forwarding file. It had been running happily without sending a single line, and no alert existed for “host stopped talking”. They got lucky: the application’s own audit table in the database still had the request, with the partner’s API key, and that answered the regulator.

It took two people most of a day.

The team made three changes that week. The forwarding file and a journald drop-in with Storage=persistent went into the Ansible role that builds every server, so no template could miss them again. A small daily job on the log server lists every host that sent nothing in 24 hours and opens a ticket.

And logrotate files for the app logs got a review with logrotate -d in the pipeline, after they found two more with broken script blocks.

Common mistakes and troubleshooting

MistakeWhat happensWhat to do instead
Trusting the journal to hold last week’s logs on a Rocky server without checkingAfter a reboot, everything before it is gone from journalctlCheck journalctl --list-boots; set Storage=persistent in a journald.conf.d drop-in
Editing the main journald.conf or rsyslog.conf for small changesPackage updates may replace it, and nobody can see what you changedUse drop-ins: /etc/systemd/journald.conf.d/ and /etc/rsyslog.d/
Restarting rsyslog without rsyslogd -N1One typo stops the default rules loading and logs go missing quietlyRun sudo rsyslogd -N1 and fix the first error first
Putting the forwarding rule after a file that ends in & stopThose messages never reach the log serverName the forwarding file so it sorts first, such as 05-forward.conf
A logrotate file with no postrotate and no copytruncateThe app keeps writing into the renamed file, and the new one stays emptySend the reopen signal in postrotate, or use copytruncate if the app has none
Skipping logrotate -d after editing a fileA missing endscript drops the whole block and the log grows until the disk is fullDry run every change; on Ubuntu test with /etc/logrotate.conf
Sending syslog over UDP to the log serverMessages are dropped without a trace when the network or receiver is busyUse TCP (protocol="tcp") with a disk queue, or TLS on 6514 across untrusted networks

Interview questions

Click a question to open it. Each one is written the way an interviewer would ask it. Under it you will see why they ask it and one way you could answer, in your own words.

Fresher level

1. What’s the difference between journald and rsyslog, and why does a Rocky server run both?

Why they ask this: They want to see that you know where logs physically live and which tool reads which store.

How you might answer: journald collects every message with extra fields such as the unit and PID and stores them in binary journal files that I read with journalctl. rsyslog reads those messages and writes plain text files like /var/log/messages and /var/log/secure, and it can forward them to another server.

On Rocky, rsyslog pulls from the journal with the imjournal module. Running both gives you journalctl’s filtering plus text files that every old tool, script and log shipper understands, and the text files survive a reboot even when the journal is kept in RAM.

2. After a reboot, journalctl shows nothing from yesterday. What do you check?

Why they ask this: A practical check that you know how journal storage works, and that you won’t blame the wrong thing.

How you might answer: I’d run journalctl --list-boots. If it lists only boot 0, the journal is volatile. Then I’d check whether /var/log/journal exists and what Storage is set to with systemd-analyze cat-config systemd/journald.conf.

To fix it I’d add a drop-in with Storage=persistent and a SystemMaxUse limit, restart systemd-journald and run journalctl --flush. For yesterday’s events I’d look in /var/log/messages or on the central log server, because rsyslog wrote those to disk.

2–5 years experience

3. Walk me through setting up remote logging from 50 servers to one central syslog server.

Why they ask this: They’re checking whether you think about delivery, separation per host, firewalls and what happens when the server is down.

How you might answer: On the log server I’d load imtcp on port 514 with its own ruleset and a template like /var/log/remote/%HOSTNAME%/%PROGRAMNAME%.log, open 514/tcp only for our subnets, and give it its own logrotate policy and a big enough disk. On each client I’d drop one file into /etc/rsyslog.d with an omfwd action over TCP and a LinkedList disk queue with saveOnShutdown, so nothing is lost while the server restarts.

I’d push that file with Ansible, check it with rsyslogd -N1, and add a daily report of hosts that sent nothing. Across untrusted networks I’d switch to TLS on 6514.

4. When would you use copytruncate in logrotate instead of create with postrotate?

Why they ask this: This tests whether you understand how a running program holds its log file open, which is the reason both options exist.

How you might answer: I prefer create with a postrotate that tells the program to reopen its log, like a HUP for rsyslog or USR1 for Nginx, because nothing is lost. After the rename the program still holds the old file open, so without that signal it keeps writing into the rotated file.

copytruncate is for programs that can’t reopen their logs, such as some Java apps writing to a fixed file. It copies the file and then truncates the original, so any lines written between the copy and the truncate are lost. I accept that only when there is no signal.

Senior level

5. How would you size and protect logging on a server that writes 20 GB of logs a day?

Why they ask this: Senior candidates should talk about disk isolation, rate limits, retention and shipping off the box, not only rotation.

How you might answer: First I’d put /var/log on its own LV or file system so logs can never fill / and stop the server. Then I’d set journald’s SystemMaxUse and SystemKeepFree, rotate the big app logs hourly or by size with compression, and keep only a day or two locally.

The long retention would live on the central system, not on the server. I’d also look at why it writes 20 GB: debug left on, a noisy health check, or journald rate limiting hiding a loop, because the cheapest log is the one you don’t write.

6. Security asks you to prove logs from your Linux fleet can’t be quietly altered by someone who gets root on a server. What do you propose?

Why they ask this: They want to hear that local logs can always be edited by root, and how you design around that.

How you might answer: I’d say plainly that root on a server can edit anything stored on that server, so the answer is to get the logs off the box quickly. I’d forward everything over TLS to a central log system the server admins can’t log in to, with write-once or append-only storage there and access audited separately.

On the servers I’d add auditd rules for the log configs and the rsyslog binary, and alert when a host stops sending. journald’s Seal option with forward-secure sealing can show tampering in local journal files, but the central copy is what auditors will trust.

Quick quiz

Q1. With Storage=auto and no /var/log/journal folder, where does journald keep the journal?

  1. /var/log/journal, created at boot
  2. /run/log/journal, in RAM, lost at reboot
  3. /var/log/messages
  4. Nowhere, logging is off
Show answer

B — auto means persistent only if /var/log/journal exists; otherwise the journal is volatile in /run/log/journal.

Q2. What does the rsyslog selector local3.warning match?

  1. Only warning messages from local3
  2. local3 messages at warning or more serious (err, crit, alert, emerg)
  3. local3 messages at warning or less serious
  4. All facilities at warning
Show answer

B — A selector matches the named priority and every more serious one. Use local3.=warning for that level only.

Q3. Which command checks the rsyslog configuration without restarting the service?

  1. rsyslogd -N1
  2. systemctl reload rsyslog
  3. logrotate -d
  4. journalctl --verify
Show answer

A — -N1 runs a configuration validation pass and reports errors with file and line.

Q4. A logrotate block has postrotate but no endscript. What does logrotate do?

  1. Rotates the log and skips the script
  2. Rotates the log and runs the script anyway
  3. Throws the whole block away and handles 0 logs for it
  4. Asks for confirmation
Show answer

C — It reports the error, removes that log config, and the file is never rotated.

Q5. Why name the forwarding file 05-forward.conf on rocky01 instead of 90-forward.conf?

  1. rsyslog only reads files up to 50
  2. Files are read in name order, and 30-saatapp.conf ends with & stop, so a later rule never sees local3 messages
  3. Lower numbers use TCP
  4. It makes rsyslog start faster
Show answer

B — Rules run in order, and stop ends processing for that message.

Quick recap

  • Linux logging is a chain: journald collects, rsyslog writes text files and forwards, logrotate and journald limits clean up.
  • Check journal storage with journalctl --list-boots; make it persistent and capped with a drop-in (Storage=persistent, SystemMaxUse=).
  • rsyslog rules are facility.priority action, read in file-name order; & stop ends processing, so forwarding goes first.
  • Remote syslog: imtcp on 514 with a per-host template on the server, omfwd over TCP with a disk queue on the client.
  • Run rsyslogd -N1 before every rsyslog restart and logrotate -d after every logrotate change.
  • Rotated logs need the program to reopen its file: postrotate with a signal, or copytruncate when there is no signal.

Next lesson

Lesson 30: Linux networking basics → You’ll read addresses, routes and neighbours with the ip command, set the host name with hostnamectl, and follow a name lookup through /etc/hosts, resolv.conf and systemd-resolved with ping, tracepath and dig.

Further reading: Linux logging

Want to go deeper into Linux logging? Read the man7.org pages for journald.conf(5) and journalctl(1), the rsyslog documentation (omfwd, imtcp and queues), the logrotate(8) manual, and Red Hat’s guide to configuring a remote logging solution. You can also see the full plan for this course on the undefined course page.

Scroll to Top

This website uses cookies

We use a few cookies to keep this site working. If you allow it, we also use Google Analytics to count visits so we can improve our lessons. You can accept all, deny the optional ones, or choose what to allow.