journalctl —since and —until take a window, and both accept plain English as well as timestamps. journalctl —since “2 hours ago” is valid, so is —since “09:00” —until “09:30”, and so is —since yesterday. Getting the window right is most of what makes the journal usable during an incident.
The journal holds everything systemd and its units have logged, which means running journalctl with no arguments gives you a wall of text starting from whenever the disk was last cleared. Every useful invocation is a filter, and time is the most useful filter there is.
Table of contents
- The time formats it accepts
- Narrowing to the thing you care about
- Making the output usable
- Why your window returns nothing
- Cleaning up when the disk fills
- How this fits the rest of the stack
- FAQ
The time formats it accepts
journalctl --since "2 hours ago"
journalctl --since "10 min ago"
journalctl --since yesterday
journalctl --since today
journalctl --since "2026-08-14"
journalctl --since "2026-08-14 09:00:00"
# A closed window, which is what you want for an incident.
journalctl --since "09:00" --until "09:30"
journalctl --since "2026-08-14 09:00" --until "2026-08-14 10:00"
journalctl --since "1 hour ago" --until "30 min ago"
The accepted forms are broader than most people realise. A full timestamp, a date alone which means midnight that day, a time alone which means today at that time, the keywords yesterday, today, tomorrow, and now, and relative offsets in seconds, minutes, hours, days, weeks, months, or years.
Relative offsets also work with a leading sign, so —since -1h is the same as —since “1 hour ago”, and —until +10m is ten minutes into the future, which is occasionally useful when following a log.
One caveat worth knowing before it confuses you: the journal records in UTC but displays in local time by default. If you are correlating with a timestamp from an application log that records UTC, add —utc to make both sides agree.
journalctl --since "09:00" --until "09:30" --utc
Narrowing to the thing you care about
A time window alone still returns everything on the machine during that window. Combine it with a unit filter and the output becomes readable.
# One service, one window.
journalctl -u nginx.service --since "1 hour ago"
# Several units at once.
journalctl -u nginx -u php-fpm --since today
# Errors and worse. 0 emerg through 3 err.
journalctl -p err --since "2 hours ago"
journalctl -p warning..err --since today
# Just this boot, or the one before it.
journalctl -b
journalctl -b -1
# Kernel messages only.
journalctl -k --since "1 hour ago"
The priority filter is the highest-value flag on that list. Most journal volume is informational, and -p err cuts it to the lines that represent something going wrong. Levels run 0 emerg, 1 alert, 2 crit, 3 err, 4 warning, 5 notice, 6 info, 7 debug, and specifying one includes everything more severe.
The boot filter deserves a mention because it answers a specific question well. After an unexpected reboot, journalctl -b -1 -p err shows the errors from the boot that ended, which is usually where the cause is.
Making the output usable
journalctl -u myapp --since today --no-pager # for scripts and pipes
journalctl -u myapp -n 100 # last 100 lines
journalctl -u myapp -f # follow, like tail -f
journalctl -u myapp --since "5 min ago" -f # recent context, then follow
# Output formats.
journalctl -u myapp -o short-iso # ISO timestamps
journalctl -u myapp -o json-pretty # full structured fields
journalctl -u myapp -o cat # message text only
# Grep, built in.
journalctl -u myapp --since today -g 'timeout|refused'
Two of these are worth adopting as habits. —no-pager is essential in any script, because otherwise journalctl invokes a pager and hangs waiting for input that never comes. And -o short-iso gives unambiguous timestamps, which matters the moment you are correlating logs across machines in different timezones.
The -g flag greps the message field with a regular expression, and is case-insensitive when the pattern is all lowercase. It saves piping to grep and keeps the journal’s own formatting intact.
For a full picture of an incident, json-pretty exposes fields the default view hides: the unit, the process ID, the executable path, and the exit status of a failed service.
Why your window returns nothing
Three common causes, in order of frequency.
First, the journal is not persistent. On many distributions the journal lives in memory and is discarded on reboot, so asking for yesterday returns nothing at all. Check for a directory at /var/log/journal, and if it is absent, that is your answer.
# Is the journal persistent?
journalctl --disk-usage
ls -d /var/log/journal 2>/dev/null || echo "volatile: logs lost on reboot"
# Make it persistent.
sudo mkdir -p /var/log/journal
sudo systemd-tmpfiles --create --prefix /var/log/journal
sudo systemctl restart systemd-journald
Second, retention limits have already discarded it. The journal caps itself by disk usage and by age, and on a busy machine the window you want may have been rotated away. journalctl —verify and the SystemMaxUse setting in /etc/systemd/journald.conf are where to look.
Third, the service does not log to the journal at all. An application writing to its own file in /var/log gets nothing from journalctl. Only what goes to stdout, stderr, or syslog under a systemd unit is captured.
And a permissions note: without membership of the systemd-journal group, or sudo, you see only your own user’s messages. That produces an empty result that looks like missing logs rather than a permissions problem.
Cleaning up when the disk fills
journalctl --disk-usage # how much is it using?
sudo journalctl --vacuum-time=7d # drop anything older than a week
sudo journalctl --vacuum-size=500M # cap total size
sudo journalctl --vacuum-files=5 # keep the newest 5 journal files
The vacuum commands take effect immediately, which is what you want when a disk is full at an awkward hour. For a permanent limit, set SystemMaxUse and MaxRetentionSec in /etc/systemd/journald.conf and restart systemd-journald.
Worth setting deliberately rather than leaving to the default, which is a proportion of the filesystem and can be several gigabytes on a large disk.
How this fits the rest of the stack
Reading logs this way means being on the box, which works until there are several boxes or the box is gone by the time you look. Deploy logs and runtime logs kept per deploy remove the SSH step entirely, and put the failing build and the failing request in the same place. The RunxBuild hosting calculator shows the service, database, and storage as separate line items, and every service carries its own runtime logs.
Useful related references:
- journalctl tail: Following systemd Logs Without Reaching for tail -f
- PostgreSQL BETWEEN: Inclusive Range Queries, Indexes, and Date Traps
- time.Sleep in Go: When It Is Right and What to Use Instead
- Services on RunxBuild
FAQ
How do I see journalctl logs between two times?
Use both bounds: journalctl —since “09:00” —until “09:30”. Each accepts a full timestamp, a date, a time, keywords such as yesterday, or a relative offset like 2 hours ago.
Why does journalctl —since yesterday return nothing?
The journal is probably volatile, stored in memory and discarded on reboot. Check whether /var/log/journal exists; if it does not, create it and restart systemd-journald to make logs persist.
How do I see only errors in journalctl?
Filter by priority with -p err, which includes everything at that level and more severe. Combine with a time window and a unit, as in journalctl -u myapp -p err —since today.
Does journalctl use UTC or local time?
It records in UTC and displays in local time by default. Add —utc to display UTC, which is worth doing when correlating with application logs that record UTC timestamps.
How do I limit how much disk the journal uses?
Run journalctl —vacuum-size or —vacuum-time for an immediate reduction, and set SystemMaxUse in /etc/systemd/journald.conf for a permanent cap, then restart systemd-journald.