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
sudoon 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¶
-
Install the audit tools and start
auditd:auditdis installed and running by default. The rule tools (auditctl,augenrules) are in the separateaudit-rulespackage, which some images, such as the AlmaLinux 10 cloud image, don't include:Check that the kernel's audit system is enabled and a daemon is receiving events:
enabled 1and a non-zeropidmean that events are being recorded. -
Add a rule for the file that survives reboots.
Create the file
/etc/audit/rules.d/50-<KEY>.ruleswith this content:Then rebuild
/etc/audit/audit.rulesfrom the files inrules.dand load it:Create the file
/etc/audit/rules.d/50-<KEY>.ruleswith this content:Then rebuild
/etc/audit/audit.rulesfrom the files inrules.dand load it:Don't restart
auditdwithsystemctl restartto 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 generatedaudit.rulesdoesn't start with the-D(delete all rules) line that makes reloading safe: without it, loading the rules a second time fails withRule 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):Then create the file
/etc/audit/rules.d/50-<KEY>.ruleswith this content:Then rebuild
/etc/audit/audit.rulesfrom the files inrules.dand load it:Gentoo's
auditdservice loads/etc/audit/audit.rulesat start-up (set byRULEFILE_STARTUPin/etc/conf.d/auditd), and that file isn't generated fromrules.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_rulesRules are matched in order, and Gentoo's default
audit.rulesbegins withneverrules that suppress some very frequent system calls. Rules you add after them still see theopenatcalls that programs use to open files.The rule's fields:
-a always,exitadds a rule to the list that's checked when a system call finishes, and always records a match.-F arch=b64limits it to 64-bit system calls, which is what programs on an x86-64 system use. Add a second line witharch=b32to cover 32-bit programs too.-F path=<FILE>matches system calls on this file, and-F perm=rwalimits them to reads, writes and attribute changes (such aschmodandchown). Usexto 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; currentauditctlversions warn that it's slower. -
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-accessauditctlshows the rule with the full list of system calls thatperm=rwastands for. The list depends on the kernel and audit version. -
Access the file to produce events: read it as root, then try to read it as an ordinary user, who isn't allowed to:
The audit rule records the refused attempt as well as the successful read.
-
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-accessEach access is one event, a group of records with the same timestamp and serial number. The first events found are usually the
CONFIG_CHANGErecords written when the rule was loaded. In theSYSCALLrecord:auidis the audit user ID: the account that originally logged in. It stays the same throughsudoandsu, so the first event shows thatlabuserread the file, as root.uidis the user the process ran as.successandexitshow whether the access was allowed;exit=EACCESis a refusal.exeandcommidentify the program, andproctitleits command line.
-itranslates numeric values (user IDs, system call numbers, flags) into names.--input-logsmakesausearchread 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 todayor-ts recentfor the last ten minutes),-ui <UID>or-ul <AUID>, and-sv nofor failed accesses only. -
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 10aureport -k --summarycounts events per key, which is a quick way to see which watched files are busy. -
Reboot, and check that the rule is loaded again at boot:
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.
Related¶
- Reference: Audit rules
- Explanation: SELinux and AppArmor
- How-to guide: How to troubleshoot SELinux or AppArmor denials