Privacy Tools Guide

inotifywait uses the Linux kernel’s inotify API to watch files and directories for changes in real time. Unlike polling-based tools, it receives kernel events instantly with zero CPU overhead when idle. This makes it ideal for security monitoring, automated backup triggers, and detecting unauthorized file modifications.

Installation

sudo apt install inotify-tools
inotifywait --version

Basic Usage

# Watch a single file
inotifywait -m /etc/passwd

# Watch a directory recursively
inotifywait -m -r /var/www/html

# Watch for specific events only
inotifywait -m -e modify,create,delete,moved_from,moved_to /etc/

Common event types: access, modify, attrib, create, delete, moved_from, moved_to, close_write.

Formatted Output

# Human-readable with timestamps
inotifywait -m -r \
  --format '%T %w %f %e' \
  --timefmt '%Y-%m-%d %H:%M:%S' \
  /etc/ /var/www/

# CSV format
inotifywait -m -r \
  --csv \
  --format '%T,%w,%f,%e' \
  --timefmt '%Y-%m-%dT%H:%M:%S' \
  /home/

Script: Alert on Unauthorized Changes

#!/bin/bash
# /usr/local/bin/watch-critical-dirs.sh

WATCH_DIRS="/etc /usr/bin /usr/sbin /bin /sbin"
LOG="/var/log/file-watch.log"
ALERT_EMAIL="admin@example.com"

log() { echo "$(date '+%Y-%m-%d %H:%M:%S') $*" | tee -a "$LOG"; }

log "Starting file integrity monitor"

inotifywait -m -r \
  --format '%T %w%f %e' \
  --timefmt '%Y-%m-%dT%H:%M:%S' \
  -e modify,create,delete,attrib,moved_from,moved_to \
  $WATCH_DIRS 2>/dev/null | \
