Tame systemd journal logs across a fleet
The systemd journal will happily grow until it fills the disk. On an FR201 with a modest M.2 SSD, that’s a real concern — a chatty service or a tight-loop error can produce gigabytes of logs in a few days. The fix is two-part:
- One-time cleanup: vacuum the existing journal to reclaim space now.
- Persistent cap: tell
journaldhow much disk it’s allowed to use, so it self-prunes from here on.
If you have one device, you SSH in and run a few commands. If you have twenty, you want automation. This guide covers both with a small PowerShell driver that pushes a bash configuration script to a range of devices in parallel.
What the automation does
Section titled “What the automation does”Two files do the work:
Directoryjournal-automation/
- automate.ps1
- configure_journald.sh
configure_journald.shruns on each FR201. It vacuums the journal down to 100 MB, sets a 1 GB on-disk cap and a 100 MB tmpfs cap in/etc/systemd/journald.conf, and restartssystemd-journaldto apply the change.automate.ps1runs on your workstation. It walks a range of IP addresses, copiesconfigure_journald.shto each device withscp, runs it withsudo, and logs success or failure for each host.
Prerequisites
Section titled “Prerequisites”- PowerShell 7+ on your workstation (the driver uses cross-platform OpenSSH; Windows PowerShell 5.1 also works).
- SSH access to every target device with key-based auth set up. Password auth will work but you’ll be typing your password a lot.
- A user account on each device (the example uses
pi) with passwordlesssudoforsystemctland journal commands. configure_journald.shsaved with LF line endings. CRLF will break the script silently on the device — see the dos2unix guide if you’re not sure.
The configuration script
Section titled “The configuration script”This is the script that runs on each device.
#!/bin/bashset -euo pipefail
# Must run as root.if [[ $EUID -ne 0 ]]; then echo "This script must be run as root." >&2 exit 1fi
echo "Vacuuming existing journal entries..."journalctl --vacuum-size=100M
echo "Updating /etc/systemd/journald.conf..."CONF=/etc/systemd/journald.conf
# Replace or append SystemMaxUse and RuntimeMaxUse.for setting in "SystemMaxUse=1G" "RuntimeMaxUse=100M"; do key="${setting%%=*}" if grep -qE "^\s*#?\s*${key}=" "$CONF"; then sed -i "s|^\s*#\?\s*${key}=.*|${setting}|" "$CONF" else echo "$setting" >> "$CONF" fidone
echo "Restarting systemd-journald..."systemctl restart systemd-journald
echo "Done. Current journal usage:"journalctl --disk-usageA few notes:
set -euo pipefailmakes the script bail on the first error. Without it, a partial run can leave the configuration half-applied.- The
forloop replaces theSystemMaxUseandRuntimeMaxUselines if they already exist (commented or not), and appends them otherwise. That makes the script idempotent — running it twice is safe. journalctl --disk-usageat the end is a nice sanity check; you’ll see in the PowerShell output what each device ended up at.
The PowerShell driver
Section titled “The PowerShell driver”This runs on your workstation and fans out to the devices.
# Adjust to match your fleet.$User = "pi"$IpRange = 50..69 | ForEach-Object { "192.168.1.$_" }$ScriptPath = Join-Path $PSScriptRoot "configure_journald.sh"$RemoteTmp = "/tmp/configure_journald.sh"$Timeout = 5
$SuccessLog = Join-Path $PSScriptRoot "journal_config_success.log"$FailLog = Join-Path $PSScriptRoot "journal_config_fail.log"
# Reset the logs each run."" | Set-Content $SuccessLog"" | Set-Content $FailLog
foreach ($ip in $IpRange) { Write-Host "[$ip] copying script..." -ForegroundColor Cyan
$scp = & scp -o "ConnectTimeout=$Timeout" -o "StrictHostKeyChecking=accept-new" ` $ScriptPath "${User}@${ip}:${RemoteTmp}" 2>&1 if ($LASTEXITCODE -ne 0) { "[$ip] scp failed: $scp" | Tee-Object -FilePath $FailLog -Append continue }
Write-Host "[$ip] running script..." -ForegroundColor Cyan $ssh = & ssh -o "ConnectTimeout=$Timeout" -o "StrictHostKeyChecking=accept-new" ` "${User}@${ip}" "chmod +x $RemoteTmp && sudo $RemoteTmp && rm $RemoteTmp" 2>&1 if ($LASTEXITCODE -eq 0) { "[$ip] OK`n$ssh" | Tee-Object -FilePath $SuccessLog -Append } else { "[$ip] ssh failed:`n$ssh" | Tee-Object -FilePath $FailLog -Append }}
Write-Host "Done. See $SuccessLog and $FailLog." -ForegroundColor GreenWhat it’s doing:
- Builds a list of IPs from a range (
192.168.1.50through192.168.1.69in this example). - For each device, copies the bash script to
/tmp, makes it executable, and runs it withsudo. - 5-second connect timeout on both
scpandsshso the loop doesn’t hang on a powered-down device. - Tees the per-host output to
journal_config_success.logorjournal_config_fail.logso you have a record without scrolling back through the terminal.
Run it
Section titled “Run it”-
Drop both files into a folder on your workstation:
Directoryjournal-automation/
- automate.ps1
- configure_journald.sh
-
Make sure
configure_journald.shhas Unix line endings. From PowerShell:Force LF line endings (Get-Content .\configure_journald.sh -Raw) -replace "`r`n", "`n" |Set-Content -NoNewline .\configure_journald.sh -
Edit the top of
automate.ps1to match your network — username, IP range, timeout. -
Run it:
Run the deployment .\automate.ps1 -
Review the logs:
Directoryjournal-automation/
- journal_config_success.log
- journal_config_fail.log
Tuning the caps
Section titled “Tuning the caps”The defaults in the script — 1 GB on-disk, 100 MB on tmpfs — are reasonable for a typical FR201 install. If you have less storage, drop them. If you want more retention for forensic reasons, raise them. Adjust these two lines in configure_journald.sh:
for setting in "SystemMaxUse=1G" "RuntimeMaxUse=100M"; doSystemMaxUse controls the persistent journal under /var/log/journal/. RuntimeMaxUse controls the volatile journal in /run/log/journal/ (used when persistent storage isn’t available).
Troubleshooting
Section titled “Troubleshooting”SSH connection failures: confirm key-based auth works against one host with ssh pi@192.168.1.50 before fanning out. The 5-second timeout means a misconfigured device will fail fast rather than hanging the whole run.
Permission errors on the device: the user needs passwordless sudo for at least systemctl and journalctl. Add a sudoers rule scoped to those binaries instead of granting blanket NOPASSWD: ALL.
No effect after the script runs: confirm systemd-journald actually restarted (systemctl status systemd-journald) and that /etc/systemd/journald.conf shows the new values. A CRLF in the bash script can cause it to silently fail partway through.
Security notes
Section titled “Security notes”- The driver requires SSH access and
sudorights — the same access you’d use to log in by hand. Use SSH keys, not passwords, and scopesudorules narrowly. - The script writes to
/tmpand removes itself after running, so nothing is left behind on the device. - All connections use connect timeouts so a hung host can’t stall the rollout.