Large logs are awkward to open in an editor when the only lines you care about are the newest ones. The tail command in Linux solves that problem by printing the end of a file immediately, then staying attached with -f when you need live updates during a restart, deployment, or incident response session.
Practical tail usage starts with line counts, byte ranges, standard input, live log following, rotation-safe monitoring, multi-file output, and filtering with grep, awk, and sed. The GNU coreutils tail manual documents the full option set used on most Linux distributions, while BusyBox systems expose a smaller tail applet that needs a few compatibility notes.
Understand the tail Command
tail Command Basics
By default, tail prints the last 10 lines of each file you give it. Add -n to choose a line count, -c to count bytes instead of lines, or -f to keep reading as the file grows. If you omit the file name, tail reads from standard input, which makes it useful after commands such as dmesg, journalctl, grep, or cat.
The basic syntax is:
tail [OPTIONS] [FILE]
OPTIONS: Flags such as-n 50,-f,-F, or-c 100that change whattailprints.FILE: The file to read. Use-or omit the file name to read from standard input.
Use tail when the end of a file matters more than the whole file: recent error messages, the latest deployment event, the newest records in a generated report, or the last bytes of a binary payload.
On systemd-based systems,
journalctl -ffollows the journal directly and is often a better fit for services that do not write traditional files under/var/log. You can still pipejournalctloutput intotailwhen you want the last N entries from a wider journal query.
Essential tail Options by Task
These common patterns cover most day-to-day tail work:
| Task | Command Pattern | What It Does |
|---|---|---|
| Show the default ending | tail file.log | Prints the last 10 lines. |
| Choose a line count | tail -n 5 access.log | Prints the last 5 lines instead of the default 10. |
| Start at a specific line | tail -n +100 app.log | Prints from line 100 through the end of the file. |
| Read by bytes | tail -c 100 payload.bin | Prints the final 100 bytes, useful for binary or fixed-width data. |
| Follow a growing file | tail -f app.log | Prints new lines as the file grows until you press Ctrl+C. |
| Follow through log rotation | tail -F app.log | Tracks the file name and retries if the file is replaced or temporarily missing. |
| Follow multiple files | tail -f access.log error.log | Prints file headers so you can tell which file produced each line. |
| Suppress file headers | tail -q file1 file2 | Hides headers when multiple files are read. |
| Force file headers | tail -v file.log | Shows the file name header even when only one file is read. |
| Stop when a process exits | tail --pid "$pid" -f app.log | GNU-only option that exits after the watched process ends. |
| Adjust polling interval | tail -s 5 -f app.log | Uses a 5-second sleep interval when polling is active. |
| Use NUL delimiters | tail -z -n 1 records.dat | GNU-only option that treats NUL bytes as record separators. |
Verify tail and Coreutils Availability
On normal Linux installations, tail comes from GNU coreutils and is already present. Confirm the shell can find it before troubleshooting package names:
command -v tail
/usr/bin/tail
GNU systems also support a short version check. Example output from a current GNU build looks like:
tail --version | head -n 1
tail (GNU coreutils) 9.11
The exact version depends on your distribution. If command -v tail fails on a very small image or container, install the coreutils package, not a package named tail.
APT-based systems use:
sudo apt install coreutils
DNF-based systems use:
sudo dnf install coreutils
Arch-based systems use:
sudo pacman -S coreutils
openSUSE systems use:
sudo zypper install coreutils
Alpine systems commonly run package commands as root. Use doas or sudo only if your image has that privilege tool configured:
apk add coreutils
Older searches for
tailf, including Ubuntu 14.04-era package questions, refer to a deprecated util-linux helper. Current Linux systems should usetail -ffor ordinary following andtail -Fwhen log rotation can replace the file.
BusyBox tail Compatibility
Minimal distributions and embedded systems may provide BusyBox tail instead of GNU tail. The BusyBox documentation lists a smaller option set: -c, -n, -f, -q, -s, and -v. Check the applet help on the actual system before using GNU-only options:
busybox tail --help
tail [OPTIONS] [FILE]...
Print last 10 lines of each FILE (or stdin) to stdout. With more than one FILE, precede each with a filename header.
Options:
-f Print data as file grows
-s SECONDS Wait SECONDS between reads with -f
-n N[kbm] Print last N lines
-c N[kbm] Print last N bytes
-q Never print headers
-v Always print headers
N may be suffixed by k (x1024), b (x512), or m (x1024^2). If N starts with a '+', output begins with the Nth item from the start of each file, not from the end.
Use GNU coreutils when you need -F, --follow=name, --retry, --pid, --version, or -z. If a BusyBox build adds local extras, trust busybox tail --help for that system rather than assuming GNU behavior.
For BusyBox query patterns such as tail -n +1 -f app.log, the +1 form starts at the first line and then follows the file. It is still not the same as GNU tail -F, because BusyBox does not document rotation-safe -F or --follow=name.
On Windows, PowerShell has similar file-reading behavior with Get-Content -Tail 20 file.log and live following with Get-Content -Wait file.log. Inside WSL, use the normal Linux tail command.
Practical tail Command Examples
View Recent Lines with tail -n
The most common tail task is printing a chosen number of recent lines. For example, print the last 5 lines of access.log:
tail -n 5 access.log
192.168.1.10 - - [02/Jan/2026:14:28:01 +0000] "GET / HTTP/1.1" 200 612 192.168.1.11 - - [02/Jan/2026:14:28:04 +0000] "GET /assets/site.css HTTP/1.1" 200 1842 192.168.1.12 - - [02/Jan/2026:14:28:07 +0000] "POST /login HTTP/1.1" 401 498 192.168.1.13 - - [02/Jan/2026:14:28:12 +0000] "GET /dashboard HTTP/1.1" 302 0 192.168.1.14 - - [02/Jan/2026:14:28:15 +0000] "GET /favicon.ico HTTP/1.1" 404 153
For system logs, the same pattern works with real log paths, usually with sudo when the file is protected:
sudo tail -n 20 /var/log/syslog
On RHEL-family systems, traditional system messages commonly live under /var/log/messages instead:
sudo tail -n 20 /var/log/messages
Start tail Output at a Specific Line
A plus sign changes -n from “show the last N lines” to “start at line N.” This prints from line 100 through the end of the file:
tail -n +100 app.log
That form is useful when a log line number appears in another tool, or when you want everything after a known marker without opening the full file in an editor.
Read tail Input from a Pipeline
When no file is supplied, tail reads standard input. This makes it a natural final stage after a command that can produce too much output:
dmesg | tail -n 20
You can also trim journal output after a broader query:
journalctl -u nginx --no-pager | tail -n 50
Do not add -f to a pipe expecting tail itself to follow a file. For live journal output, make the upstream command follow with journalctl -f, then use other filters as needed.
Follow Logs in Real Time with tail -f and tail -F
Use -f when a file is actively growing and you want new lines as they are written. Press Ctrl+C to stop following:
tail -f /var/log/nginx/access.log
192.168.1.20 - - [02/Jan/2026:15:05:10 +0000] "GET /index.html HTTP/1.1" 200 1234 192.168.1.21 - - [02/Jan/2026:15:05:15 +0000] "POST /api/login HTTP/1.1" 401 512
Use -F for production log files that a rotation tool may rename and recreate:
tail -F /var/log/nginx/access.log
GNU tail -F is equivalent to --follow=name --retry. It tracks the path, not only the already-open file descriptor, so it can reopen the log after rotation.
Monitor Multiple Files with tail
Pass more than one file when you need to compare related logs side by side. tail prints headers as each file contributes output:
tail -f /var/log/nginx/access.log /var/log/nginx/error.log
==> /var/log/nginx/access.log <== 192.168.1.10 - - [02/Jan/2026:15:12:01 +0000] "GET /missing.html HTTP/1.1" 404 153 ==> /var/log/nginx/error.log <== 2026/01/02 15:12:01 [error] 1234#0: *44 open() "/var/www/html/missing.html" failed (2: No such file or directory)
Add -q only when file names would be noise in a script. During manual troubleshooting, the headers are usually worth keeping.
Filter tail Output with grep, awk, and sed
The grep command guide is the natural companion when you need matching log lines only. Use --line-buffered with live tail -f pipelines so matches appear immediately:
tail -f /var/log/auth.log | grep --line-buffered "Failed password"
On RHEL-family systems, authentication events commonly use /var/log/secure instead:
tail -f /var/log/secure | grep --line-buffered "Failed password"
Jan 02 15:18:30 server sshd[1234]: Failed password for invalid user admin from 192.168.1.100 port 52944 ssh2 Jan 02 15:18:41 server sshd[1235]: Failed password for root from 10.0.0.5 port 53102 ssh2
Use awk when you want specific fields from a structured line. In the standard NGINX combined log format, the client address is field 1 and the requested path is field 7:
tail -n 2 access.log | awk '{print $1, $7}'
192.168.1.1 /home.html 10.0.0.2 /login.html
Use sed command examples when you need to normalize repeated text before comparison. This command replaces ISO-style dates with a stable placeholder so recurring errors are easier to compare:
tail -n 50 app.log | sed 's/[0-9]\{4\}-[0-9]\{2\}-[0-9]\{2\}/DATE/g'
DATE ERROR: Unable to connect to database. DATE ERROR: Timeout occurred.
The
awk '/error/'pattern is case-sensitive. If logs mixerror,Error, andERROR, useawk 'tolower($0) ~ /error/'orgrep -i errorso the case difference does not hide relevant lines.
Inspect Bytes with tail -c
Use -c when the file ending is not line-oriented, or when you need the last few bytes of a payload, checksum trailer, or generated binary file. Pipe the result through od for a readable hex and character view:
tail -c 16 payload.bin | od -An -tx1 -c
0a 62 79 74 65 73 3d 31 30 32 34 0a 45 4e 44 21 \n b y t e s = 1 0 2 4 \n E N D !
For plain text logs, prefer -n. Byte counts can split a line in the middle, which is useful for binary checks but less readable for normal log review.
Advanced tail Techniques
Stop Following When a Process Exits
GNU tail can stop automatically when the process writing the file exits. This is useful for builds, imports, and one-shot jobs where a normal tail -f would keep running after the work is done:
make >build.log 2>&1 & build_pid=$!
tail --pid "$build_pid" -f build.log
The process being watched and the tail command must run on the same machine. If the PID is wrong, reused, or not the process writing the file, tail may exit too early or too late.
Reduce Polling Frequency on Quiet Filesystems
On Linux, GNU tail -f usually uses inotify for prompt event-driven updates. The -s interval matters when polling is active, such as on some network filesystems, with some nonstandard files, or when paired with --pid checks:
tail -s 5 -f /mnt/nfs/app.log
Use a longer interval when near-instant updates do not matter. Use the default when you need errors to appear as soon as possible.
Read NUL-Delimited Records with tail -z
GNU tail -z treats NUL bytes as record separators instead of newlines. This is mainly useful in pipelines that preserve arbitrary file names or binary-safe records:
printf 'one\0two\0three\0' | tail -z -n 1 | tr '\0' '\n'
three
This option is GNU-specific. BusyBox tail does not document -z, so avoid it in scripts meant for minimal systems unless you have verified the target image.
Avoid Obsolete tail Syntax in Scripts
Older examples sometimes use compact forms such as tail -1 file.log or historical plus syntax. Prefer the explicit POSIX-style forms in new scripts:
tail -n 1 file.log
tail -n +4 file.log
tail -c 4 file.bin
The explicit forms are easier to read and avoid edge cases where older compatibility parsing can interpret an argument differently than you intended.
Troubleshoot Common tail Errors
Permission Denied When Reading Logs
Protected system logs often require elevated access. A regular user may see:
tail: cannot open '/var/log/syslog' for reading: Permission denied
Use sudo for one-off troubleshooting:
sudo tail -f /var/log/syslog
For repeated access, first check the file ownership and group instead of guessing the right group name:
ls -l /var/log/syslog
-rw-r----- 1 syslog adm 95342 Jan 02 14:20 /var/log/syslog
On Debian and Ubuntu systems where the log is group-readable by adm, add your user to that group, then log out and back in:
sudo usermod -aG adm "$USER"
Verify the new session sees the group before retrying without sudo:
groups
youruser adm sudo
Journal-only workflows may use a different access path, such as the systemd-journal group for journal reads. Check the file or journal access model on the target distribution before adding broad group memberships.
tail -f Stops Updating After Log Rotation
If a rotation tool renames the active log and creates a fresh file, tail -f can keep watching the old file descriptor. New entries go to the new path, but your terminal appears quiet.
Switch to -F for rotation-safe following:
tail -F /var/log/nginx/access.log
When GNU tail notices replacement, it prints a status line like:
tail: '/var/log/nginx/access.log' has been replaced; following new file
That message is a good sign during rotation, not a failure.
tail Reports No Such File or Directory
If the file does not exist yet, a normal read fails immediately:
tail: cannot open 'app.log' for reading: No such file or directory
If a service will create the file shortly, use GNU -F so tail keeps retrying:
tail -F app.log
If the file should already exist, verify the path first with ls -l app.log or check the service configuration that owns the log location.
Delayed Output When Piping tail Through grep or awk
When a live tail -f pipeline appears to pause even though the log is active, the downstream command may be buffering output.
For GNU grep, add --line-buffered:
tail -f app.log | grep --line-buffered "ERROR"
For tools that do not have their own line-buffering option, GNU stdbuf can force line-buffered output:
tail -f app.log | stdbuf -oL awk '/error/'
BusyBox and some non-GNU environments may not provide stdbuf. In that case, prefer a tool with an explicit line-buffering option or run the filter on saved output instead of a live stream.
GNU tail Options Fail on BusyBox
BusyBox systems usually reject GNU-only options such as --version, --pid, --follow=name, and -z:
tail: unrecognized option: version
Confirm the applet option list:
busybox tail --help
If the script needs GNU behavior, install coreutils and confirm that the shell resolves tail to the GNU binary:
command -v tail
tail --version | head -n 1
For portable scripts, stick to the BusyBox-documented options unless you control the target image.
Conclusion
The tail command is the fastest way to inspect the end of a file, watch logs as they change, and trim noisy command output to the part that matters. Use -n for line counts, -f for live files, -F for rotated logs, -c for byte-level checks, and GNU-only options such as --pid or -z only when the target system actually provides GNU coreutils.


Formatting tips for your comment
You can use basic HTML to format your comment. Useful tags currently allowed in published comments:
<code>command</code>command<strong>bold</strong><em>italic</em><blockquote>quote</blockquote>