FUTIA
GÜVENLIK9 min read

Linux File Integrity Monitoring: Bash + Telegram Alert System

Detect changed files on your server in 5 minutes. I built a real-time security monitoring structure with Bash script, inotify, and Telegram bot.

Linux File Integrity Monitoring: Bash + Telegram Alert System
Miraç Eroğlu
April 22, 2026

Last month, a config file was silently modified on the kamupersonelhaber.com server, and I noticed it 3 days later. That's when I realized manual log checking doesn't work. Would you like to receive a Telegram notification within 30 seconds when critical files change on your server? I set up this system in 2 hours, and it's now running on all FUTIA projects.

In this article, we'll build a file integrity monitoring system from scratch. Without installing any 3rd party tools, using only bash, inotify-tools, and Telegram bot API. The system will do the following: monitor critical files like /etc/nginx/, wp-config.php,.env, send you a Telegram message on any change, keep JSON logs to record which file changed and when. I'll show you a structure you can understand and adapt to your own needs, not just copy-paste ready scripts.

Why file integrity monitoring is important

Most security breaches start silently. A backdoor is added, a config line changes, a cron job is injected. By the time you notice, the attacker may have been roaming the system for days. I experienced this on doktorbul.com: a WordPress plugin was updated with a hidden eval() function inside. It sent spam mail from the server for 5 days, and the hosting provider blacklisted us.

The classic approach is: review logs once a week, investigate if there's anything suspicious. But this is a reactive method. File integrity monitoring is proactive: you take action the moment a change occurs. There are enterprise tools like Tripwire, AIDE, Samhain, but they mean 300-page documentation and complex config files. Overkill for a small project.

A Bash script is sufficient because:

  • inotify works at kernel level, minimal CPU consumption
  • Telegram bot API is 5 lines of curl command
  • When run as a systemd service, it becomes daemonized
  • Single file, easily versionable

In FUTIA projects, I monitor these files: /etc/nginx/sites-available/, /var/www/html/.env, /home/deploy/.ssh/authorized_keys, /etc/crontab. You set it up once, then forget about it. But when a change occurs, a notification drops on your phone.

Creating a Telegram bot and getting a token

First step is setting up a Telegram bot. You can handle it in 2 minutes with BotFather. Search for @BotFather on Telegram, type /newbot. Give it a bot name, choose a unique username. It will give you a token, something like: 6234567890:AAHdqTcvCH1vGWJxfSeofSAs0K5PALDsaw. Save this token somewhere safe, never put it in a public repo.

Now you need to find your chat ID. Send a message to your bot, then open this URL in your browser:

https://api.telegram.org/bot6234567890:AAHdqTcvCH1vGWJxfSeofSAs0K5PALDsaw/getUpdates

You'll see "chat":{"id":123456789} in the JSON response. This is your chat ID. With the bot token and chat ID, you can now send messages to Telegram from anywhere.

In FUTIA projects, I use a separate bot for each server. This way I instantly know which server the notification came from. For example, doktorbul-monitor, diolivo-watcher. Alternatively, you can use a single bot with different chat IDs (group or channel).

Run this command to test:

curl -s -X POST https://api.telegram.org/bot6234567890:AAHdqTcvCH1vGWJxfSeofSAs0K5PALDsaw/sendMessage -d chat_id=123456789 -d text="Test message"

If you received a notification on your phone, you're ready. If not, the token or chat ID is wrong. Check the getUpdates output again.

inotify-tools installation and basic usage

inotify is the Linux kernel's file system event monitoring mechanism. The inotify-tools package gives us the inotifywait command. On Ubuntu/Debian:

sudo apt update && sudo apt install inotify-tools -y

On RHEL/CentOS:

sudo yum install inotify-tools -y

A simple test:

inotifywait -m /etc/nginx/

This command starts monitoring the /etc/nginx/ directory. In another terminal, open nano /etc/nginx/nginx.conf, add a line, save. You'll see output like this in the first terminal:

/etc/nginx/ OPEN nginx.conf /etc/nginx/ MODIFY nginx.conf /etc/nginx/ CLOSE_WRITE,CLOSE nginx.conf

inotifywait can monitor these events: CREATE, DELETE, MODIFY, MOVE, CLOSE_WRITE. We generally use CLOSE_WRITE because it triggers when the file is completely written and closed. MODIFY triggers on every byte change, creating too much noise.

An important point: inotify doesn't do recursive monitoring. So if you're monitoring /var/www/html/, it only monitors files in that directory, not subdirectories. Add the -r flag for recursive monitoring:

