opscape

Viewing files

tail

Print the last lines of files, or follow a file as it grows with -f.

tail [OPTION...] [FILE...]

9 of 10 examples run in Debian 13.7 on 2026-10-31 · coreutils 9.7-3 · manual: tail(1)

tail prints the end of each file, 10 lines by default. On a regular file it seeks from the end instead of reading everything, so it is just as fast on a huge log as on a small one.

Two forms do most of the work. tail -n +K turns the meaning around and prints from line K to the end, which is how you skip a CSV header. tail -f keeps the file open and prints new lines as they are appended, which is how most people watch a log live; tail -F does the same but survives log rotation by reopening the file when it is replaced.

To see the start of a file use head; to scroll around a large one use less, whose F key also follows a growing file.

Examples

Show the last 10 lines

tail logs/app.log
explain
output
2026-03-14 08:03:45 WARN  failed login for user=alice from 198.51.100.7
2026-03-14 08:03:46 WARN  failed login for user=alice from 198.51.100.7
2026-03-14 08:05:09 INFO  user lookup id=7 not found
2026-03-14 08:11:47 WARN  permission denied: DELETE /api/users/3
2026-03-14 08:16:40 ERROR health check failed: database timeout after 5000ms
2026-03-14 08:16:50 INFO  health check recovered
2026-03-14 08:20:12 WARN  blocked request for /.env from 198.51.100.23
2026-03-14 08:25:00 INFO  user id=2 updated by api
2026-03-14 08:30:00 ERROR disk usage on /var at 91%
2026-03-14 08:30:05 INFO  cleanup job removed 42 temp files

The log has 12 lines, so the first two (server start and database connection) are left out.

Choose how many lines

tail -n 3 access.log
explain
output
198.51.100.23 - - [14/Mar/2026:08:20:12 +0000] "GET /.env HTTP/1.1" 404 58 "-" "Mozilla/5.0 (compatible; scanner/1.0)"
192.0.2.10 - - [14/Mar/2026:08:22:45 +0000] "GET /contact HTTP/1.1" 200 2875 "https://example.com/about" "Mozilla/5.0 (X11; Linux x86_64)"
203.0.113.42 - - [14/Mar/2026:08:25:00 +0000] "PUT /api/users/2 HTTP/1.1" 200 221 "-" "python-requests/2.31"

-n 3 prints the three most recent requests.

Skip a header line

tail -n +2 data/users.csv | head -n 3
explain
output
1,Alice Moreau,alice@example.com,engineering,2021-04-12
2,Bob Okafor,bob@example.com,sales,2019-11-03
3,Chen Wei,chen@example.com,engineering,2023-01-20

+2 means "start at line 2", so the CSV header is dropped and only data rows remain. The head just shortens the output here.

The last bytes of a file

tail -c 12 notes.txt
explain
output
e on web02.

-c 12 prints the final 12 bytes: e on web02. is 11 characters, and the newline that ends the file is the twelfth.

The end of several files

tail -n 1 logs/app.log logs/error.log
explain
output
==> logs/app.log <==
2026-03-14 08:30:05 INFO  cleanup job removed 42 temp files

==> logs/error.log <==
[2026-03-14 08:30:00] disk usage on /var at 91%

Each file gets a ==> name <== header; use -q to drop the headers.

Follow a file as it grows

(sleep 1; echo 'line added 1s later' >> logs/app.log) &
timeout 3 tail -n 2 -f logs/app.log
echo "tail stopped with status $?"
output
2026-03-14 08:30:00 ERROR disk usage on /var at 91%
2026-03-14 08:30:05 INFO  cleanup job removed 42 temp files
line added 1s later
tail stopped with status 124

-f prints the last 2 lines, then waits and prints each new line as another process appends it. tail -f never exits on its own; in a terminal you press Ctrl-C. Here timeout stops it after 3 seconds, hence status 124.

Keep following across log rotation

tail -F /var/log/nginx/access.log
explain

Not run in the sandbox: rename events inside the build sandbox's container filesystem are not delivered reliably, so the output would be misleading. No output is shown because none was captured.

When logrotate renames access.log and nginx starts a new one, -F notices, prints a notice such as '/var/log/nginx/access.log' has been replaced; following new file on standard error, and carries on with the new file. Plain -f would keep reading the renamed old file and show nothing new.

Stop following when a process exits

echo 'job started' > job.log
(sleep 2; echo 'job done' >> job.log) &
tail -f --pid=$! job.log
echo "tail returned after the job finished"
output
job started
job done
tail returned after the job finished

--pid makes tail -f exit once the given process has ended, so a script can stream a job's log and continue when the job is over, without timeout or Ctrl-C.

The last few matches in a log

grep WARN logs/app.log | tail -n 2
explain
output
2026-03-14 08:11:47 WARN  permission denied: DELETE /api/users/3
2026-03-14 08:20:12 WARN  blocked request for /.env from 198.51.100.23

Filter first, then keep the end of the filtered stream to get the two most recent warnings.

A file that does not exist

tail -n 1 nope.log
explain
outputexit 1
tail: cannot open 'nope.log' for reading: No such file or directory

tail prints an error and exits with status 1.

Common pitfalls

  • tail -f on a log that gets rotated (renamed and recreated) silently goes quiet: it keeps following the old file. Use tail -F (same as --follow=name --retry).

  • tail -n +2 means from line 2, not the last 2 lines. Dropping the plus sign by accident prints a different thing entirely.

  • tail -f file | grep x can appear to show nothing for a while because grep buffers its output when writing to a pipe. Add grep --line-buffered when another command reads grep's output.

GNU vs BSD and macOS

The examples above ran the GNU/Linux version. These are the differences that matter on macOS and the BSDs (written from their documentation, not run here).

  • --pid, -s/--sleep-interval with fractions, and long options are GNU. BSD/macOS tail supports -f, -F, -n, -c and +N, and has -r to print a file in reverse (GNU uses tac for that).

Options worth knowing

A selection, described in our own words. For the full list, see tail(1) or tail --help.

OptionWhat it does
-n, --lines [+]NUMPrint the last NUM lines, or from line NUM on with a leading plus.
-c, --bytes [+]NUMPrint the last NUM bytes, or from byte NUM on with a leading plus.
-f, --follow[=name|descriptor]Keep printing data appended to the file; by default follows the open file, not the name.
-FFollow by name and retry: keeps working when the file is rotated or recreated.
--retryKeep trying to open a file that is missing or becomes inaccessible.
--pid PIDWith -f, exit after process PID has ended.
-s, --sleep-interval SECONDSWith -f, how long to wait between checks when polling.
-q, --quietNever print file name headers.
-v, --verboseAlways print file name headers.
-z, --zero-terminatedLines end with a NUL byte instead of newline.

Related