while read -r timestamp filepath event; do
    case "$filepath" in
        */proc/*|*/.git/*|*/tmp/*) continue ;;
    esac

    log "ALERT: $event on $filepath at $timestamp"

    if [[ "$filepath" == /etc/passwd || \
          "$filepath" == /etc/shadow || \
          "$filepath" == /etc/sudoers* || \
          "$filepath" =~ /usr/(s)?bin/ ]]; then
        echo "CRITICAL: $event on $filepath at $timestamp" | \
          mail -s "File Integrity Alert: $filepath" "$ALERT_EMAIL" 2>/dev/null || \
          logger -p auth.crit "FILE MONITOR: $event on $filepath"
    fi
done

Script: Detect Webshell Drops

#!/bin/bash
# /usr/local/bin/watch-webroot.sh

WEBROOT="/var/www/html"
QUARANTINE="/var/quarantine"
LOG="/var/log/webshell-watch.log"

mkdir -p "$QUARANTINE"
log() { echo "$(date '+%Y-%m-%dT%H:%M:%S') $*" >> "$LOG"; }

inotifywait -m -r \
  --format '%w%f %e' \
  -e create,moved_to,close_write \
  "$WEBROOT" 2>/dev/null | \
while read -r filepath event; do
    case "${filepath,,}" in
        *.php|*.php5|*.phtml|*.jsp|*.asp|*.aspx|*.sh)
            if command -v clamscan &>/dev/null; then
                result=$(clamscan --quiet "$filepath" 2>&1)
                if [ $? -ne 0 ]; then
                    log "MALWARE DETECTED: $filepath"
                    mv "$filepath" "$QUARANTINE/"
                    logger -p auth.crit "WEBSHELL QUARANTINED: $filepath"
                    continue
                fi
            fi
            if [[ "$event" == *CREATE* ]]; then
                log "NEW PHP FILE: $filepath"
                logger -p auth.warning "NEW PHP FILE: $filepath"
            fi
            ;;
    esac
done

Systemd Service

sudo tee /etc/systemd/system/file-watch.service > /dev/null <<'EOF'
[Unit]
Description=File integrity monitoring with inotifywait
After=network.target

[Service]
Type=simple
ExecStart=/usr/local/bin/watch-critical-dirs.sh
Restart=on-failure
RestartSec=10
User=root

[Install]
WantedBy=multi-user.target
EOF

sudo systemctl daemon-reload
sudo systemctl enable --now file-watch

Watching for Specific Patterns

# Alert on new executables in temp directories
inotifywait -m -r \
  --format '%w%f' \
  -e create \
  /tmp /var/tmp /dev/shm | \
while read -r filepath; do
    if [ -x "$filepath" ]; then
        logger -p auth.crit "EXECUTABLE IN TEMP: $filepath"
    fi
done

# Watch for crontab modifications (persistence mechanism)
inotifywait -m \
  -e modify,create \
  /etc/crontab /etc/cron.d/ /var/spool/cron/ 2>/dev/null | \
while read -r path event file; do
    logger -p auth.warning "CRONTAB MODIFIED: ${path}${file} ($event)"
done

inotifywait vs AIDE

inotifywait gives real-time alerting for immediate response. AIDE provides forensic baseline comparison that detects rootkit-level changes. Use both: inotifywait for rapid alerting, AIDE for periodic deep verification.

Advanced Monitoring Patterns

Detecting Supply Chain Attacks via File Integrity

Supply chain attacks often modify trusted binaries. Detect them by watching for unexpected changes to system executables:

#!/bin/bash
# /usr/local/bin/detect-supply-chain-changes.sh

CRITICAL_BINARIES=(
  "/usr/bin/sudo"
  "/usr/bin/ssh"
  "/usr/bin/curl"
  "/usr/bin/wget"
  "/bin/bash"
  "/bin/sh"
)

BASELINE_DIR="/var/lib/file-baseline"
mkdir -p "$BASELINE_DIR"

# Create baseline hashes on first run
for binary in "${CRITICAL_BINARIES[@]}"; do
  sha256sum "$binary" > "$BASELINE_DIR/$(basename $binary).hash"
done

# Monitor for changes
inotifywait -m -e modify,attrib "${CRITICAL_BINARIES[@]}" | \
while read path action file; do
  current_hash=$(sha256sum "${path}${file}" | cut -d' ' -f1)
  stored_hash=$(cat "$BASELINE_DIR/$(basename ${path}${file}).hash" 2>/dev/null | cut -d' ' -f1)

  if [ "$current_hash" != "$stored_hash" ]; then
    logger -p auth.crit "SUPPLY_CHAIN_ALERT: ${path}${file} modified"
    echo "ALERT: $current_hash vs $stored_hash"

    # Quarantine immediately
    mv "${path}${file}" "${path}${file}.quarantined"
    logger -p auth.crit "Binary quarantined: ${path}${file}"
  fi
done

This catches scenarios where compromised package updates or repository mirrors replace binaries with malicious versions.

Detecting Privilege Escalation via setuid Changes

Attackers often create setuid binaries as persistence mechanisms. Monitor for suspicious setuid assignments:

#!/bin/bash
# /usr/local/bin/detect-setuid-changes.sh

WATCH_DIRS=(
  "/usr/bin"
  "/usr/sbin"
  "/bin"
  "/sbin"
  "/usr/local/bin"
  "/home"
)

# Build baseline of current setuid binaries
BASELINE="/var/lib/setuid-baseline.txt"
find "${WATCH_DIRS[@]}" -perm /4000 -type f -exec ls -lh {} \; > "$BASELINE" 2>/dev/null

log() { echo "[$(date '+%Y-%m-%dT%H:%M:%S')] $*" >> /var/log/setuid-monitor.log; }

log "Starting setuid monitoring"

inotifywait -m -r -e attrib "${WATCH_DIRS[@]}" 2>/dev/null | \
while read path action file; do
  filepath="${path}${file}"

  # Check if file now has setuid
  if [ -u "$filepath" ]; then
    # Check if it's in baseline
    if ! grep -q "$filepath" "$BASELINE" 2>/dev/null; then
      log "ALERT: New setuid binary detected: $filepath"
      ls -lh "$filepath" >> /var/log/setuid-monitor.log

      # Log to syslog
      logger -p auth.crit "SETUID_ALERT: New setuid binary: $filepath"

      # Optional: remove setuid bit immediately
      chmod u-s "$filepath"
      log "Removed setuid bit from $filepath"
    fi
  fi
done

Real-Time Backup Triggering

Use inotifywait to trigger backups the moment critical files change, rather than relying on scheduled backups:

#!/bin/bash
# /usr/local/bin/realtime-backup.sh

CRITICAL_PATHS=(
  "/etc"
  "/var/www"
  "/srv/app"
  "/home"
)

BACKUP_DEST="/backup/realtime-$(date +%Y%m%d)"
LOG="/var/log/realtime-backup.log"

mkdir -p "$BACKUP_DEST"

log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" | tee -a "$LOG"; }

# Track recently backed-up files to avoid redundant backups
BACKUP_WINDOW_SECONDS=300
declare -A last_backup_time

log "Starting real-time backup monitor"

inotifywait -m -r \
  --format '%w%f' \
  -e modify,create,delete \
  "${CRITICAL_PATHS[@]}" 2>/dev/null | \
while read filepath; do
  # Skip common temp/cache files
  case "$filepath" in
    */.git/*|*/__pycache__/*|*/.venv/*|*/node_modules/*|*/.cache/*) continue ;;
  esac

  # Debounce: don't back up same file more than once per 5 minutes
  file_key=$(echo "$filepath" | md5sum | cut -d' ' -f1)
  current_time=$(date +%s)
  last_time=${last_backup_time[$file_key]:-0}

  if [ $((current_time - last_time)) -lt $BACKUP_WINDOW_SECONDS ]; then
    continue
  fi

  last_backup_time[$file_key]=$current_time

  # Backup the file
  backup_subdir=$(dirname "$filepath" | sed 's|/|_|g')
  mkdir -p "$BACKUP_DEST/$backup_subdir"

  if [ -e "$filepath" ]; then
    cp -p "$filepath" "$BACKUP_DEST/$backup_subdir/$(basename $filepath).$(date +%s)"
    log "Backed up: $filepath"
  else
    log "Deleted (not backed up): $filepath"
  fi
done

Detecting Configuration Drift in Container Orchestration

For Kubernetes clusters, detect when config files drift from their committed versions:

#!/bin/bash
# /usr/local/bin/detect-k8s-config-drift.sh

KUBECONFIG_DIR="/etc/kubernetes"
GIT_REPO="/var/lib/k8s-config-repo"

log() { echo "[$(date '+%Y-%m-%dT%H:%M:%S')] $*" | logger -p user.notice; }

# Initial commit of configs
if [ ! -d "$GIT_REPO/.git" ]; then
  mkdir -p "$GIT_REPO"
  cd "$GIT_REPO"
  git init
  cp -r "$KUBECONFIG_DIR"/* .
  git add -A
  git commit -m "Initial K8s config baseline"
  log "Created initial K8s config baseline"
fi

# Monitor for changes
inotifywait -m -r \
  --format '%w%f' \
  -e modify,create,delete \
  "$KUBECONFIG_DIR" 2>/dev/null | \
while read filepath; do
  # Sync to git repo
  relative_path=${filepath#$KUBECONFIG_DIR/}
  repo_path="$GIT_REPO/$relative_path"

  mkdir -p "$(dirname "$repo_path")"

  if [ -e "$filepath" ]; then
    cp "$filepath" "$repo_path"
  else
    rm -f "$repo_path"
  fi

  # Commit the change
  cd "$GIT_REPO"
  git add -A
  git commit -m "Config change: $relative_path" 2>/dev/null && \
    log "Config drift detected and committed: $relative_path"
done

Monitoring Log Files for Security Events

Watch application logs in real-time for security-relevant patterns:

#!/bin/bash
# /usr/local/bin/monitor-security-logs.sh

SECURITY_PATTERNS=(
  "Failed password"
  "authentication failure"
  "DENIED"
  "SUSPICIOUS"
  "ERROR.*auth"
  "invalid user"
  "root.*attempt"
)

LOG_FILES=(
  "/var/log/auth.log"
  "/var/log/secure"
  "/var/log/audit/audit.log"
  "/var/log/apache2/error.log"
)

inotifywait -m -e modify "${LOG_FILES[@]}" 2>/dev/null | \
while read path action file; do
  # Get new lines added since last check
  tail -f "$path" 2>/dev/null &
  tail_pid=$!

  sleep 0.5

  # Check each security pattern
  for pattern in "${SECURITY_PATTERNS[@]}"; do
    if grep -i "$pattern" "$path" | tail -5 | grep -q .; then
      matching_lines=$(grep -i "$pattern" "$path" | tail -5)
      logger -p auth.warning "Security event detected in $path:
$matching_lines"
    fi
  done

  kill $tail_pid 2>/dev/null
done

CPU-Efficient Long-Term Monitoring

For large directory trees, inotifywait can consume CPU. Optimize with strategic watches:

#!/bin/bash
# /usr/local/bin/efficient-dir-monitor.sh

# Watch only directories, not every file
# This drastically reduces inotify event volume

inotifywait -m -r \
  --exclude '(\.git|\.venv|node_modules|__pycache__|\.cache)' \
  --format '%w %e' \
  -e create,delete,moved_to,moved_from \
  /var/www /srv/app 2>/dev/null | \
while read dir event; do
  # Filter out noise
  case "$event" in
    CREATE|DELETE|MOVED_TO|MOVED_FROM)
      # Only process directory-level changes
      if [ -d "$dir" ]; then
        logger "Directory event: $event in $dir"
      fi
      ;;
  esac
done

Performance note: Watching 1 million files costs significant memory. Use --exclude aggressively to watch only what matters.