Follow Rotating Logs Safely with tail

GNU tail can print the end of a file, an exact range, or a log that keeps rotating underneath it, all without changing a byte. This walks through the defaults, the options that change what "end" means, and the follow modes that keep working after logrotate swaps the file out from under you. The installed command here is GNU coreutils 9.4. Allow about ten minutes; you need a shell and a readable text file, and no elevated privileges are normally required.

Checkpoint: This guide only reads files and starts a foreground monitor. It does not edit, truncate, delete or restart anything. If a file is not readable, fix its permissions through your normal administrative process or ask its owner; do not make a log world-readable just to inspect it.

1. Confirm the installed command

Check which executable your shell will run and record its version. Both are ordinary, read-only commands:

$ command -v tail
/usr/bin/tail
$ tail --version | head -n 1
tail (GNU coreutils) 9.4

Your path or version may differ. This guide follows GNU tail, so do not assume an option described here exists on a different implementation, such as a minimal embedded system.

2. Print the last ten lines

With one file operand, tail prints its last ten lines by default. Use a harmless temporary file to reproduce this without touching an application log:

$ printf 'line %s\n' 1 2 3 4 5 6 7 8 9 10 11 12 > /tmp/tail-example.txt
$ tail /tmp/tail-example.txt
line 3
line 4
line 5
line 6
line 7
line 8
line 9
line 10
line 11
line 12

The redirection creates or replaces the temporary example file, so keep it pointed at /tmp/tail-example.txt, never a real log. The same command against an existing file does not change that file.

3. Choose lines deliberately

Use -n NUM when ten lines is the wrong amount. This prints the final three lines:

$ tail -n 3 /tmp/tail-example.txt
line 10
line 11
line 12

A plus sign changes the meaning from "last" to "starting at". -n +5 prints from line 5 through the end, skipping the first four lines:

$ tail -n +5 /tmp/tail-example.txt | head -n 2
line 5
line 6

The head in that verification command only limits what you see after tail has already selected the range; it does not make tail -n +5 stop reading early.

Checkpoint: Use -n for records separated by newlines. Do not use it to estimate bytes in a multi-byte character encoding, where one character can occupy more than one byte.

4. Read standard input and compare files

With no file operand, or with -, GNU tail reads standard input, which makes it useful at the end of a pipeline:

$ printf 'alpha\nbeta\ngamma\n' | tail -n 1
gamma

Give it more than one file and tail adds a header before each one, identifying the source, which helps when checking several logs at once:

$ printf 'one\ntwo\n' > /tmp/tail-a.txt
$ printf 'three\nfour\n' > /tmp/tail-b.txt
$ tail -n 1 /tmp/tail-a.txt /tmp/tail-b.txt
==> /tmp/tail-a.txt <==
two

==> /tmp/tail-b.txt <==
four

Those headers can surprise a parser expecting only data. Add -q or --quiet to suppress them, or leave them on when a human needs the file names. -v does the opposite and always prints headers, even for a single file.

5. Select bytes when the boundary is byte-based

Use -c NUM for bytes rather than lines. A positive number selects the last bytes:

$ printf 'abcdef' | tail -c 3
def

With +NUM, counting starts at byte 1 and output begins there:

$ printf 'abcdef' | tail -c +4
def

GNU tail also accepts suffixes: 10K means 10 times 1024 bytes, 10kB means 10 times 1000 bytes, and binary spellings such as MiB are accepted too. If the data is text, prefer -n unless you have a specific byte-level reason, since cutting through a multi-byte character can produce invalid text.

6. Follow a log as it grows

tail -f FILE prints the current end, then waits for appended data. It runs in the foreground, so press Ctrl+C when you are done:

$ tail -f /var/log/example.log
... existing final lines appear here ...
^C

This normally follows the open file descriptor. If a logger renames the old file and creates a new one at the same path, that default leaves you watching the old file. For the common rotation case, follow the name instead:

$ tail --follow=name --retry /var/log/example.log

--follow=name checks the named path and can move to the replacement file. --retry keeps trying if the path is temporarily missing. If the log rotates by renaming but the replacement is not created promptly, expect a diagnostic and a quiet period with no new lines. This still does not alter the log.

Some systems fall back to polling, with a default sleep interval of about one second. Set --sleep-interval=2 when a slower check is acceptable, or a smaller value once you have confirmed the extra polling load is fine. With inotify support, GNU tail can react to changes without waiting for the full interval.

7. Stop a monitor with its owner

A forgotten tail -f can keep running long after the task that launched it has finished. When following a log for a bounded operation, use --pid with the process ID that owns it:

$ tail --follow=name --pid=12345 /var/log/example.log

Replace 12345 with the real process ID. GNU tail checks that process at least once per sleep interval and exits after it dies. Verify the ID before running the command; the wrong process can leave the monitor running, or stop it at the wrong time. If you do not need this automation, Ctrl+C remains the simple recovery path.

Common failures and safe checks

tail: cannot open ... usually means the path is wrong, the file does not exist yet, or your account cannot read it. Check the path with ls -l -- and confirm the parent directories exist before touching permissions. If the file appears later, pair --retry with name following:

$ tail --follow=name --retry /path/to/application.log

A command that looks stuck may simply be following a file. Check whether you used -f, --follow, or a pipeline whose producer is still open, then press Ctrl+C to return to the shell. If another program writes binary data, use -c only when you understand the byte layout; tail does not parse records or repair encoding.

Done means