Skip to content

How to audit access to a file

Use this guide when you need a record of who reads or changes a particular file, for example a file with personal data or a credentials file, including attempts that were refused. The Linux audit system records this in the kernel, for every process and user, and auditd writes it to /var/log/audit/audit.log.

Throughout this guide, replace these placeholders with your own values:

Placeholder Meaning Example
<FILE> The file to watch, as an absolute path /srv/reports/payroll.csv
<KEY> A short name that tags this rule's events, so you can search for them payroll-access

Before you begin

  • You have a shell with sudo on the machine.
  • Pick a file that is read only occasionally. Every matching access writes several log records, so watching a file that programs read constantly (such as /etc/passwd) produces a lot of log data.

Steps

  1. Install the audit tools and start auditd:

    sudo apt-get update
    sudo apt-get install -y auditd
    

    The package enables and starts auditd.service.

    auditd is installed and running by default. The rule tools (auditctl, augenrules) are in the separate audit-rules package, which some images, such as the AlmaLinux 10 cloud image, don't include:

    sudo dnf install -y audit audit-rules
    

    The audit package is installed with the base system, but its services aren't enabled:

    sudo pacman -S --needed --noconfirm audit
    sudo systemctl enable --now auditd.service audit-rules.service
    

    audit-rules.service loads the rules from /etc/audit/rules.d/ at boot.

    sudo emerge --getbinpkg --noreplace sys-process/audit
    sudo rc-update add auditd default
    sudo rc-service auditd start
    

    With the systemd profile (not verified), run sudo systemctl enable --now auditd.service audit-rules.service instead of the last two commands.

    Check that the kernel's audit system is enabled and a daemon is receiving events:

    $ sudo auditctl -s
    enabled 1
    failure 1
    pid 1223
    rate_limit 0
    backlog_limit 8192
    ...
    

    enabled 1 and a non-zero pid mean that events are being recorded.

  2. Add a rule for the file that survives reboots.

    Create the file /etc/audit/rules.d/50-<KEY>.rules with this content:

    -a always,exit -F arch=b64 -F path=<FILE> -F perm=rwa -k <KEY>
    

    Then rebuild /etc/audit/audit.rules from the files in rules.d and load it:

    sudo augenrules --load
    

    Create the file /etc/audit/rules.d/50-<KEY>.rules with this content:

    -a always,exit -F arch=b64 -F path=<FILE> -F perm=rwa -k <KEY>
    

    Then rebuild /etc/audit/audit.rules from the files in rules.d and load it:

    sudo augenrules --load
    

    Don't restart auditd with systemctl restart to load rules: the unit refuses to be stopped manually, so that audit logging can't be interrupted.

    Arch's /etc/audit/rules.d/ is empty, so the generated audit.rules doesn't start with the -D (delete all rules) line that makes reloading safe: without it, loading the rules a second time fails with Rule exists, and removing a rule file doesn't unload the rule. Copy the base configuration from the sample rules first; it starts with -D, and also sets the kernel's buffer size (-b 8192) and failure mode (-f 1, log to the kernel log):

    sudo cp /usr/share/audit-rules/10-base-config.rules /etc/audit/rules.d/
    

    Then create the file /etc/audit/rules.d/50-<KEY>.rules with this content:

    -a always,exit -F arch=b64 -F path=<FILE> -F perm=rwa -k <KEY>
    

    Then rebuild /etc/audit/audit.rules from the files in rules.d and load it:

    sudo augenrules --load
    

    Gentoo's auditd service loads /etc/audit/audit.rules at start-up (set by RULEFILE_STARTUP in /etc/conf.d/auditd), and that file isn't generated from rules.d. Add the rule to the end of it, then reload the rules:

    echo '-a always,exit -F arch=b64 -F path=<FILE> -F perm=rwa -k <KEY>' | sudo tee -a /etc/audit/audit.rules
    sudo rc-service auditd reload_rules
    

    Rules are matched in order, and Gentoo's default audit.rules begins with never rules that suppress some very frequent system calls. Rules you add after them still see the openat calls that programs use to open files.

    The rule's fields:

    • -a always,exit adds a rule to the list that's checked when a system call finishes, and always records a match.
    • -F arch=b64 limits it to 64-bit system calls, which is what programs on an x86-64 system use. Add a second line with arch=b32 to cover 32-bit programs too.
    • -F path=<FILE> matches system calls on this file, and -F perm=rwa limits them to reads, writes and attribute changes (such as chmod and chown). Use x to record executions.
    • -k <KEY> tags every event the rule produces.

    To watch every file in a directory tree instead of one file, use -F dir=<DIRECTORY> in place of -F path=<FILE>.

    Older guides use the shorter form -w <FILE> -p rwa -k <KEY>, which does the same; current auditctl versions warn that it's slower.

  3. Check that the rule is loaded:

    $ sudo auditctl -l
    -a always,exit -F arch=b64 -S open,bind,truncate,ftruncate,rename,…,openat2 -F path=/srv/reports/payroll.csv -F perm=rwa -F key=payroll-access
    

    auditctl shows the rule with the full list of system calls that perm=rwa stands for. The list depends on the kernel and audit version.

  4. Access the file to produce events: read it as root, then try to read it as an ordinary user, who isn't allowed to:

    sudo cat <FILE>
    

    $ cat <FILE>
    cat: /srv/reports/payroll.csv: Permission denied
    

    The audit rule records the refused attempt as well as the successful read.

  5. Find the events by their key:

    $ sudo ausearch --input-logs -k <KEY> -i
    ----
    type=PROCTITLE msg=audit(11/07/2026 13:52:36.739:9) : proctitle=cat /srv/reports/payroll.csv
    type=PATH msg=audit(11/07/2026 13:52:36.739:9) : item=0 name=/srv/reports/payroll.csv inode=33708209 dev=fd:02 mode=file,640 ouid=root ogid=root ...
    type=CWD msg=audit(11/07/2026 13:52:36.739:9) : cwd=/home/labuser
    type=SYSCALL msg=audit(11/07/2026 13:52:36.739:9) : arch=x86_64 syscall=openat success=yes exit=3 ... auid=labuser uid=root ... comm=cat exe=/usr/bin/cat key=payroll-access
    ----
    type=PROCTITLE msg=audit(11/07/2026 13:52:36.749:10) : proctitle=cat /srv/reports/payroll.csv
    type=PATH msg=audit(11/07/2026 13:52:36.749:10) : item=0 name=/srv/reports/payroll.csv ...
    type=CWD msg=audit(11/07/2026 13:52:36.749:10) : cwd=/home/labuser
    type=SYSCALL msg=audit(11/07/2026 13:52:36.749:10) : arch=x86_64 syscall=openat success=no exit=EACCES(Permission denied) ... auid=labuser uid=labuser ... comm=cat exe=/usr/bin/cat key=payroll-access
    

    Each access is one event, a group of records with the same timestamp and serial number. The first events found are usually the CONFIG_CHANGE records written when the rule was loaded. In the SYSCALL record:

    • auid is the audit user ID: the account that originally logged in. It stays the same through sudo and su, so the first event shows that labuser read the file, as root.
    • uid is the user the process ran as.
    • success and exit show whether the access was allowed; exit=EACCES is a refusal.
    • exe and comm identify the program, and proctitle its command line.

    -i translates numeric values (user IDs, system call numbers, flags) into names. --input-logs makes ausearch read the log files even when its standard input isn't a terminal, as in a script or a cron job; in an interactive shell it makes no difference. Narrow the search with -ts (start time, for example -ts today or -ts recent for the last ten minutes), -ui <UID> or -ul <AUID>, and -sv no for failed accesses only.

  6. Get a summary of the accesses, for example every failed one:

    $ sudo aureport --input-logs -f -i --failed
    
    File Report
    ===============================================
    1. 11/07/2026 13:52:36 /srv/reports/payroll.csv openat no /usr/bin/cat labuser 10
    

    aureport -k --summary counts events per key, which is a quick way to see which watched files are busy.

  7. Reboot, and check that the rule is loaded again at boot:

    sudo auditctl -l
    

