SSD Nodes Learn 🎉 VPS from $4.99/mo
Guides Matt ConnorBy Matt Connor

The watch command in Linux, properly

The watch command re-runs anything every two seconds. Pick a sane interval, highlight the diff, exit on change, and quote pipes so watch runs them.

What the watch command does

The watch command re-runs another command at a fixed interval and redraws its output in the same place on screen. The default interval is two seconds. Reach for it when you are waiting for a number to move: a disk filling up, a container settling down, a certificate renewal landing, or a queue draining.

watch comes from procps-ng and is already installed on a fresh Ubuntu or Debian VPS as part of the procps package. Check your version before you trust any flag below, because two of the useful options are recent.

watch --version

On Ubuntu 24.04 that prints a line like watch from procps-ng 4.0.4. Now the simplest useful example.

watch df -h /

The screen clears and shows a header line, a blank line, then the output of df -h /:

Every 2.0s: df -h /                          server1: Sun Aug  9 10:21:44 2026

On the left is the interval and the exact command watch is running. On the right is the hostname and the current time. The clock is the useful half, because it proves the screen is still refreshing when the numbers below it are not moving. Press Ctrl+C to quit.

watch draws one frame that fits the terminal. Long lines wrap, -w truncates them instead, and anything past the bottom row is not drawn at all. There is no scrollback inside the frame, because every cycle overwrites the last one. That single fact decides most of what watch is good for.

How often should watch re-run the command?

Two seconds is only a default. Pick the interval from how fast the value changes and how much one run costs.

  • -n 0.5 for counters that move constantly, such as ss -s or an interface byte count. The smallest interval watch accepts is 0.1 seconds.
  • -n 30 or -n 60 for disks and certificates. A filesystem that fills over a day does not need thirty frames a minute.

watch runs the command, waits for it to finish, and only then sleeps for the interval. Runtime is added to the gap, so a command that takes eight seconds under -n 2 gives you a frame every ten seconds and keeps the machine busy the whole time. Add -p (--precise) and watch aims to start a run every interval seconds instead, measured from the start of one run to the start of the next.

If you always want a different default, export WATCH_INTERVAL in your shell profile. An explicit -n still wins over it. Run watch --help to confirm your build reads that variable.

Highlight what changed with -d

Reading a wall of identical text to find the one field that moved is hard work. -d (--differences) does it for you.

watch -n 30 -d 'df -h / /var'

Characters that differ from the previous frame are shown in reverse video, so the Use% cell lights up the moment it moves and everything else stays quiet. If /var is not a separate filesystem you will see the root filesystem listed twice, which is a fast way to learn how the box was partitioned.

The comparison is positional, which means watch lines the new frame up against the old one character by character. Output whose columns change width will light up almost entirely, so prefer commands with a stable layout. Current procps-ng also accepts watch --differences=permanent, which keeps every position that has ever changed highlighted instead of only the most recent change. Confirm it with watch --help before you depend on it.

How do I make watch exit when the output changes?

-g (--chgexit) stops watch the first time the output differs from the run before it. Your prompt comes back, so you can put something after it on the same line.

watch -n 10 -g 'systemctl is-active myapp' ; echo 'state changed'

This is the flag that turns watch into a "tell me when the deploy lands" tool. It has one trap, and it catches everybody: the comparison covers the whole output, so any clock or PID (process identifier) in it changes on the very first refresh and watch exits at once. systemctl status myapp prints elapsed time and memory use, so -g on it is useless. Reduce the command to the single stable fact you are waiting for, which is what systemctl is-active gives you.

Newer procps-ng adds the opposite test, --equexit <cycles>, which exits after the output has stayed the same for that many cycles. It answers "tell me when this has gone quiet". Check watch --help, because older builds do not have it.

Why does watch ignore my pipe and my glob?

This is the failure everyone hits once, and the cause sits in your shell rather than in watch.

watch docker compose ps | grep web

Your interactive shell reads the whole line before anything runs, and it splits that line at the pipe. It starts watch docker compose ps and connects watch's own screen output to grep. watch is no longer writing to a terminal, so the display is broken or empty, and grep is filtering redraw output instead of a container list.

Quote the whole pipeline so it arrives as one argument.

watch 'docker compose ps | grep web'

watch passes that string to sh -c, and that shell runs the pipeline once per cycle. Globs follow the same rule with a quieter symptom.

