journald Logs: Retention, Size Limits and Queries
On a systemd-based VPS the journal is not a plain text file you can tail and forget — it is a structured, indexed binary store managed by systemd-journald, and left at its defaults it will quietly grow until it claims a slice of your disk you never agreed to give it. That structure is a feature, not an annoyance: every log line carries metadata — the unit that emitted it, the priority, the PID, a monotonic timestamp — so you can ask precise questions instead of grepping a wall of text. This guide covers the two things that matter most for a server you actually operate: keeping the journal's size under control, and querying it quickly when something breaks at 2 a.m.
/etc/systemd/journald.conf. Set SystemMaxUse= to cap total size and MaxRetentionSec= to age entries out, then reclaim space immediately with journalctl --vacuum-size= or --vacuum-time=. For day-to-day debugging, filter rather than scroll: journalctl -u <service> for one unit, -p err for priority, --since "1 hour ago" for a window, and -f to follow live.Where the Journal Lives, and Whether It Persists
By default many distributions store the journal under /run/log/journal, which is tmpfs — meaning it lives in RAM and is wiped on every reboot. That is fine for a desktop and terrible for a server, where the logs explaining why a box rebooted are exactly the ones you want after it reboots. Persistent storage is controlled by Storage= in journald.conf. The most reliable setting is explicit:
1[Journal]
2Storage=persistent
With persistent, journald writes to /var/log/journal, creating the directory if needed, and survives reboots. After changing the file, apply it:
1sudo systemctl restart systemd-journald
Confirm where entries are actually going and how much space they occupy:
1journalctl --disk-usage
This one command is the number to watch. If it reports several gigabytes on a small VPS, the size controls below are not optional housekeeping — they are the difference between a healthy disk and a No space left on device outage.
Cap the Size Before It Caps You
Two directives in journald.conf bound how large the persistent journal is allowed to grow. They work together, and journald enforces whichever limit it hits first:
1[Journal]
2SystemMaxUse=500M
3SystemKeepFree=1G
SystemMaxUse=is the hard ceiling on total journal size. If you set nothing, journald defaults to 10% of the filesystem — on a 40 GB VPS that is 4 GB of logs you never asked for. Pin it to something sane like500Mor1G.SystemKeepFree=tells journald to always leave at least this much free on the filesystem, regardless ofSystemMaxUse. It is a safety net that keeps logging from being the thing that fills a disk shared with your application.
There is also a time-based control, which is often what you actually want — "keep two weeks, drop the rest":
1MaxRetentionSec=2week
MaxRetentionSec= accepts friendly units (day, week, month), and entries older than the window are removed during the journal's normal rotation. Combine a size cap and a retention window and you have a journal that can neither grow without bound nor hoard history you will never read.
After editing, restart the service so the new limits take effect:
1sudo systemctl restart systemd-journald
Reclaim Space Right Now with vacuum
Editing the config bounds future growth, but it will not instantly shrink a journal that is already oversized — rotation does that over time. When you need space back immediately, journalctl has explicit vacuum operations that delete old archived journal files on the spot:
1# Keep at most 200 MB of journal data
2sudo journalctl --vacuum-size=200M
3
4# Delete everything older than 7 days
5sudo journalctl --vacuum-time=7d
6
7# Keep at most 5 rotated journal files
8sudo journalctl --vacuum-files=5
Each form prints exactly which journal files it removes and how much it freed, so you see the effect rather than guessing. Vacuuming only ever touches archived (rotated) journal files, never the active one being written, so it is safe to run on a live server. A common recovery pattern when a disk alert fires is --vacuum-size=200M to free space in seconds, followed by setting SystemMaxUse=200M in the config so the problem does not return.
Query Like You Mean It
The reason to tolerate a binary log format is the querying. Scrolling the whole journal is the slow, error-prone way to debug; filtering is the point. The filters compose — stack them to narrow in on exactly the lines you need.
By unit — almost always your first move. Show only what one service logged:
1journalctl -u nginx.service
By priority — cut the noise to errors and worse. Priorities run 0 (emerg) to 7 (debug); -p err shows level 3 and above:
1journalctl -p err
By time window — --since and --until accept both absolute and natural-language times:
1journalctl --since "2026-06-20 09:00" --until "2026-06-20 10:00"
2journalctl --since "1 hour ago"
This boot only — after a crash, -b restricts to the current boot, and -b -1 shows the previous one:
1journalctl -b
2journalctl -b -1
Follow live — the journal's answer to tail -f, and it respects every filter above:
1journalctl -u myapp.service -f
The real power is in combining them. To see every error a single service logged in the last fifteen minutes — the canonical "what just went wrong" query — stack the filters:
1journalctl -u myapp.service -p err --since "15 min ago"
That is one precise question answered instantly, instead of paging through thousands of unrelated lines hoping to spot the relevant one.
Make the Output Readable and Scriptable
Two more flags earn their place in muscle memory. For triage, -e jumps straight to the end (newest entries), and -x adds explanatory help text to messages from systemd's catalog:
1journalctl -xe
When you want to feed log data to another tool, switch the output format. -o json emits one JSON object per entry — every field, machine-parseable — and -o cat strips all metadata down to the bare message text:
1journalctl -u myapp.service -o json --since today
Because every entry is structured, you can also filter on arbitrary fields directly, such as everything from a given executable:
1journalctl _COMM=sshd
That field-level matching is something a flat text log simply cannot do without a fragile regular expression.
A Journal You Control
The defaults optimise for "log everything forever" on a machine with infinite disk, which describes no VPS anywhere. Two edits fix that: set Storage=persistent so your logs survive a reboot, and set SystemMaxUse= plus MaxRetentionSec= so they never eat the disk that survival depends on. Pair that with --vacuum-* for emergencies and the filtered journalctl queries above for everyday debugging, and the journal stops being a mysterious blob under /var/log and becomes the first tool you reach for when something breaks. Check journalctl --disk-usage once in a while, confirm your caps are holding, and move on — a bounded, queryable journal is one less thing waiting to page you.
References
Posts in this series
- Linux VPS Performance Tuning: sysctl & swap 2026
- Systemd Service Hardening: Sandbox a Unit (2026)
- Fail2ban on Ubuntu VPS: Stop SSH Brute Force 2026
- UFW Firewall Rules for a Public VPS: 2026 Setup
- How to Harden SSH on an Ubuntu VPS (2026 Guide)
- Automated Backups with Rsync and Cron on Linux 2026
- TLS Certificates with Certbot on an Ubuntu VPS 2026
- Harden Nginx on Ubuntu: Headers, TLS & Limits 2026
- Automatic Security Updates on Ubuntu: 2026 Setup
- Logrotate for Nginx and App Logs on a Linux VPS
- Systemd Timers: Replace Cron Jobs on a Linux VPS
- Manage Linux Users and sudo Permissions on a VPS
- Linux VPS Disk and Inode Cleanup: du, find, lsof
- journald Logs: Retention, Size Limits and Queries