inotifywait -m -r /var/www/html/

But be careful: in very large directory trees (like node_modules), inotify watch limits can cause problems. You may need to increase the /proc/sys/fs/inotify/max_user_watches value:

echo fs.inotify.max_user_watches=524288 | sudo tee -a /etc/sysctl.conf sudo sysctl -p

In FUTIA projects, I generally monitor specific files, not entire directories. For example, /var/www/html/.env, /var/www/html/wp-config.php. This way false positives are reduced.

Bash script: basic structure and Telegram integration

Now let's write the script. Create /usr/local/bin/file-integrity-monitor.sh:

#!/bin/bash

TELEGRAM_BOT_TOKEN="6234567890:AAHdqTcvCH1vGWJxfSeofSAs0K5PALDsaw" TELEGRAM_CHAT_ID="123456789" WATCH_PATHS=("/etc/nginx/sites-available" "/var/www/html/.env" "/home/deploy/.ssh/authorized_keys") LOG_FILE="/var/log/file-integrity.log"

send_telegram() { local message="$1" curl -s -X POST "https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/sendMessage" \ -d chat_id="${TELEGRAM_CHAT_ID}" \ -d text="${message}" \ -d parse_mode="Markdown" > /dev/null }

log_event() { local event="$1" local filepath="$2" local timestamp=$(date +"%Y-%m-%d %H:%M:%S") echo "{\"timestamp\":\"${timestamp}\",\"event\":\"${event}\",\"file\":\"${filepath}\"}" >> "${LOG_FILE}" }

for path in "${WATCH_PATHS[@]}"; do if [[! -e "$path" ]]; then send_telegram "⚠️ WARNING: ${path} not found, monitoring cannot be started." fi done

inotifywait -m -r -e close_write,move,delete "${WATCH_PATHS[@]}" | while read -r directory event filename; do filepath="${directory}${filename}" message="🔴 File change detected\n\n📁 File: ${filepath}\n🔧 Event: ${event}\n⏰ Time: $(date +"%Y-%m-%d %H:%M:%S")\n🖥 Server: $(hostname)"

send_telegram "${message}" log_event "${event}" "${filepath}" done

The script does this: monitors all paths in the WATCH_PATHS array, sends a message to Telegram when a change occurs, writes the event to logs in JSON format. We're sending messages in Markdown format, so we can use emoji and bold text.

Make it executable with chmod:

sudo chmod +x /usr/local/bin/file-integrity-monitor.sh

Run it to test:

sudo /usr/local/bin/file-integrity-monitor.sh

In another terminal, do touch /var/www/html/.env. Did you get a notification on your phone? If yes, the system is working.

Creating a systemd service and daemonizing

The script is currently running in the foreground. Let's make it run in the background as a systemd service. Create /etc/systemd/system/file-integrity-monitor.service:

[Unit] Description=File Integrity Monitoring Service After=network.target

[Service] Type=simple User=root ExecStart=/usr/local/bin/file-integrity-monitor.sh Restart=always RestartSec=10 StandardOutput=journal StandardError=journal

[Install] WantedBy=multi-user.target

Activate the service:

sudo systemctl daemon-reload sudo systemctl enable file-integrity-monitor.service sudo systemctl start file-integrity-monitor.service

Status check:

sudo systemctl status file-integrity-monitor.service

If you see Active: active (running), the system is working. To view logs:

sudo journalctl -u file-integrity-monitor.service -f

Thanks to Restart=always and RestartSec=10, even if the script crashes, it automatically restarts after 10 seconds. This system has been running on the doktorbul.com server for 4 months, I haven't intervened once.

One detail: we're running it as User=root because directories like /etc/nginx/ require root privileges. If you're only monitoring user-level files (like /home/deploy/), you can set User=deploy.

Advanced features: hash checking and change diff

The current system notifies when a file changes but doesn't show what changed. Let's add hash checking and diff. Add this function to the script:

HASH_DIR="/var/lib/file-integrity-hashes" mkdir -p "${HASH_DIR}"

compute_hash() { local filepath="$1" sha256sum "${filepath}" | awk '{print $1}' }

