Guides

tail command: -n, -f, -F and following log files on Linux

The tail command prints the last lines of a file; tail -f follows it live and tail -F survives log rotation. Plus +N, head combos and grep buffering.

Published ~7 min read
On this page

Short answer: the tail command prints the end of a file: tail file shows the last 10 lines, tail -n 50 file the last 50. tail -f file keeps the file open and prints new lines as they are written, until you press Ctrl+C, and tail -F file does the same but keeps following when the log is rotated, which is the version you want for anything under /var/log.

any Linux
tail -n 50 app.log                              # last 50 lines
sudo tail -F /var/log/nginx/error.log           # follow live, survives log rotation
tail -n +2 users.csv                            # everything except the first line

The tail examples and the messages they print were checked on Debian 12 with GNU coreutils 9.1, the tail that Debian, Ubuntu, Rocky Linux and most other distributions ship.

tail cheat sheet

TaskCommand
Last 10 linestail file
Last N linestail -n 100 file
Everything from line N ontail -n +20 file
Skip a header linetail -n +2 data.csv
Last N bytestail -c 200 file
Follow a file as it growstail -f file
Follow through log rotationtail -F file
Last 100 lines, then followtail -n 100 -F file
Several files at oncetail -f a.log b.log
Several files, no ==> headerstail -q -n 5 a.log b.log
Stop following when a process exitstail -f --pid=4321 build.log
Follow and filtertail -F app.log | grep --line-buffered ERROR
Follow a systemd service insteadjournalctl -u nginx -f

tail -n: the last N lines

any Linux
seq 1 15 | sed 's/^/line /' > numbers.txt
tail -n 3 numbers.txt
Output
line 13
line 14
line 15

tail -3 numbers.txt gives the same result; the short form without -n is older syntax that GNU tail still accepts, but -n 3 is the portable spelling to put in a script. -c counts bytes instead of lines, and the newline at the end of a line is a byte too: printf 'abcdefghij\n' | tail -c 4 prints hij, not ghij.

tail -n +N: everything from line N on

A plus sign turns the count around: instead of “the last N lines” it means “start at line N”. The everyday use is dropping a header line before the data goes into another command:

any Linux
printf 'id,name\n1,a\n2,b\n' | tail -n +2
Output
1,a
2,b

head and tail together: a range of lines

any Linux
head -n 7 numbers.txt | tail -n 3      # lines 5 to 7
tail -n +5 numbers.txt | head -n 3     # lines 5 to 7, counted from the start
sed -n '5,7p' numbers.txt              # the same range in one command
head -n -12 numbers.txt                # all but the last 12 lines (GNU head)

All three range commands print line 5 to line 7. On a multi-gigabyte log the two pipes stop reading once they reach line 7, while sed -n '5,7p' reads on to the end of the file unless you add a quit: sed -n '5,7p;7q'. The sed command guide explains -n, p and line addresses.

tail -f: follow a file as it grows

Debian · Ubuntu
sudo tail -f /var/log/nginx/access.log

-f prints the last 10 lines, then waits and prints every new line as soon as it is written. It never exits on its own; Ctrl+C stops it. Combine it with -n for more history first (tail -n 200 -f), or with --pid=PID to end the follow automatically when a build or a transfer you are watching finishes. On Linux, GNU tail uses inotify to notice changes, so following a quiet file costs next to nothing.

Debian’s nginx package creates its logs with mode 0640, owner www-data and group adm (see /etc/logrotate.d/nginx), which is why tail needs sudo there. Adding an admin account to the adm group lets it read them without sudo.

tail -F: keep following through log rotation

Plain -f follows the open file, not the name. logrotate’s usual method renames access.log to access.log.1 and creates a new, empty access.log; Debian’s nginx config does exactly that every day (daily with create 0640 www-data adm). A tail -f left running overnight keeps reading the renamed file, which no longer grows, and simply goes quiet. -F (short for --follow=name --retry) notices that the name now points to a different file and reopens it. Here is one rotation (rename, then a new file, then two more lines) as each of them reports it:

tail -F app.log
start 1
before rotate
tail: 'app.log' has become inaccessible: No such file or directory
tail: 'app.log' has appeared;  following new file
after rotate 1
after rotate 2
tail -f app.log, same events
start 1
before rotate

The other rotation method, copytruncate, copies the log and then empties it in place. Both -f and -F survive that, and print a notice when it happens:

Output after a copytruncate
line one of the old log
line two of the old log
tail: app.log: file truncated
first line after copytruncate

For a log file there is rarely a reason to prefer -f, so typing -F by default is a good habit.

Several files at once

any Linux
tail -n 1 a.log b.log
Output
==> a.log <==
a2

