Monitor a local directory for changes and automatically archive every new or modified file to a remote server — with zero manual intervention.
inotify-toolsrsyncsystemdDebian · Ubuntu · RHELBash
Table of contents
- Prerequisites
- The rsync script
- The inotify watcher script
- systemd service unit
- Managing the service
- Logs & monitoring
- File summary
01 Prerequisites
Install the required packages on the source machine (the one being watched):
terminalbash
# Debian / Ubuntu
sudo apt update && sudo apt install -y inotify-tools rsync
# RHEL / CentOS / Fedora
sudo dnf install -y inotify-tools rsync
🔑rsync transfers files over SSH. You’ll need passwordless SSH key authentication between the source machine and the archive server so the service can run unattended. See my dedicated article on SSH key setup for the full walkthrough.
02 The rsync script
This script does the actual file transfer. It will be called by the inotify watcher every time a change is detected. Create it at /usr/local/bin/sync-to-archive.sh:
/usr/local/bin/sync-to-archive.shbash
#!/usr/bin/env bash
# ================================================================
# sync-to-archive.sh
# Syncs SOURCE_DIR to DEST_SERVER:DEST_DIR via rsync over SSH.
# Called by the inotify watcher on every detected event.
# ================================================================
# ── Configuration (edit these) ──────────────────────────────────
SOURCE_DIR="/path/to/watched/directory"
DEST_USER="user"
DEST_SERVER="archive-server"
DEST_DIR="/path/to/archive"
SSH_KEY="/home/$DEST_USER/.ssh/archive_key"
LOG_FILE="/var/log/rsync-archive.log"
# ────────────────────────────────────────────────────────────────
TIMESTAMP=$(date '+%Y-%m-%d %H:%M:%S')
echo "[$TIMESTAMP] Starting sync..." >> "$LOG_FILE"
rsync -rvaz \
-e "ssh -i $SSH_KEY -o StrictHostKeyChecking=no" \
"$SOURCE_DIR/" \
"$DEST_USER@$DEST_SERVER:$DEST_DIR/" >> "$LOG_FILE" 2>&1
EXIT_CODE=$?
if [ $EXIT_CODE -eq 0 ]; then
echo "[$TIMESTAMP] Sync successful." >> "$LOG_FILE"
else
echo "[$TIMESTAMP] ERROR: rsync exited with code $EXIT_CODE." >> "$LOG_FILE"
fi
exit $EXIT_CODE
What do the rsync flags do?
| Flag | Meaning |
|---|---|
| -r | Recursive — process all subdirectories. |
| -v | Verbose — log every transferred file. |
| -a | Archive mode — preserves permissions, timestamps, symlinks, and ownership. |
| -z | Compress data in transit to reduce bandwidth usage. |
Make it executable and run a quick manual test:
terminalbash
sudo chmod +x /usr/local/bin/sync-to-archive.sh
# Manual test — check the log afterwards
sudo /usr/local/bin/sync-to-archive.sh
cat /var/log/rsync-archive.log
03 The inotify watcher script
This script runs in a loop, watching your directory with inotifywait and triggering the rsync script on every relevant filesystem event. Create it at /usr/local/bin/watch-directory.sh:
/usr/local/bin/watch-directory.shbash
#!/usr/bin/env bash
# ================================================================
# watch-directory.sh
# Watches SOURCE_DIR with inotifywait and triggers rsync on
# every file creation, modification, move, or deletion.
# ================================================================
SOURCE_DIR="/path/to/watched/directory"
SYNC_SCRIPT="/usr/local/bin/sync-to-archive.sh"
LOG_FILE="/var/log/inotify-watch.log"
echo "[$(date '+%Y-%m-%d %H:%M:%S')] Watcher started: $SOURCE_DIR" >> "$LOG_FILE"
inotifywait -m -r \
-e close_write -e create -e delete -e moved_to \
--format '%T %w%f %e' --timefmt '%Y-%m-%d %H:%M:%S' \
"$SOURCE_DIR" 2>> "$LOG_FILE" | \
while read -r DATE TIME FILEPATH EVENT; do
echo "[$DATE $TIME] Event: $EVENT on $FILEPATH" >> "$LOG_FILE"
# Run rsync in the background so the loop isn't blocked
bash "$SYNC_SCRIPT" &
done
Watched inotify events
| Event | Triggers when… |
|---|---|
| close_write | A file is closed after being written. More reliable than IN_MODIFY because it fires once the write is complete. |
| create | A new file or directory is created. |
| delete | A file or directory is removed. |
| moved_to | A file is moved into the watched directory (e.g. via mv or a rename). |
💡rsync is launched in the background with & so the watcher loop is never blocked. If many files change at once, multiple rsync processes may run concurrently — that’s fine for most workloads. For high-frequency writes you may want to add a small debounce delay (sleep 2) before calling rsync.
terminalbash
sudo chmod +x /usr/local/bin/watch-directory.sh
04 systemd service unit
Wrapping the watcher in a systemd unit lets you enable/disable it at will, have it start automatically on boot, and benefit from automatic restarts on failure. Create the file below:
/etc/systemd/system/inotify-rsync.serviceini
[Unit]
Description=inotify directory watcher + rsync archive sync
Documentation=man:inotifywait(1) man:rsync(1)
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
ExecStart=/usr/local/bin/watch-directory.sh
Restart=on-failure
RestartSec=10
StandardOutput=journal
StandardError=journal
SyslogIdentifier=inotify-rsync
# Optional hardening — uncomment if running as a dedicated user
# User=archive-user
# Group=archive-group
# NoNewPrivileges=true
[Install]
WantedBy=multi-user.target
| Directive | Purpose |
|---|---|
| Type=simple | systemd manages the main process started by ExecStart directly. |
| Restart=on-failure | Automatically restarts the watcher if it exits with a non-zero code. |
| RestartSec=10 | Waits 10 seconds before each restart attempt. |
| After=network-online.target | Ensures the network is available before rsync tries to connect. |
| SyslogIdentifier | Tags all log lines with inotify-rsync for easy filtering with journalctl. |
05 Managing the service
Enable & start
terminalbash
# Reload systemd after creating or editing the unit file
sudo systemctl daemon-reload
# Enable auto-start at boot
sudo systemctl enable inotify-rsync.service
# Start it right now
sudo systemctl start inotify-rsync.service
# Check status
sudo systemctl status inotify-rsync.service
Stop & disable
terminalbash
# Stop the service (boot auto-start unchanged)
sudo systemctl stop inotify-rsync.service
# Remove from boot auto-start
sudo systemctl disable inotify-rsync.service
# Stop AND disable in one command
sudo systemctl disable --now inotify-rsync.service
Restart after changes
terminalbash
# After editing a script or the .service file
sudo systemctl daemon-reload
sudo systemctl restart inotify-rsync.service
06 Logs & monitoring
systemd journal
terminalbash
# Follow logs in real time
sudo journalctl -u inotify-rsync.service -f
# Last 50 lines
sudo journalctl -u inotify-rsync.service -n 50
# Logs since today
sudo journalctl -u inotify-rsync.service --since today
Application log files
terminalbash
# inotify events log
tail -f /var/log/inotify-watch.log
# rsync transfer log
tail -f /var/log/rsync-archive.log
⚠️Don’t forget to set up logrotate to prevent these log files from growing unbounded. Create /etc/logrotate.d/rsync-archive with rotate 7, daily, compress, and missingok.
07 File summary
Here’s every file created during this tutorial:
/usr/local/bin/sync-to-archive.sh
rsync script — performs the actual file transfer.
/usr/local/bin/watch-directory.sh
inotify script — watches the directory and triggers rsync.
/etc/systemd/system/inotify-rsync.service
systemd unit — manages the service lifecycle.
/var/log/inotify-watch.log
Log of filesystem events detected by inotify.
/var/log/rsync-archive.log
Log of all rsync transfer operations and outcomes.
✅Once everything is in place, activate the full setup with a single command:sudo systemctl enable --now inotify-rsync.service
Your automated archive sync is now live.