check_and_alert() { local filepath="$1" local event="$2"

if [[! -f "${filepath}" ]]; then return fi

local hash_file="${HASH_DIR}/$(echo ${filepath} | sed 's/\//_/g').sha256" local current_hash=$(compute_hash "${filepath}")

if [[ -f "${hash_file}" ]]; then local previous_hash=$(cat "${hash_file}") if [[ "${current_hash}"!= "${previous_hash}" ]]; then local message="🔴 File content changed\n\n📁 File: ${filepath}\n🔧 Event: ${event}\n⏰ Time: $(date +"%Y-%m-%d %H:%M:%S")\n🖥 Server: $(hostname)\n\n🔐 Old hash: ${previous_hash}\n🔐 New hash: ${current_hash}" send_telegram "${message}" log_event "${event}" "${filepath}" echo "${current_hash}" > "${hash_file}" fi else echo "${current_hash}" > "${hash_file}" send_telegram "ℹ️ New file added to monitoring: ${filepath}" fi }

Change the main loop like this:

inotifywait -m -r -e close_write,move,delete "${WATCH_PATHS[@]}" | while read -r directory event filename; do filepath="${directory}${filename}" check_and_alert "${filepath}" "${event}" done

This version stores the SHA256 hash of each file. When the file changes, it compares the hash, and if different, sends an alert. This reduces false positives: if the file was touched but the content didn't change, no alert is sent.

If you want to add diff (caution, may contain sensitive information):

DIFF_DIR="/var/lib/file-integrity-diffs" mkdir -p "${DIFF_DIR}"

if [[ "${current_hash}"!= "${previous_hash}" ]]; then local backup_file="${DIFF_DIR}/$(basename ${filepath}).$(date +%s).bak" cp "${filepath}" "${backup_file}"

# Create diff (optional, use carefully with sensitive files) # diff -u "${previous_backup}" "${backup_file}" > "${DIFF_DIR}/$(basename ${filepath}).diff" fi

I don't use diff in FUTIA projects because.env files contain API keys. But it's useful for non-sensitive files like nginx config.

Real case: SSH key change on kamupersonelhaber.com

Last month we had an interesting incident on kamupersonelhaber.com. We were monitoring the /home/deploy/.ssh/authorized_keys file on the system. One night at 03:24, we got a Telegram notification: the file had changed. I immediately connected to the server and saw that a new SSH public key had been added.

My first thought: security breach. But after some investigation, I realized the hosting provider had added their own key for the automatic backup system. They hadn't informed us. We talked to them, verified the key, added it to the system whitelist.

If we didn't have this monitoring system, we would never have noticed that key. Maybe a problem would have emerged months later. Thanks to proactive monitoring, we detected and resolved the situation within 10 minutes.

Another case: wp-config.php change on diolivo.com.tr. A developer turned on debug mode, forgot to commit it. It got deployed to production. The system alerted within 30 seconds, we immediately rolled it back. We fixed it before users saw debug messages.

These types of systems stay silent 99% of the time. But in that critical 1%, they're lifesavers.

Performance optimization and limitations

inotify is a very lightweight system but has some limitations. By default, there's a limit of 8,192 watches per user. Insufficient for large projects. Check it:

cat /proc/sys/fs/inotify/max_user_watches

To increase:

echo fs.inotify.max_user_watches=524288 | sudo tee -a /etc/sysctl.conf sudo sysctl -p

Another limitation: inotify only works on local file systems. It doesn't work on network file systems like NFS, CIFS. If you're using network storage, you need to use a polling-based solution (find + hash compare).

Performance-wise: inotifywait uses almost zero CPU. I use it on doktorbul.com in a directory with 79,000 files, and it shows 0.0% CPU in top output. The only cost is the Telegram API call, but that doesn't even happen once per second.

Telegram API rate limit: 30 messages per second, 20 different chats per minute. Not a problem in normal usage, but if there are too many file changes, you can set up a queue system:

MESSAGE_QUEUE=() QUEUE_INTERVAL=5

queue_message() { MESSAGE_QUEUE+=("$1") }

