AIDE File Integrity Monitoring: Build a Baseline You Can Trust
Last edited on August 9, 2026

AIDE can tell you that a Linux file changed, but it cannot tell you whether the change was malicious, approved, or already present when the baseline was created. The database is evidence only when it begins from a known-clean state, stays protected from the host it measures, and is replaced only after somebody verifies the reported changes.

That operating contract matters more than installing a package. A daily scan that nobody reads is noise. A script that automatically promotes every new database can convert an attacker’s modification into the next “trusted” state. Useful file integrity monitoring therefore has four parts: deliberate scope, a protected baseline, an alert path that distinguishes drift from tool failure, and approval-gated baseline changes.

This guide is for Linux VPS and server operators comfortable with a shell, package files and systemd. It uses AIDE—Advanced Intrusion Detection Environment—in a disposable Debian 13 lab. The lab does not install a system package, edit /etc, enable a service or touch production files.

Decide What the Baseline Is Allowed to Prove

AIDE records selected attributes for paths matched by its rules. Depending on the rule and compiled features, those attributes can include a content hash, permissions, owner, group, size, timestamps, access control lists, extended attributes and Linux capabilities. The current AIDE project overview describes the tool as a file and directory integrity checker, not a complete endpoint detection platform.

That distinction prevents two common mistakes. First, a clean result means only that the current files match the selected attributes in the input database. It does not prove the host was clean when that database was created. Second, a changed result is a triage signal, not a compromise verdict. A package upgrade, configuration deployment or certificate renewal can legitimately alter monitored files.

Start with paths whose unexpected change has a clear owner and response. Authentication configuration, service units, scheduled tasks, privileged binaries and application deployment manifests usually provide higher signal than caches, logs, temporary files or container writable layers. Track volatile paths only with rules designed for their expected behavior; otherwise normal activity will bury the changes that deserve investigation.

Scope also determines cost. Hashing a focused set of security-sensitive files is easier to schedule and review than scanning every changing application object. When planned patching is automated, pair its change record with the controlled reboot and acceptance workflow rather than blindly accepting every filesystem difference after the maintenance window.

Reproduce Content and Permission Drift Safely

The following workflow was reproduced on Debian 13.6, kernel 6.12.96, with Debian’s AIDE 0.19.1 package, on August 8–9, 2026 UTC. Keep all commands in the same root shell so AIDE_LAB remains available. The package is downloaded and extracted below a mktemp directory; it is not installed on the host.

Create the isolated tree, extract the package, and write one sample SSH configuration plus a narrow AIDE rule. The sample is ordinary text—not the server’s live sshd_config.

set -euo pipefail
AIDE_LAB=$(mktemp -d /tmp/voxfor-aide.XXXXXX)
mkdir -p "$AIDE_LAB/db" "$AIDE_LAB/watched"
cd "$AIDE_LAB"

apt download aide
AIDE_PACKAGE=$(find . -maxdepth 1 -name 'aide_*.deb' -print -quit)
test -n "$AIDE_PACKAGE"
dpkg-deb -x "$AIDE_PACKAGE" rootfs
AIDE_BIN="$AIDE_LAB/rootfs/usr/bin/aide"

printf '%s\n' \
  'PasswordAuthentication no' \
  'PermitRootLogin prohibit-password' \
  > "$AIDE_LAB/watched/sshd_config.sample"
chmod 0644 "$AIDE_LAB/watched/sshd_config.sample"

cat > "$AIDE_LAB/aide.conf" <<EOF
database_in=file:$AIDE_LAB/db/aide.db
database_out=file:$AIDE_LAB/db/aide.db.new
gzip_dbout=no
log_level=warning
report_level=changed_attributes
LabRule = p+u+g+s+m+c+sha256
$AIDE_LAB/watched LabRule
EOF

"$AIDE_BIN" --version | sed -n '1p'

Before building a database, validate the configuration and prove that the intended sample path matches LabRule. A syntax check alone does not prove a rule covers the path you think it covers; AIDE’s --path-check closes that gap.

"$AIDE_BIN" --config "$AIDE_LAB/aide.conf" --config-check
"$AIDE_BIN" --config "$AIDE_LAB/aide.conf" \
  --path-check f:"$AIDE_LAB/watched/sshd_config.sample"

Initialize from the known lab state, promote the generated file to the active input database, and record an independent SHA-256 fingerprint. In production, keep that fingerprint and a baseline copy outside the monitored host’s normal administrative boundary.