==> b.log <==
b2

With more than one file, tail prints a ==> name <== header before each block, and with -f it prints a new header whenever the output switches to a different file. That makes sudo tail -F /var/log/nginx/access.log /var/log/nginx/error.log a quick way to watch a request and its error together. -q leaves the headers out; -v adds one even for a single file.

tail -f with grep: --line-buffered

any Linux
sudo tail -F /var/log/nginx/error.log | grep --line-buffered upstream

tail -f | grep pattern works fine when grep writes straight to your terminal. It goes wrong once grep’s output goes into another pipe or a file: grep then collects its output in a block buffer and writes nothing until that fills up, so on a quiet log the matches can sit there for a long time. On Debian 12, two matching lines piped through plain GNU grep 3.8 into a file were still at 0 bytes 1.5 seconds later, and arrived only when the pipeline ended; with --line-buffered they were there at once.

Every program after grep in the chain needs the same treatment: awk with fflush() after each print (or mawk -W interactive), sed with -u:

any Linux
sudo tail -F /var/log/nginx/access.log \
  | grep --line-buffered ' 502 ' \
  | awk '{print $1, $7; fflush()}'

The grep command guide has the flags for narrowing the match itself, such as -i, -v and -E.

journalctl -f: the systemd equivalent

A service that runs under systemd and writes to standard output or syslog logs into the journal, not a file you can tail. Its live view is:

Linux
journalctl -u myapp -f            # follow one service
journalctl -u myapp -n 100 -f     # last 100 lines first, then follow

nginx is the common exception: it writes requests and errors to its own files under /var/log/nginx, and its journal mostly shows the service starting, stopping or failing to start, so nginx is a tail -F job while your app server is usually a journalctl -f one. The journalctl guide covers time filters, priorities and boots.

Watching an error as it happens

The most useful habit with tail -F is to start it before reproducing a problem. Open the error log in one terminal, send the failing request from another, and the cause appears the moment it is logged:

terminal 1
sudo tail -n 0 -F /var/log/nginx/error.log
terminal 2
curl -sS -o /dev/null -w '%{http_code}\n' https://example.com/api/report

-n 0 skips the old lines so that only what this request causes shows up. A line such as connect() failed (111: Connection refused) while connecting to upstream means the backend behind nginx is not running, which is the first cause covered in fixing a 502 Bad Gateway.

tail errors and what they mean

  • tail: cannot open 'app.log' for reading: No such file or directory: wrong path, or the log has not been created yet. -F waits for it to appear instead of exiting. The same message ending in Permission denied means you need sudo or membership of the log’s group.
  • has become inaccessible followed by has appeared; following new file: a rotation seen by tail -F. Nothing to fix.
  • file truncated: the file was emptied in place (copytruncate, or : > file). tail carries on from the new start.
  • inotify resources exhausted or inotify cannot be used, reverting to polling: the user has run out of inotify watches or instances, often because an IDE server, a file sync tool or many tail -f sessions hold them. tail keeps working by checking the file once a second. To raise the limit, set fs.inotify.max_user_instances (128 by default) or fs.inotify.max_user_watches with sysctl.
  • Nothing new appears after midnight. The log was rotated under a tail -f. Restart it as tail -F.
  • Matches arrive late or in bursts. A program in the pipe is buffering: see grep’s --line-buffered above.

less +F, and tail on Windows

less +F file follows a file like tail -f, but Ctrl+C stops only the following, not the program: you can then scroll back, search with /, and press F to resume following, per the less man page. On Windows, PowerShell’s equivalent of tail -n 20 -f is Get-Content app.log -Tail 20 -Wait.

Frequently asked

What is the difference between tail -f and tail -F?

tail -f follows the open file. When a log is rotated by renaming it and creating a new one, tail -f keeps reading the renamed old file and shows nothing new. tail -F follows the file name and retries, so it reopens the new log after rotation and also waits for a file that does not exist yet.

How do I show everything except the first line with tail?

tail -n +2 file. A plus sign turns the number into a starting line instead of a count from the end, so tail -n +2 prints from line 2 to the end of the file, the usual way to drop a CSV header before processing the rest.

Why does tail -f piped through grep show nothing for a long time?

When grep's output goes into another pipe or a file instead of a terminal, grep buffers it and writes in large blocks, so matches from a quiet log wait in the buffer. Add --line-buffered to grep, and fflush() in awk or -u in sed for any later step, so each line is passed on at once.

What is the journalctl equivalent of tail -f?

journalctl -u SERVICE -f follows one systemd service's log live; add -n 100 to see the last 100 lines first. It covers services that log to the journal. Programs that write their own log files, such as nginx under /var/log/nginx, still need tail -F.

Related articles

All guides