send_queued_messages() { if [[ ${#MESSAGE_QUEUE[@]} -gt 0 ]]; then local combined_message=$(printf "%s\n\n" "${MESSAGE_QUEUE[@]}") send_telegram "${combined_message}" MESSAGE_QUEUE=() fi }

while true; do send_queued_messages sleep "${QUEUE_INTERVAL}" done &

This structure waits 5 seconds for messages and sends them in bulk. But not necessary for most projects.

Alternative approaches and the system we use at FUTIA

There are other options besides Bash scripts. Tripwire: industry standard, very powerful but complex installation. AIDE: similar to Tripwire, default on Debian. Samhain: client-server architecture, for large infrastructures. OSSEC: full HIDS (Host Intrusion Detection System), also does log analysis.

Why do I prefer bash? Because simplicity matters in FUTIA projects. Each project is on a different server, with a different structure. I copy a single file script and set it up in 5 minutes. Installing OSSEC takes 2 hours, reading documentation takes 1 day.

The system we use at FUTIA is a bit more advanced. We added these features:

  • Slack integration (some clients don't use Telegram)
  • Daily rotation of JSON log files (logrotate config)
  • Whitelist system: certain files or patterns don't trigger alerts
  • Separate Telegram channel for critical files (high priority)
  • Weekly summary report: how many changes occurred, which files

The code isn't open source, but I've explained the logic, you can build it yourself. If you want a ready-made system, we also do server security automation at FUTIA. You can write email info@futia.net.

Security notes and before taking to production

Before taking this system to production, a few security points:

1. Never commit the Telegram bot token to git. Use an environment variable or separate config file. 2. Log files may contain sensitive information. Make the /var/log/file-integrity.log file chmod 600, only root can read. 3. If the script runs as root, watch out for injection attacks. If file names have spaces or special characters, problems can occur. Quote variables as "${variable}". 4. Try not to send sensitive information in Telegram messages. Send only file name and hash, not file content. 5. If there are too many changes in monitored directories (like log files), exclude the system. Otherwise you'll get spammed.

Also definitely test in a test environment. When you first run it on a production server, you might get hundreds of alerts (backup scripts, cron jobs, etc.). First edit the whitelist, then take it to production.

The approach I use in FUTIA projects: I run "silent mode" for the first week, only writing to logs, not sending to Telegram. I examine the logs, understand which changes are normal and which are abnormal. Then I adjust the whitelist and activate Telegram.

One last note: this system doesn't replace security audits. You should regularly review server logs, follow security updates, and have penetration tests done. File integrity monitoring is just one layer, not sufficient on its own.

At FUTIA, I provide website + automation + security services from the Netherlands to Turkish brands. If you want consulting or ready system installation on server security, you can reach me at info@futia.net. We actively use these types of systems on high-traffic sites like İtalyanmutfagi.com, memuratamalari.com.

Frequently Asked Questions

Can I use another tool instead of inotify-tools?

Yes, there are alternatives. You can use fswatch on macOS, kqueue on BSD. But on Linux servers, inotify is the most stable and performant option. If you want a cross-platform solution, you can consider watchdog (Python) or chokidar (Node.js) libraries. However, these require extra runtime (Python or Node.js must be installed). The Bash + inotify combination works without any dependencies, which is why I prefer it.

Wouldn't it work if I used email notifications instead of Telegram?

Email works but it's not real-time. You need to set up an SMTP server, deal with spam filters. Telegram sends instant push notifications, even if the app isn't open on your phone, you get the notification. Also, the Telegram bot API is free and the rate limit is very high. Email services like SendGrid, Mailgun have monthly limits. Slack or Discord can also be alternatives, webhook integration is similarly easy. I use all of them depending on client preference, but Telegram is my choice for personal projects.

I'm getting too many false positives on the system, how can I reduce them?

First step: only monitor the CLOSE_WRITE event, don't monitor MODIFY or OPEN. Second step: add hash checking, so if the file was touched but content didn't change, it doesn't alert. Third step: define whitelist patterns. For example, ignore files like *.log, *.tmp, *~. Fourth step: if backup scripts, cron jobs run at certain times, mute notifications during those times. In FUTIA projects, I run silent mode for the first week to learn normal traffic, then adjust thresholds.

Can I monitor files inside Docker containers?

Yes, but there are points to be careful about. inotify-tools must be installed inside the container. If you monitor volume-mounted directories, it also counts as inotify on the host side. If there are too many containers, you might hit the max_user_watches limit. Alternatively, you can run the script on the host system and monitor volume paths. For example, like /var/lib/docker/volumes/myapp/_data/. But when the container restarts, the path might change, so you need to find the path dynamically with container name or ID. In Kubernetes it's more complex, you need to use a sidecar container or DaemonSet.

What should I do when I detect a security vulnerability on the system?

First step: immediately back up the changed file, then save it for analysis. Second step: isolate the server from the network, prevent further damage. Third step: find the source of the file change, which user, which process did it. Check active connections with last, w, ps aux, netstat commands. Fourth step: close the security vulnerability, restore the system from a clean backup. Fifth step: change all passwords, API keys. Last step: write an incident report, how it happened, how to prevent it. In FUTIA projects, I prepare runbooks for these situations, so you know what to do in a panic moment.

ABOUT THE AUTHOR
Miraç Eroğlu

Hacettepe mezunu, 6 yıldır sosyal medya, 2 yıldır AI otomasyon.

Learn more →

Want to apply one of the techniques from this post? Fill out a short form and we'll email you a free preview audit within 48 hours.