watch runs a command over and over, full screen, and shows the latest output. Use it when you’re waiting for something to change: a file growing, a disk filling, connections building up, a deployment rolling out or a queue draining.
watch df -h
By default it re-runs every 2 seconds. Press Ctrl+C to quit.
Options you’ll use
watch -n 5 free -h # every 5 seconds (decimals work: -n 0.5)
watch -d free -h # highlight what changed since the last run
watch -d=cumulative free -h # keep highlighting everything that has ever changed
watch -t uptime # hide the header line
watch -g 'ls /tmp/done' # exit as soon as the output changes
watch -e 'some-check' # stop and wait for a key if the command fails
watch -c 'ls --color=always'# interpret colour codes
watch -p -n 1 date # precise timing (start every second, not 1 s after the last run)
-d is the most useful option. Changes jump out without you having to compare numbers.
Pipes and special characters: quote the command
watch passes its arguments to sh -c. Pipes, redirects and variables need quotes, so they run inside watch instead of in your shell:
watch 'ps aux --sort=-%mem | head -n 10'
watch "ss -tn state established | wc -l"
Without quotes, watch ps aux | head pipes watch’s own output into head, which breaks the display.
Use single quotes when the command contains $ that should be evaluated on every run:
watch 'echo "Load: $(cut -d" " -f1 /proc/loadavg)"'
Aliases from your ~/.bashrc don’t work inside watch, because it uses a fresh sh. Write out the full command.
Practical server examples
# disk filling during an upload, a backup or log growth
watch -d -n 10 'df -h /; du -sh /var/backups'
# memory and swap
watch -n 2 free -h
# a file being written
watch -n 1 'ls -lh /var/backups/db.sql.gz'
# established connections to the web server
watch -d "ss -tn state established '( sport = :443 )' | wc -l"
# top processes by memory
watch -n 3 'ps -eo pid,user,%mem,%cpu,cmd --sort=-%mem | head -n 15'
# Docker containers
watch -n 2 'docker ps --format "table {{.Names}}\t{{.Status}}"'
# Kubernetes pods during a rollout
watch -n 2 kubectl get pods
# MySQL activity
watch -n 2 "mysql -e 'SHOW PROCESSLIST'"
# Redis memory
watch -n 5 "redis-cli INFO memory | grep used_memory_human"
# certificate renewal: expiry date
watch -n 60 'echo | openssl s_client -connect example.com:443 2>/dev/null | openssl x509 -noout -enddate'
# wait for DNS to propagate
watch -n 30 dig +short example.com
Wait for something, then continue
watch -g exits when the output changes, which makes it handy in a one-liner:
watch -g -n 5 'systemctl is-active myapp' && echo "state changed"
In scripts, a plain loop is usually clearer:
until curl -sf http://127.0.0.1:3000/health > /dev/null; do sleep 2; done
echo "app is up"
watch vs tail -f vs top
| Want | Tool |
|---|---|
| A command’s output refreshed | watch |
| New lines appended to a log | tail -F or journalctl -f |
| Live processes and load | top / htop |
| Network traffic per interface | iftop, nload, vnstat |
See head and tail and htop.
Keep watching after you disconnect
watch stops when your SSH session ends. To leave it running on a remote server, start it inside tmux or screen. See tmux.
Quick reference
| Task | Command |
|---|---|
| Every 2 s | watch cmd |
| Custom interval | watch -n 10 cmd |
| Highlight changes | watch -d cmd |
| With pipes | watch 'cmd | grep x' |
| Exit on change | watch -g cmd |
| No header | watch -t cmd |
| Colours | watch -c cmd |