You can monitor file system changes in real time on Linux with inotify-tools, a small package that exposes the kernel's inotify API through two commands: inotifywait and inotifywatch. This guide installs the tools, watches a directory tree, raises the kernel limits that large projects need, and runs a permanent watcher under systemd so you get a log line every time a file is created, written, moved or deleted.
It is a practical way to trigger deploy hooks, catch unexpected writes to /etc, or audit what an application is doing with its own storage.
Before you start
- A Linux VPS running Ubuntu 22.04/24.04, Debian 12, AlmaLinux 8–9 or Rocky Linux 8–9.
- A sudo-capable user. If you only have root, see create a sudo user on Linux first.
- SSH access to the server — connect to your Linux VPS with SSH if you have not set up keys yet.
- A directory worth watching. Examples below use
/var/www/example.comand/etc.
Inotify is local-only. It reports changes made on the machine where it runs, not edits made to an NFS or CIFS share from another host.
Install inotify-tools
Install from the distribution repository:
# Ubuntu and Debian
sudo apt update && sudo apt install -y inotify-tools
# AlmaLinux and Rocky Linux
sudo dnf install -y inotify-toolsCheck that both binaries are present:
inotifywait --help | head -n 5
inotifywatch --help | head -n 5inotifywait waits for events; inotifywatch counts them over a period. This guide focuses on inotifywait.
Watch a directory in real time
The -m flag keeps the process running, -r adds subdirectories, and -e lists the events you care about:
sudo inotifywait -m -r -e create,close_write,delete,move \
--timefmt '%F %T' --format '%T %e %w%f' \
/var/www/example.comOpen a second terminal as the deploy user and touch a file:
touch /var/www/example.com/test.txtThe first terminal prints something like:
2026-01-01 10:12:04 CREATE /var/www/example.com/test.txt
2026-01-01 10:12:04 CLOSE_WRITE,CLOSE /var/www/example.com/test.txtUseful events include create, delete, modify, close_write, moved_to, moved_from, attrib and delete_self. Prefer close_write over modify for deploy hooks: it fires once when a writer closes the file, while modify fires on every write.
Filter noise with a regular expression:
sudo inotifywait -m -r -e close_write,moved_to \
--exclude '\.(log|tmp|swp)$' \
--format '%w%f' /var/www/example.comAdd -t 30 to exit after 30 seconds with no events — handy inside scripts and cron jobs.
Raise the inotify watch limits
Each watched directory consumes one watch, and the kernel caps how many a user may hold. Check the current values:
cat /proc/sys/fs/inotify/max_user_watches
cat /proc/sys/fs/inotify/max_user_instances
cat /proc/sys/fs/inotify/max_queued_eventsWarning: Raising these limits increases kernel memory use, roughly proportional to the watch count. Read the current numbers first and never set a value lower than what is already there.
Create /etc/sysctl.d/99-inotify.conf:
fs.inotify.max_user_watches = 262144
fs.inotify.max_user_instances = 512
fs.inotify.max_queued_events = 32768Apply and confirm:
sudo sysctl --system
cat /proc/sys/fs/inotify/max_user_watchesWatching a large tree such as a full container runtime or a big Git checkout is the usual reason to increase these. Only recursive watches over many thousands of directories need it.
Run the watcher as a systemd service
A background process started from an SSH session dies when the session ends. Put the watcher in a script and a service instead.
sudo tee /usr/local/bin/watch-www.sh >/dev/null <<'EOF'
#!/usr/bin/env bash
set -euo pipefail
WATCH_DIR="/var/www/example.com"
exec /usr/bin/inotifywait -m -r -e create,close_write,delete,move \
--timefmt '%F %T' --format '%T %e %w%f' "$WATCH_DIR"
EOF
sudo chmod 755 /usr/local/bin/watch-www.shCreate /etc/systemd/system/watch-www.service:
[Unit]
Description=Watch /var/www/example.com for file changes
After=network.target
[Service]
Type=simple
User=deploy
Group=deploy
ExecStart=/usr/local/bin/watch-www.sh
Restart=always
RestartSec=5
StandardOutput=append:/var/log/watch-www.log
StandardError=append:/var/log/watch-www.log
[Install]
WantedBy=multi-user.targetThe deploy user needs read and execute access on every directory in the tree; see chmod and chown if the log stays empty. Enable and start it:
sudo touch /var/log/watch-www.log
sudo chown deploy:deploy /var/log/watch-www.log
sudo systemctl daemon-reload
sudo systemctl enable --now watch-www.service
sudo systemctl status watch-www.serviceUse it for deploys and auditing
A common pattern is a loop that reacts to a single event, such as rebuilding a cache after an upload lands:
#!/usr/bin/env bash
set -euo pipefail
WATCH_DIR="/var/www/example.com/uploads"
while inotifywait -r -e close_write,moved_to --format '%w%f' "$WATCH_DIR"; do
echo "$(date -Is) changed: ${REPLY:-$WATCH_DIR}"
# run your rebuild or notification command here
doneFor auditing, watch configuration directories and separate sessions from bulk edits. To catch changes to system accounts, watch /etc/passwd, /etc/shadow and /etc/sudoers individually rather than all of /etc, which is noisy on a busy server. Also watch /etc/ssh/sshd_config after hardening, and pair this with the resource and log monitoring described in monitor CPU, memory, disk and logs on a Linux server.
Tip: inotify queues events in the kernel. If your handler is slow or the queue overflows, events are dropped silently. Use a short, non-blocking handler and raise fs.inotify.max_queued_events if you see gaps.
Check it works
- Confirm the service is active:
systemctl is-active watch-www.serviceshould printactive. - Generate an event:
sudo -u deploy touch /var/www/example.com/verify.txt. - Read the log tail:
sudo tail -n 5 /var/log/watch-www.log. You should see theCREATEline forverify.txt. - Delete the file and confirm a
DELETEline appears. - Watch the live counter while a test runs:
sudo inotifywatch -r -e create,delete -t 20 /var/www/example.comTroubleshooting
- "Failed to watch … upper limit on inotify watches reached" — the user has too many watches. Set
fs.inotify.max_user_watchesas shown above and restart the service. - No events at all on a network share — inotify does not report remote edits on NFS, CIFS or SMB mounts. Run the watcher on the server that owns the files.
- Missing events during heavy writes — the queue is overflowing or you are relying on
modify. Switch toclose_writeand increasefs.inotify.max_queued_events. - Duplicate lines for one file save — editors write to a temporary file and rename it, producing
CREATE,MOVED_TOandCLOSE_WRITE. Filter down tomoved_to,close_writeor exclude*.swpand*.tmp. - Permission denied on a subdirectory — the service user cannot traverse the tree. Fix ownership and mode on the parent directories, then reload the unit.
- Service restarts in a loop — the watched directory was deleted or renamed. Check
journalctl -u watch-www.serviceand confirm the path still exists.
Next steps
- Schedule tasks with cron on Linux to run one-shot inotify reports or rotate the watcher log.
- Back up your Linux VPS with rsync, restic and cron to pair change detection with real backups.
- Transfer files to your Linux VPS with SCP, SFTP, WinSCP or FileZilla to test upload events from your own machine.
