journalctl -n 100 | grep nginx does not show you the last 100 nginx lines.
It shows you whichever nginx lines happen to sit in the last 100 entries of the whole machine. On a busy server that is often none. This guide covers -n, the filters that belong in front of it, and the four ways it surprises people.
journalctl -n 100
-n is short for --lines. With no number it shows 10. -n all removes the limit.
The output comes oldest first, so the newest entry is at the bottom. In a terminal it opens in less, at the top. Press G to jump to the end.
Want the newest at the top instead? Add -r:
journalctl -r -n 100
That gives the same 100 entries, newest first.
Put the filter on journalctl, not after it:
journalctl -u nginx -n 100
journalctl applies -u, -p, -b, --since and field matches first, then counts back 100 from the end. So this is the last 100 entries nginx wrote, however long ago that was.
The pipe does it the other way round. journalctl -n 100 | grep nginx takes the last 100 entries from every service on the machine, then throws away the ones that are not nginx. tail has the same problem, plus one of its own: journalctl -u nginx | tail -n 100 reads the unit’s whole history to print 100 lines.
The same goes for any filter:
journalctl -p err -n 50 # the last 50 errors and worse
journalctl _COMM=sshd -n 20 # the last 20 entries from sshd
journalctl -u nginx -u php-fpm -n 100
An entry with line breaks in its message is still one entry. journalctl prints the extra lines indented under the first, so -n 10 can fill a screen.
Which messages have line breaks depends on how they arrived. A program that writes to stdout or stderr does not make any: journald splits that stream at every newline, so a 30-line traceback becomes 30 entries. Programs that log through the journal’s own API, sd_journal_send(), can send one entry with several lines in it.
Plain -n 100 takes the last 100 entries in the journal, whichever boot they came from. If the machine restarted five lines ago, 95 of them are from before the restart. A -- Boot 3f2a… -- line marks the join.
Want this boot only? Say so:
journalctl -b -n 100
That only matters if the journal is persistent. If it is not, the previous boot is already gone. Finding out why a Linux server rebooted covers how to tell.
-g searches the message text. Combine it with -n and journalctl turns on --reverse without telling you:
journalctl -g timeout -n 20
You get the last 20 matches, newest first. That is the opposite of every other -n command on this page. The man page says so in one sentence under --grep. A script that takes the last line as the latest match is now taking the oldest.
-n counts matches here, not entries searched. So the 20 can reach back as far as needed. -g only looks at the message, not the unit name. Searching journalctl logs for a specific string covers the rest of -g.
journalctl -u nginx --since "1 hour ago" -n 100
This shows the last 100 entries inside that hour. If nginx logged 12, you get 12. That is what you want. Just do not read 12 lines as a sign that something is broken.
journalctl -u nginx -n +20
A + in front of the number takes the oldest 20 entries instead of the newest. It needs systemd 255 or newer. journalctl --version prints the number on its first line. Debian 12 and RHEL 9 ship older versions, so check before you put it in a script.
-n +N refuses to run with -r or -f.
Inside the pager, long lines stop at the right edge. Nothing is lost. journalctl runs less with SYSTEMD_LESS=FRSXMK, and the S chops the line instead of wrapping it. Press the right arrow to scroll across.
Three ways to get the whole line:
journalctl -u nginx -n 100 --no-pager # print it, let the terminal wrap
SYSTEMD_LESS=FRXMK journalctl -u nginx -n 100 # keep less, drop the chop
journalctl -u nginx -n 100 -o cat # message text only, nothing else on the line
Setting LESS does nothing here. journalctl reads SYSTEMD_LESS and ignores LESS.
Seeing … in the middle of a line is different. Current systemd only shortens fields like that if you pass --no-full. Drop it, or add -a to show every field in full.
journalctl -u nginx -e
-e opens the pager at the bottom, on the newest entry. It also sets -n 1000 and -b for you, unless you give your own. So journalctl -e on its own shows the last 1000 entries from this boot, not the whole journal.
-f follows the journal and starts with the last 10 entries. Give it more history with -n:
journalctl -u nginx -f -n 100
-f -n 0 starts from an empty screen. Following logs in real time covers the rest, including the grep pipe that looks like it hung.
journalctl -u nginx -n 100 --no-pager > nginx-last-100.log
Redirecting already turns the pager off, so --no-pager is only there to make the intent obvious. Add -o short-iso if the file is going to someone in another time zone. Saving journalctl output to a file covers the other output formats and when each one is the right choice.
-n reads one machine’s journal. When the question is “what did the web tier log just before it fell over”, that is three SSH sessions and three windows.
Ship each journal to one place with the CL Agent and ask once. Each journal entry lands as a JSON row, so the filters you put on journalctl become a WHERE clause:
SELECT received, host, message FROM (
SELECT timestamp,
datetime(timestamp, 'unixepoch') AS received,
json_extract(msg, '$._HOSTNAME') AS host,
json_extract(msg, '$.MESSAGE') AS message
FROM logs
WHERE json_valid(msg)
AND json_extract(msg, '$._SYSTEMD_UNIT') = 'nginx.service'
ORDER BY timestamp DESC
LIMIT 100
) ORDER BY timestamp;
The inner query takes the newest 100 rows. The outer one puts them back oldest first, the way -n prints them. Drop the outer query if you want newest first, like -r.
Keep json_valid(msg) in the WHERE clause. json_extract errors on the first plain-text row it meets, and one is enough to stop the query.
timestamp is when Central Logging received the row, not when the entry was written. The agent uploads every 30 seconds, so the two are close in normal running. After an agent has been offline, a backlog arrives at once. Sort by journald’s own clock when that matters:
ORDER BY CAST(json_extract(msg, '$.__REALTIME_TIMESTAMP') AS INTEGER) DESC
Keep the CAST. The field arrives as a string, and strings sort in a different order from numbers.
-u in depth, and what to check when a unit prints nothing.--since and --until instead of a count.-p err, and why it finds nothing for most applications.-g, case sensitivity, and matching fields other than the message.💌 Get notified on new features and updates