Verify

The rule is loaded (sudo auditctl -l lists it), it's loaded again after a reboot, and sudo ausearch --input-logs -k <KEY> -i shows an event for every access to <FILE>, with the login user in auid.

Remove the rule

When you no longer need the record, delete the rule file (or, on Gentoo, its line in /etc/audit/audit.rules) and load the rules again with sudo augenrules --load (Gentoo: sudo rc-service auditd reload_rules). On Arch, leave 10-base-config.rules in place: its -D line is what unloads the deleted rule. The events already logged stay in /var/log/audit/ until auditd rotates them out, as configured by num_logs and max_log_file in /etc/audit/auditd.conf.

Troubleshooting

auditctl -s shows pid 0, or ausearch finds nothing. auditd isn't running, so events go to the kernel log at best. Start it as in step 1, and on Arch check that auditd.service is enabled.

The rule is gone after a reboot. On systemd distributions, check that audit-rules.service is enabled and the rule file is in /etc/audit/rules.d/ with a name ending in .rules; run sudo augenrules --check to see whether audit.rules is out of date. On Gentoo, check that the line is in /etc/audit/audit.rules and that auditd is in the default runlevel (rc-update show default).

Error sending add rule data request (Rule exists). The same rule is already loaded, because rules were loaded twice without the -D (delete all) line that the generated audit.rules normally begins with. This usually happens on Arch, where /etc/audit/rules.d/ is empty by default: copy 10-base-config.rules into it as in step 2, and run sudo augenrules --load again. Check the result with sudo auditctl -l.

The rule isn't loaded, and augenrules says Audit system is in immutable mode - exiting with no changes. A rules file ends with -e 2, which locks the configuration until the next reboot (sudo auditctl -s shows enabled 2). augenrules --load prints this message but exits successfully, so a script that loads rules doesn't notice; on Gentoo, rc-service auditd reload_rules (and auditctl itself) fails with The audit system is in immutable mode, no rule changes allowed. Add your rule, then reboot to load it.