"$AIDE_BIN" --config "$AIDE_LAB/aide.conf" --init
install -m 0600 "$AIDE_LAB/db/aide.db.new" "$AIDE_LAB/db/aide.db"
sha256sum "$AIDE_LAB/db/aide.db"

set +e
"$AIDE_BIN" --config "$AIDE_LAB/aide.conf" --check
CLEAN_STATUS=$?
set -e
test "$CLEAN_STATUS" -eq 0

Now introduce two materially different changes: append a policy line and narrow the file mode from 0644 to 0640. The first changes content and size; the second changes access permissions. Capturing the exit status is essential because AIDE uses a bitmask: 1 means added entries, 2 removed entries and 4 changed entries. Combinations add those values, so normal drift occupies 1 through 7.

printf '%s\n' 'AllowGroups ssh-operators' \
  >> "$AIDE_LAB/watched/sshd_config.sample"
chmod 0640 "$AIDE_LAB/watched/sshd_config.sample"

set +e
"$AIDE_BIN" --config "$AIDE_LAB/aide.conf" --check
DRIFT_STATUS=$?
set -e

printf 'status=%s added=%s removed=%s changed=%s\n' \
  "$DRIFT_STATUS" \
  "$(( (DRIFT_STATUS & 1) != 0 ))" \
  "$(( (DRIFT_STATUS & 2) != 0 ))" \
  "$(( (DRIFT_STATUS & 4) != 0 ))"
test "$DRIFT_STATUS" -eq 4

The reproduced report identified one changed file and showed both the original and current mode, size, timestamps and SHA-256 value. Exact timestamps and hashes will differ on another run.

AIDE 0.19.1
AIDE found differences between database and filesystem!!

Summary:
  Total number of entries: 2
  Added entries:          0
  Removed entries:        0
  Changed entries:        1

Changed entries:
f > p.. mc  H : /tmp/voxfor-aide.LAB/watched/sshd_config.sample

Size : 61          | 86
Perm : -rw-r--r--  | -rw-r-----
SHA256: changed

status=4 added=0 removed=0 changed=1

Next, stage an updated database without promoting it. --update performs a comparison and writes the candidate output database; it does not make that candidate the trusted input. The active and candidate fingerprints should differ while the approved baseline remains unchanged.

ACTIVE_BEFORE=$(sha256sum "$AIDE_LAB/db/aide.db" | awk '{print $1}')

set +e
"$AIDE_BIN" --config "$AIDE_LAB/aide.conf" --update
UPDATE_STATUS=$?
set -e
test "$UPDATE_STATUS" -eq 4

ACTIVE_AFTER=$(sha256sum "$AIDE_LAB/db/aide.db" | awk '{print $1}')
CANDIDATE_HASH=$(sha256sum "$AIDE_LAB/db/aide.db.new" | awk '{print $1}')
test "$ACTIVE_BEFORE" = "$ACTIVE_AFTER"
test "$ACTIVE_AFTER" != "$CANDIDATE_HASH"
printf 'active_unchanged=yes candidate_distinct=yes\n'

This separation is the change-control boundary. Investigate the file, compare it with a package manifest, deployment commit or approved ticket, and verify the live service before promotion. For SSH material, the host-key cutover procedure and authorized_keys trust-path diagnosis show why content, ownership and live acceptance must be evaluated together.

Treat Drift and Scanner Failure as Different Alerts

An automation wrapper must not collapse every nonzero status into “files changed.” AIDE reserves 1 through 7 for the added/removed/changed bitmask. Current Debian 13 documentation lists generic failures from 14 upward, including configuration, I/O, database, memory and file-lock errors. A scanner that could not read its database did not prove integrity drift; it failed to measure.

The following wrapper and units were checked with bash -n and systemd-analyze verify. Adjust paths for the distribution’s package layout, and ensure the log directory is protected and collected somewhere an attacker on the monitored host cannot silently rewrite.

cat > "$AIDE_LAB/aide-check-wrapper.sh" <<'EOF'
#!/usr/bin/env bash
set -uo pipefail
report=/var/log/aide/aide-check-$(date -u +%Y%m%dT%H%M%SZ).log
mkdir -p "${report%/*}"
set +e
/usr/bin/aide --check >"$report" 2>&1
status=$?
set -e
case $status in
  0) logger -t aide-check "clean report=$report" ;;
  1|2|3|4|5|6|7)
    logger -p authpriv.warning -t aide-check \
      "filesystem drift status=$status report=$report" ;;
  *) logger -p authpriv.err -t aide-check \
      "AIDE execution failure status=$status report=$report" ;;
