File Integrity Monitoring on Linux: Which Tool Is Right for RHEL/CentOS?
Whenever preparing for a PCI-DSS audit (Requirement 11.5) or ISO 27001 compliance, File Integrity Monitoring (FIM) is always a mandatory item. In reality, once an attacker successfully exploits an RCE vulnerability, their next target is almost certainly critical system files. They typically alter binaries like /usr/bin/login, inject malware into /etc/crontab, or establish backdoors inside /etc/ssh/sshd_config.
To catch these modifications before it is too late, sysadmins generally consider three approaches:
- Custom Bash script + sha256sum: Periodically hashing files and comparing them with
diff. Fast to set up, but crude and brittle. - Centralized SIEM/FIM cluster (Wazuh, OSSEC): Excellent real-time monitoring. However, the architecture is heavyweight, requiring at least 2–4 GB of RAM for the management server alone.
- AIDE (Advanced Intrusion Detection Environment): Lightweight, reliable, and available directly from the standard RHEL/CentOS AppStream repositories.
Practical Comparison of the Three Approaches
Here is a quick breakdown based on real-world operational experience:
| Criteria | Custom Script (Bash + Hash) | Centralized Agent (Wazuh/OSSEC) | AIDE |
|---|---|---|---|
| Deployment Complexity | Easy at first, a maintenance nightmare at scale | Complex; requires setting up a manager, database, and dashboard | Done with a single dnf command |
| CPU/RAM Usage | Low for few files; freezes the host during deep scans | Runs continuous background daemons, consumes RAM | 0% idle RAM; only uses CPU and I/O during scheduled scans |
| Inspection Scope | Usually checks file content hashes only | Real-time events, log correlation, audit rules | Checks hashes (SHA256/SHA512), permissions, inodes, SELinux, mtime |
| Best Fit | Temporary stopgap | Large environments with 50–100+ servers | Standalone servers, gateways, VPS instances, or Ansible-managed fleets |
Homegrown scripts often overlook key security metrics: they fail to inspect file permissions, suspicious symlinks, or SELinux contexts. Conversely, deploying a full Wazuh stack just to monitor two or three web/database VPS instances is often overkill.
When Should You Choose AIDE for CentOS Stream 9?
On public gateway servers or standalone database instances running CentOS Stream 9, AIDE stands out for its practicality. It does not run persistent background processes that consume valuable RAM, while thoroughly auditing attributes from ownership (UID/GID) and timestamps to file size and cryptographic hashes.
Best of all, you fulfill audit compliance requirements without the overhead and cost of maintaining dedicated management infrastructure.
How to Deploy AIDE on CentOS Stream 9
Step 1: Install AIDE from AppStream
The package is readily available in the default system repositories. Run the following command:
sudo dnf install -y aide aide-selinux
Do not omit the aide-selinux package. Without it, SELinux in Enforcing mode will block AIDE from reading sensitive system files.
Step 2: Fine-Tune the Configuration Rules
Open the primary configuration file:
sudo nano /etc/aide.conf
By default, this file predefines macros specifying which attributes to inspect:
# Standard macro: check SHA256 hash, permissions, inode, user, group, SELinux, ACL
CONTENT_EX = sha256+ftype+p+u+g+n+acl+selinux+xattrs
DATAONLY = p+u+g+sha256
Pro Tip: The most frequent mistake is monitoring the entire /var/log directory with content rules. Because logs are written continuously, every scan will trigger false positives. Use the ! operator to exclude volatile paths:
# 1. Core directories requiring strict monitoring
/etc CONTENT_EX
/usr/bin CONTENT_EX
/usr/sbin CONTENT_EX
/boot CONTENT_EX
# 2. Exclude logs, temporary files, and container data
!/var/log/.*
!/var/tmp/.*
!/tmp/.*
!/var/lib/docker/.*
!/var/lib/containers/.*
Step 3: Initialize the Baseline Database
AIDE works by capturing a snapshot of your filesystem’s current state into a database file. Generate the initial baseline snapshot:
sudo aide --init
On an NVMe SSD with roughly 50,000 standard files, this takes about 20–40 seconds. Once completed, the file /var/lib/aide/aide.db.new.gz will be created (around 3–8 MB in size).
Promote this file to become the active baseline database:
sudo mv /var/lib/aide/aide.db.new.gz /var/lib/aide/aide.db.gz
Hardening step: If an attacker gains root privileges, they could overwrite this database to conceal their tracks. Prevent tampering by setting the immutable attribute on the database file:
sudo chattr +i /var/lib/aide/aide.db.gz
Step 4: Test Change Detection
Simulate an intrusion by modifying the system hosts file:
echo "192.168.1.50 rogue-c2.internal" | sudo tee -a /etc/hosts
Run a verification scan against the baseline:
sudo aide --check
AIDE will immediately catch the discrepancy and output a detailed report:
AIDE 0.16 found differences between database and filesystem!!
Start timestamp: 2026-10-25 10:30:00
Summary:
Total number of entries: 54210
Added entries: 0
Removed entries: 0
Changed entries: 1
---------------------------------------------------
Changed entries:
---------------------------------------------------
changed: /etc/hosts
---------------------------------------------------
Detailed information about changes:
---------------------------------------------------
File: /etc/hosts
Size : 158 , 192
Mtime : 2026-10-25 09:12:00 , 2026-10-25 10:29:45
SHA256 : e3b0c44298fc1c149afbf4c8996fb924 , b94d27b9934d3e08a52e52d7da7dabfa
Step 5: Update the Baseline After System Upgrades
Whenever you run dnf upgrade, dozens of binaries and libraries will have their checksums altered. Running an integrity check right after will trigger a flood of false alarms. Rebuild the baseline instead:
# Temporarily remove the immutable flag if set in Step 3
sudo chattr -i /var/lib/aide/aide.db.gz
# Update and replace the baseline database
sudo aide --update
sudo mv /var/lib/aide/aide.db.new.gz /var/lib/aide/aide.db.gz
# Re-lock the database file
sudo chattr +i /var/lib/aide/aide.db.gz
Step 6: Automate via Cron and Route Alerts to Telegram
Manually running scans every day is impractical. Automate the process by creating /usr/local/bin/aide-monitor.sh:
#!/usr/bin/env bash
BOT_TOKEN="123456789:ABCdefGHIjklMNOpqrSTUvwxYZ"
CHAT_ID="-987654321"
# Run the scan with the lowest I/O priority to avoid impacting server performance
OUTPUT=$(ionice -c 3 /usr/sbin/aide --check 2>&1)
EXIT_CODE=$?
# AIDE exit codes: 0 = clean; non-zero = changes detected or error occurred
if [ $EXIT_CODE -ne 0 ]; then
REPORT_SNIP=$(echo "$OUTPUT" | head -n 25)
TEXT="⚠️ <b>FIM ALERT ON $(hostname)</b>%0A%0A<code>${REPORT_SNIP}"
curl -s -X POST "https://api.telegram.org/bot${BOT_TOKEN}/sendMessage" \
-d "chat_id=${CHAT_ID}" \
-d "parse_mode=HTML" \
-d "text=${TEXT}" > /dev/null
fi
Apply strict permissions to the script:
sudo chmod 700 /usr/local/bin/aide-monitor.sh
sudo chown root:root /usr/local/bin/aide-monitor.sh
Schedule it to execute daily at 03:00 AM in root’s crontab:
echo "0 3 * * * /usr/local/bin/aide-monitor.sh" | sudo tee -a /var/spool/cron/root
3 Essential Best Practices for Running AIDE
- Throttle Disk I/O: Hashing 50,000 files in a single pass can saturate disk I/O, especially on production database hosts running MySQL or PostgreSQL. Always wrap scan commands with
ionice -c 3so AIDE only consumes idle I/O capacity. - Store Database Copies Off-Host: If an intruder compromises root access, they can readily strip
chattr +i. Regularly synchronizeaide.db.gzto an external storage server (such as a read-only NFS mount or an S3 bucket with Object Lock enabled). - Protect the AIDE Binary Itself: Record the SHA256 checksum of
/usr/sbin/aidein external offline documentation so you can verify the integrity of the detection tool at any time.