watch ls -l /var/log/*.log
watch 'ls -l /var/log/*.log'

The first line expands the glob once, in your shell, at the moment you press Enter. watch then re-runs a frozen list of filenames, so a log file created a minute later never appears. The second line hands the glob to watch, which expands it inside sh on every cycle, so new files show up on their own.

Quotes also decide when a variable or a command substitution is evaluated. watch "echo $(date)" runs date once, in your shell, then echoes that fixed string forever. watch 'echo $(date)' runs date every cycle. Single quotes mean later. Double quotes mean now.

Two more consequences of that sh -c:

  • Aliases and shell functions do not exist inside it. watch ll fails with sh: 1: ll: not found, because sh -c never reads your .bashrc. Write the real command out, or give the full path to a binary that is only on your interactive PATH.
  • Standard error is worth capturing. Put 2>&1 inside the quotes when you want error text laid out in the frame rather than scattered over it.

When quoting gets painful, -x (--exec) runs the command directly instead of through sh -c. Arguments with spaces get easier, and pipes and globs stop working completely, because there is no shell left to interpret them.

Privileges follow the same logic, since watch is an ordinary process. watch 'sudo ss -tulpn' hits a password prompt you cannot see or type into. Run sudo watch 'ss -tulpn' instead, and remember the whole loop then runs as root until you press Ctrl+C.

Four things worth watching on a VPS

A disk that is filling up

watch -n 30 -d 'df -h / /var'

df asks the kernel for figures it already holds, so it is cheap enough to repeat forever. du -sh /var/log is not, because it walks every file in the tree on every cycle. Under watch it reads the disk continuously and slows down the machine you are trying to diagnose. Run du once, or use ncdu, then leave df under watch to tell you whether the number is still climbing.

A container that keeps restarting

cd /srv/myapp
watch -n 5 -d 'docker compose ps'

The status column shows how long each container has been up. A container in a crash loop keeps resetting to "Up 2 seconds", and -d makes that obvious at a glance. Change directory first, because watch inherits the working directory of the shell that started it and docker compose needs its project directory. watch tells you a restart is happening. docker compose logs -f web tells you why. Once you know why, encode the answer as a Compose healthcheck that restarts the container for you so nobody has to sit and stare at a screen.

A certificate renewal landing

sudo -i
watch -n 60 -g 'openssl x509 -noout -enddate -in /etc/letsencrypt/live/example.com/fullchain.pem'

The command prints one line, notAfter=Nov 7 09:14:22 2026 GMT, and nothing else, so -g fires exactly when the file is replaced by a renewed one. The files under /etc/letsencrypt/live are readable by root only, which is why this runs in a root shell. Note that it watches the certificate on disk. A running web server keeps the old certificate in memory until it reloads, so check what is really being served with echo | openssl s_client -connect example.com:443 2>/dev/null | openssl x509 -noout -enddate, and do not put that command on a two second interval against a public host. The renewal itself belongs to a systemd service and timer, and systemctl list-timers shows when it will next run.

A queue draining

watch -n 15 'postqueue -p | tail -n 1'

That prints a summary line such as -- 24 Kbytes in 6 Requests., or Mail queue is empty once it is done. The same shape works for any queue you can count: watch -n 5 'find /srv/queue/incoming -type f | wc -l'.

Here watch reaches its limit. It compares one frame against the frame before it, and it cannot test a condition, so it can never tell you "the count reached zero". A plain shell loop can, using command substitution to capture the count on each pass:

until [ "$(find /srv/queue/incoming -type f | wc -l)" -eq 0 ]; do sleep 5; done; echo 'queue drained'

When the watch command is the wrong tool

watch re-runs your command with no memory of the previous run and no idea what that command does. That is fine for df. It is wrong in four situations.

  • Logs. watch redraws a fixed frame, so lines that appear and scroll away between two cycles are lost. Use journalctl -fu nginx or tail -f, which stream new lines as they are written.
  • Expensive commands. du -sh / or a query against a busy database becomes a permanent background load at any interval, because watch keeps starting it again.
  • Anything with side effects. A command that writes, posts, restarts or installs will do it again on every cycle. A curl against an API every two seconds is 43,200 requests a day, which is how a free API key gets suspended.
  • Long-lived checks. watch runs in your terminal and dies with your session. It alerts nobody and keeps no history after you close the laptop. For that you want a real status monitor such as Uptime Kuma, or a scheduled check that lives on the server.

The honest boundary is time. watch is for the ten minutes you spend waiting for one specific thing to happen. Anything that should still be checking tomorrow belongs in a scheduled timer or a real monitor.

Run watch inside tmux when you are on SSH

Over SSH, watch is only as durable as the connection. When the link drops, your shell receives SIGHUP and takes watch down with it. tmux fixes that, and it is what makes watch genuinely useful on a remote box.

tmux new -As ops
watch -n 30 -d 'df -h /'

Press Ctrl+b then d to detach. The loop keeps running on the server. Reconnect later with tmux attach -t ops and the frame is still updating, with the header clock proving it. Split the window and you can keep watch on one side and journalctl -f on the other, which is the whole idea behind a tmux based terminal workbench. When the same question applies to several boxes at once, that is a job for a tool built for managing many Linux servers rather than a wall of watch panes.

The screens quoted here are illustrative. Column layout and version strings differ between distributions and procps-ng releases, so read man watch on your own machine for the flags your build really has.

FAQ

Why does watch ignore the pipe in watch mycmd | grep foo?

Your shell splits the line at the pipe before watch ever starts, so it pipes watch's own screen drawing into grep, and the command's output is never filtered. Quote the whole pipeline instead: watch 'mycmd | grep foo'. watch hands that single argument to sh -c, which runs the pipeline once per cycle. The same rule applies to globs and to command substitution.

What interval should I use with watch?

Match it to how fast the value moves and how expensive one run is. Half a second suits fast counters, and 0.1 seconds is the smallest interval watch accepts. Thirty to sixty seconds suits disks and certificates. The command's runtime is added to the gap unless you pass -p, so a slow command under -n 1 simply runs back to back and never pauses.

Can watch follow a log file?

No. watch redraws one frame and keeps no history, so any line that appears and scrolls away between two cycles is gone for good. journalctl -fu <unit> and tail -f stream new lines as they arrive, which is what logs need. Keep watch for values that have a current state, such as a disk percentage or a queue length.

How do I make watch stop when the thing I am waiting for happens?

Use -g (--chgexit) and give it a command whose output stays identical until that event. Output containing a clock or a PID changes on the first refresh and exits immediately, so systemctl is-active myapp works where systemctl status myapp does not. For a real condition such as a count reaching zero, use a shell until loop, because watch can only detect that the output changed.

Why does watch say command not found for something that works in my shell?

watch passes the command to sh -c, and that shell does not read .bashrc, so your aliases and shell functions do not exist there. The message is usually sh: 1: ll: not found. Write the real command out, or use the full path for a binary that only sits on your interactive PATH.