esac
exit "$status"
EOF
chmod 0750 "$AIDE_LAB/aide-check-wrapper.sh"
bash -n "$AIDE_LAB/aide-check-wrapper.sh"

cat > "$AIDE_LAB/aide-check.service" <<EOF
[Unit]
Description=AIDE file integrity check

[Service]
Type=oneshot
ExecStart=$AIDE_LAB/aide-check-wrapper.sh
Nice=10
IOSchedulingClass=best-effort
IOSchedulingPriority=7
EOF

cat > "$AIDE_LAB/aide-check.timer" <<'EOF'
[Unit]
Description=Daily AIDE file integrity check

[Timer]
OnCalendar=*-*-* 03:17:00
Persistent=true
RandomizedDelaySec=20m

[Install]
WantedBy=timers.target
EOF

systemd-analyze verify \
  "$AIDE_LAB/aide-check.service" "$AIDE_LAB/aide-check.timer"
printf 'wrapper_syntax=pass unit_verify=pass\n'

Do not add --update to the scheduled unit. A timer should measure and report. Baseline promotion belongs to an approved maintenance workflow with a before/after fingerprint and an accountable operator. If the report path itself is only local, central logging or a remote collector gives the result a stronger evidence boundary. The Linux Audit lost-event investigation covers a complementary problem: continuous event evidence can be lost in queues, whereas AIDE compares current filesystem state with an earlier snapshot.

The lab passes only when the configuration and target rule validate; the initial check exits 0; one controlled content-plus-mode change exits 4 and reports exactly one changed file; --update leaves the active database fingerprint unchanged while creating a distinct candidate; drift statuses 1-7 are separated from execution failures; the Bash wrapper and systemd units validate; and the final restored state exits 0 after an explicitly reviewed baseline promotion.

Promote a Baseline Only After Independent Checks

Planned change does not automatically mean approved change. For a package upgrade, compare the AIDE paths with package-manager history and the package’s own file verification where available. For a configuration deployment, compare the file with the reviewed commit and run the service’s native syntax check. For a credential or certificate change, confirm the public or client-visible result; the stale live TLS certificate investigation demonstrates why a renewed file is not proof that the intended endpoint serves it.

If compromise is plausible, preserve the current report, active database fingerprint, relevant logs and volatile evidence before modifying the host. Do not reinitialize AIDE on the suspected machine and call the result clean. A privileged attacker may be able to change the files, AIDE binary, configuration, database and local reports together. An off-host baseline and report copy do not make the host invulnerable; they make silent history rewriting harder.

Promotion should be atomic and reversible. Keep the previous active database, install the reviewed candidate with restrictive permissions, run an immediate check, and restore the previous database if the new input produces an unexpected result. Never overwrite the only trusted copy.

In the disposable lab, restore the known sample content and permissions first. Because AIDE also tracks mtime and ctime, restoring bytes and mode does not rewind every metadata timestamp. Review that remaining difference, generate a candidate from the restored state, promote it deliberately, and demand a clean check.

printf '%s\n' \
  'PasswordAuthentication no' \
  'PermitRootLogin prohibit-password' \
  > "$AIDE_LAB/watched/sshd_config.sample"
chmod 0644 "$AIDE_LAB/watched/sshd_config.sample"

test "$(stat -c %a "$AIDE_LAB/watched/sshd_config.sample")" = 644
test "$(sha256sum "$AIDE_LAB/watched/sshd_config.sample" | awk '{print $1}')" \
  = 53924962345fc91a34c92aa34eeb7040a1c322b18a04cb7a76bc24c2e5f768a0

set +e
"$AIDE_BIN" --config "$AIDE_LAB/aide.conf" --update
REVIEW_STATUS=$?
set -e
test "$REVIEW_STATUS" -eq 4

cp -a "$AIDE_LAB/db/aide.db" "$AIDE_LAB/db/aide.db.previous"
install -m 0600 "$AIDE_LAB/db/aide.db.new" "$AIDE_LAB/db/aide.db"
"$AIDE_BIN" --config "$AIDE_LAB/aide.conf" --check

