← Back to intel

Your AIDE Init Locked. Your Nessus Server Is Why.

The CIS RHEL9 hardening playbook ran clean on two dev servers. Same playbook, same roles, same variables. Then it hit the Tenable server and locked mid-run on the AIDE init task. No error message that pointed anywhere useful. Just a process that wouldn't finish.

This is what happened, why the Tenable server specifically caused it, and the fix that handles it cleanly.

// what aide init actually does

When Ansible runs aide --init, AIDE walks the entire filesystem, checksums every file it's configured to monitor, and writes the result to /var/lib/aide/aide.db.new.gz. On a clean dev server with a minimal footprint that takes 2–5 minutes. On a server running Nessus it can take 30 minutes or more — and that's when the lock conflict probability spikes.

AIDE and Nessus are competing for the same file handles. Nessus continuously reads plugin files, writes scan databases, and updates log files. AIDE is trying to open and checksum those same files. The lock isn't always fatal — sometimes the playbook just hangs waiting for a file handle that never frees. Sometimes the aide --init process errors with a lock file conflict. Either way the task stalls.

// why the dev servers didn't have this problem

The dev servers had no database, no active scanning agent, and no files being continuously written. AIDE init ran against a static filesystem. The Tenable server had:

That's significantly more filesystem activity than a vanilla RHEL server. The original Ansible task only asked one question before running aide --init:

when: not aide_db.stat.exists

Does aide.db.gz exist? No? Run init. That logic works fine when nothing else is touching the filesystem. It fails when Nessus is running because it doesn't account for a partially completed init, a currently running AIDE process, or files locked by another process.

// the original task and why it failed

- name: Initialize AIDE database
  ansible.builtin.command: aide --init
  when: not aide_db.stat.exists
  async: 3600
  poll: 30

One condition. No awareness of whether AIDE was already running. No check for aide.db.new.gz which is the intermediate file AIDE writes before the final database exists. If a previous run started aide --init and got interrupted, aide.db.new.gz exists but aide.db.gz doesn't — and the original task would fire again, creating a second AIDE process trying to init against the same filesystem at the same time.

That's the exact scenario that produces the lock conflict on the Tenable server.

// the fix — 5-task wait loop

The fix replaces the single task with five tasks that cover every scenario:

- name: "1.3.1 | Check if AIDE database exists"
  ansible.builtin.stat:
    path: /var/lib/aide/aide.db.gz
  register: aide_db

- name: "1.3.1 | Check if AIDE init already in progress"
  ansible.builtin.stat:
    path: /var/lib/aide/aide.db.new.gz
  register: aide_db_new

- name: "1.3.1 | Wait for any running AIDE process to complete"
  ansible.builtin.shell: |
    timeout=120
    while pgrep -x aide > /dev/null && [ $timeout -gt 0 ]; do
      sleep 5
      timeout=$((timeout - 5))
    done
    pgrep -x aide > /dev/null && echo "running" || echo "clear"
  register: aide_wait
  changed_when: false
  when:
    - not aide_db.stat.exists
    - not aide_db_new.stat.exists

- name: "1.3.1 | Initialize AIDE database"
  ansible.builtin.command: aide --init
  async: 3600
  poll: 30
  when:
    - not aide_db.stat.exists
    - not aide_db_new.stat.exists
    - aide_wait.stdout is defined
    - aide_wait.stdout == 'clear'

- name: "1.3.1 | Move AIDE database into place"
  ansible.builtin.command: >
    mv /var/lib/aide/aide.db.new.gz /var/lib/aide/aide.db.gz
  when: >
    not aide_db.stat.exists and
    (aide_db_new.stat.exists or
    (aide_wait.stdout is defined and aide_wait.stdout == 'clear'))

The original task asked one question. This asks three before running init:

Does aide.db.gz exist? If yes — done, skip everything. Does aide.db.new.gz exist? If yes — a previous init completed or is in progress, skip init and go straight to the move task. Is AIDE currently running? If yes — wait up to 120 seconds for it to finish, then check again.

Only when all three conditions are clear does init fire.

// the nessus exclusion problem

Fixing the init conflict surfaces a second issue specific to Tenable servers. Once AIDE initializes successfully, the daily aide --check cron will generate significant noise because Nessus plugin files update constantly. Every plugin update, every scan result write, every log rotation shows as a file integrity change.

The fix is excluding Nessus directories from AIDE monitoring entirely:

- name: "1.3.1 | Configure AIDE exclusions for Nessus"
  ansible.builtin.blockinfile:
    path: /etc/aide.conf
    block: |
      !/opt/nessus/.*
      !/var/opt/nessus/.*
    marker: "# {mark} AIDE NESSUS EXCLUSIONS"
  when: ansible_hostname is search('nessus|tenable|scanner')

Run this exclusion task before aide --init. If you add exclusions after initialization, the existing database reflects the full filesystem including Nessus files. Your next aide --check will flag every Nessus file as changed.

The exclusion targets the Nessus installation and data directories. AIDE still monitors everything that matters for compliance — OS binaries, system configs, PAM files, sudoers, SSH configuration. It just stops chasing Nessus plugin updates that will never stop changing.

// what to watch for on other security tool servers

The same conflict can occur on any server running a tool that continuously reads or writes large numbers of files. DTEX endpoint agent, Microsoft Defender, CrowdStrike Falcon, and other EDR agents all have similar filesystem activity patterns. Before running AIDE init on any server running a security agent:

Check what's running:

ps aux | grep -E "nessus|falcon|dtex|defender|sentinel"

Check what the agent's data directory looks like:

du -sh /opt/nessus /var/opt/nessus 2>/dev/null
find /opt/nessus -newer /etc/hostname -type f 2>/dev/null | wc -l

If the agent directory has thousands of recently modified files, add exclusions before init.

// check, fix, verify

Run the playbook with the updated AIDE tasks:

ansible-playbook cis-rhel9-l1.yml --tags aide -l tenable_servers

Confirm the database initialized correctly:

ls -lh /var/lib/aide/aide.db.gz
aide --check | head -20

Verify Nessus exclusions are in the AIDE config:

grep -A3 "NESSUS" /etc/aide.conf

Confirm no duplicate AIDE processes:

pgrep -x aide

The Tenable server isn't a problem. It just required the hardening playbook to be aware of what else is running on it. Same CIS controls, same compliance target — the implementation just needs to account for the environment it's running against.

These posts come from real production environments — not labs, not documentation. If that's the kind of writing you want more of, follow Root & Secure on Medium.