Production acceptance needs more than AIDE’s clean exit. Verify the changed service through its real client path, confirm monitoring and rollback still work, and attach the accepted report plus hashes to the change record. AIDE proves selected file attributes match the approved snapshot; it does not prove the application is healthy.

Clean Up the Disposable Lab

Cleanup is scoped by a strict path pattern and removes only the directory created by mktemp. The guard refuses an empty, broad or unexpected target. On a production deployment, rollback means restoring the previous database and configuration from protected copies, disabling only the new timer if necessary, and leaving incident evidence intact.

case ${AIDE_LAB:-} in
  /tmp/voxfor-aide.*)
    test -f "$AIDE_LAB/db/aide.db"
    cd /
    rm -rf --one-file-system "$AIDE_LAB"
    test ! -e "$AIDE_LAB"
    ;;
  *) printf 'Refusing cleanup: unexpected AIDE_LAB\n' >&2; exit 1 ;;
esac
unset AIDE_LAB AIDE_BIN AIDE_PACKAGE

Build the Production Operating Contract

Reliable file integrity monitoring is a small system, not one command. Assign an owner for rule changes, scan failures, drift triage and database promotion. Store the baseline and its fingerprint where ordinary root access on the monitored server cannot silently replace every copy. Send reports to a channel that is actually watched, and test both the clean path and a controlled drift path after deployment.

Maintenance needs an explicit sequence:

  1. capture the active baseline fingerprint and open the approved change record;
  2. apply the package or configuration change through its normal deployment path;
  3. run native syntax, service and client acceptance checks;
  4. execute AIDE and reconcile every added, removed and changed path;
  5. stage a new database, compare its fingerprint and preserve the previous one;
  6. promote only after review, then run an immediate clean check;
  7. confirm the next scheduled scan and remote report delivery.

Keep exclusions reviewable. A broad exclusion can silence an important path just as surely as a missing timer. Use --path-check during configuration review, and periodically test one harmless file whose expected AIDE attributes are known. That controlled canary proves rule selection, database access, exit-status handling and alert delivery together.

The service ownership boundary also matters. A self-managed VPS gives the operator root access and therefore responsibility for the AIDE policy, protected copies and response process. The live Voxfor service pages inspected for this article describe general security checks, monitoring, updates and backups, but they do not explicitly promise AIDE or a file-integrity baseline. No service link is included because implying that specific control would overstate the observed offer.

FAQ: AIDE File Integrity Monitoring

Does a clean AIDE check prove a Linux server is uncompromised?

No. It proves only that the files and attributes selected by the configuration match the input database. Trust still depends on when and where the baseline was created, whether the AIDE binary and configuration are trustworthy, and whether an attacker could replace local evidence.

Which AIDE exit codes mean files changed?

Statuses 1 through 7 encode added, removed and changed entries as a bitmask: 1, 2 and 4. Current AIDE also uses higher statuses for execution problems such as invalid configuration, I/O or database errors. Automation should route drift and scanner failure separately.

Should an automated job run AIDE with –update?

No. A routine job should run --check, preserve the report and alert an owner. --update creates a candidate database containing the current state; promoting it without review can accept unauthorized changes into the next baseline.

Where should the AIDE database be stored?

The active local copy must be readable for checks, but a protected copy and fingerprint should live outside the monitored host’s ordinary administrative boundary. Offline, read-only or separately controlled remote storage makes it harder for a host compromise to rewrite both current files and the historical baseline.

How often should AIDE run?

Choose an interval from the detection requirement and scan cost. Daily checks are common, while high-risk or narrowly scoped paths may justify more frequent runs. Randomized timer delay prevents many hosts from scanning at once; an independent alert path must still prove the scan actually ran.

What should happen after a legitimate package update?

Reconcile every AIDE difference with package-manager evidence and the approved change, run service acceptance checks, stage a new database, preserve the old database and promote only the reviewed candidate. Finish with an immediate clean check and a tested rollback record.

Trust the Process, Not the Last Green Line

AIDE is valuable because its comparison is deterministic: selected attributes either match the baseline or they do not. The security value comes from everything around that comparison—known-clean initialization, focused rules, protected history, separate failure alerts, independent acceptance checks and deliberate promotion.

Write down five facts for every deployment: what is monitored, who reviews drift, where protected evidence lives, what authorizes a baseline change, and how the previous database is restored. If any answer is missing, a clean result is weaker than it appears.

Leave a Reply

Your email address will not be published. Required fields are marked *