Audit Rules and auditd
auditctl,auditd, and theau*query tools are the userspace half of the Linux audit subsystem — the policy and plumbing that decide what the kernel records and where it lands. Theauditddaemon connects to the kernel overNETLINK_AUDIT, registers itself as the recipient of audit records, and writes them to disk (typically/var/log/audit/audit.log);auditctlloads the rules that tell the kernel audit subsystem which syscalls, files, and event types to capture; andausearch/aureportquery the resulting log. Rules come in three shapes: syscall rules (-a <list>,<action> -S <syscall> -F <field>=<value>), file watches (-w <path> -p <perms> -k <key>), and control settings (-eenable/lock,-bbacklog,-ffailure mode). Persistent rules live in/etc/audit/rules.d/and are compiled byaugenrulesinto/etc/audit/audit.rules, whichauditdloads at start. All facts here are pinned to the audit-userspace 4.1.x package line — 4.1.4 was released 2026-03-23 per the linux-audit GitHub releases — and the kernel-side rule semantics to Linux v6.12 LTS.
This note is the userspace companion to The Linux Audit Subsystem, which covers the kernel mechanism (audit_context, kauditd, the filter engine, netlink). Read that note for how a record is produced; read this one for how you make the kernel produce it and how you read it back.
Mental Model — Three Programs Around One Kernel Queue
The userspace audit stack is three cooperating programs hanging off the kernel’s audit queue. auditctl is a thin front-end to the netlink control channel: every rule you type becomes an AUDIT_ADD_RULE netlink message, every -e/-b/-f an AUDIT_SET message. auditd is the long-lived daemon that claims the role of the audit recipient (only one process can), drains records, and writes them to disk through a configurable flush policy, optionally fanning copies out to plugins (the dispatcher, historically audisp). The au* tools never talk to the kernel — they parse the on-disk log.
flowchart TD ADMIN["administrator"] -->|"auditctl -a / -w / -e"| ACTL["auditctl<br/>(netlink control msgs)"] ACTL -->|"AUDIT_ADD_RULE / AUDIT_SET"| KERN["kernel audit subsystem<br/>(rule lists + audit_queue)"] RULESD["/etc/audit/rules.d/*.rules"] -->|"augenrules"| ARULES["/etc/audit/audit.rules"] ARULES -->|"loaded at start via auditctl -R"| KERN KERN -->|"NETLINK_AUDIT unicast"| AUDITD["auditd daemon"] AUDITD -->|"flush policy"| LOG["/var/log/audit/audit.log"] AUDITD -->|"plugin_dir /etc/audit/plugins.d"| DISP["dispatcher / audisp plugins<br/>(syslog, remote, SIEM)"] LOG -->|"parse"| SEARCH["ausearch (events by criteria)"] LOG -->|"parse"| REPORT["aureport (summaries)"]
The userspace audit pipeline. What it shows: rules flow down into the kernel (interactively via auditctl or persistently via augenrules → audit.rules), records flow up from the kernel to auditd and onto disk and plugins, and the query tools read only the disk log. The insight to take: auditctl and the query tools are stateless front-ends; the durable state is the kernel’s loaded rule set (volatile, lost at reboot unless -e 2 locked) and the daemon’s on-disk log. This is why “edit rules.d, run augenrules --load” is the persistent path, while raw auditctl -a ... is ephemeral.
Syscall Rules — -a list,action
The richest rule form intercepts system calls. Its skeleton is auditctl -a <list>,<action> -S <syscall> [-F <field><op><value> ...] [-k <key>]. The -a flag appends the rule to a list; -A prepends it (matching the kernel’s AUDIT_FILTER_PREPEND bit). The <list> selects which kernel filter list the rule joins, and per auditctl(8) the valid lists are:
task— “Add a rule to the per task list.” Evaluated at task (process) creation; can only use fields known at fork time (uid,gid,auid).exit— “Add a rule to the syscall exit list.” The workhorse: evaluated at syscall exit, when the return value and all resolved pathnames are known.user— “Add a rule to the user message filter list.” Filters records originating from userspace (e.g. PAM,sudo).exclude— “Add a rule to the event type exclusion filter list.” Suppresses whole record types before they are built (e.g. drop noisyCWDrecords).filesystem— “Add a rule that will be applied to a whole filesystem.”io_uring— “Add a rule to the io_uring syscall filter.” (Maps to the kernel’sAUDIT_FILTER_URING_EXITlist, present sinceio_uringauditing was added.)
Uncertain
Verify: that the
entrylist is deprecated/removed in currentauditctland that the practical lists aretask,exit,user,exclude,filesystem, andio_uringexactly as above. Reason: the v6.12 uapi header still definesAUDIT_FILTER_ENTRY(0x02) but the kernel discourages entry-list syscall rules for performance, and theauditctl(8)man page fetched here did not listentryamong the accepted-avalues. To resolve: confirm against theauditctlsourceaudit_rule_syscallbyname_data/ list-name table at the installed 4.1.x version and the kernel’s rejection of entry-list syscall rules.
The <action> is one of two values per auditctl(8):
never— “No audit records will be generated.” (Kernel actionAUDIT_NEVER.) Used to carve exceptions before a broadalwaysrule.always— “Allocate an audit context, always fill it in at syscall entry time.” (Kernel actionAUDIT_ALWAYS.) The rule that actually causes recording.
Because rules are evaluated in order and the first match wins, the idiom is “specific never rules first, broad always rules last.” A rule that matches never for a noisy daemon’s UID suppresses its events even though a later always rule would otherwise catch them.
The -S flag names the syscall (“Any syscall name or number may be used. The word ‘all’ may also be used.”). Multiple -S on one rule are OR’d. The -F flag builds a field comparison — auditctl(8) documents the operators as n=v | n!=v | n<v | n>v | n<=v | n>=v | n&v | n&=v (“Build a rule field: name, operation, value”), where & is a bit-mask test and &= a bit-set test. The supported field names include (quoting the man page descriptions): auid (“The original ID the user logged in with”), uid/euid, gid/egid, pid, ppid, exit (“Exit value from a syscall”), success (“If the exit value is >= 0 this is true/yes”), perm (“Permission filter for file operations”), arch (“The CPU architecture of the syscall”), msgtype (“Used to match the event’s record type”), and exe (“Absolute path to application that while executing”). The -C flag builds an inter-field comparison (-C f=f/-C f!=f), e.g. comparing auid against uid to catch privilege changes.
A worked example — log every successful unlink/rename of a file under /etc performed by a non-system user:
auditctl -a exit,always \
-S unlink -S unlinkat -S rename -S renameat \
-F dir=/etc -F auid>=1000 -F auid!=4294967295 -F success=1 \
-k etc_deleteReading it line by line. -a exit,always appends to the syscall-exit list with the recording action. The four -S flags cover both the legacy and *at variants of delete/rename. -F dir=/etc confines matches to that directory subtree (the kernel AUDIT_DIR field). -F auid>=1000 restricts to real users (login UIDs ≥ 1000 are non-system on most distros). -F auid!=4294967295 excludes processes with no set login UID (4294967295 is (uid_t)-1, the “unset” sentinel) so daemons started before login don’t match. -F success=1 records only completed operations. -k etc_delete tags every resulting record with key="etc_delete" so ausearch -k etc_delete finds them later. The arch field is important for a subtle reason: on a 64-bit kernel that also runs 32-bit binaries, syscall numbers differ between ABIs, so a syscall rule should specify -F arch=b64 (or b32) to match the intended ABI — omitting it can silently miss the 32-bit calling convention.
File Watches — -w path -p perms -k key
A watch is syntactic sugar for “audit any access to this file or directory subtree.” Per auditctl(8), -w path “Place a watch on path” and -p [r|w|x|a] describes “the permission access type that a file system watch will trigger on” — r read, w write, x execute, a attribute change. -k key tags matches. So:
auditctl -w /etc/shadow -p wa -k shadow_changes
auditctl -w /usr/bin/passwd -p x -k privileged_passwdThe first watches /etc/shadow for write and attribute changes (the two ways its contents or permissions get altered); the second logs every xecution of /usr/bin/passwd. Under the hood a watch becomes a syscall rule whose object is the watched inode — the kernel places an fsnotify mark and, when a syscall touches that inode, the audit inode-resolution hooks (__audit_inode/__audit_inode_child) flag the event. The man page notes -w/-p are deprecated in favor of the equivalent path-field syscall rule (-a exit,always -F path=/etc/shadow -F perm=wa -k shadow_changes); the watch syntax persists because it is far more readable and is what every hardening baseline uses. The practical limitation: a watch on a directory is not recursive — it watches that directory’s immediate entries, not the whole tree; use -F dir= for subtree semantics.
Control Settings
A second class of auditctl invocation tunes the subsystem rather than adding rules:
-e [0|1|2]— “Set enabled flag. When 0 is passed, disable auditing; 1 enables it; 2 locks configuration.” The2(lock) value sets the kernel’sAUDIT_LOCKEDstate: no rule or config change is accepted until reboot, and attempts are themselves logged. Hardening rule files end with-e 2.-b backlog— “Set max number (limit) of outstanding audit buffers allowed.” Sets the kernelaudit_backlog_limit(default 64); production baselines raise it (commonly-b 8192) to avoid dropping records under load.-f [0|1|2]— “Set failure mode 0=silent 1=printk 2=panic.” Controls what the kernel does when it cannot record (theAUDIT_FAIL_SILENT/AUDIT_FAIL_PRINTK/AUDIT_FAIL_PANICconstants).panicis for environments where an unrecordable security event must halt the machine (common-criteria deployments).-r rate— “Set limit in messages/sec (0=none).” Rate-limits records to protect the daemon.-s— “Report the kernel’s audit subsystem status” (enabled flag, backlog limit, lost counter, failure mode). Thelostfield is the first thing to check when records go missing.-l— “List all rules 1 per line.”-D— “Delete all rules and watches.”--reset-lost— “Reset the lost record counter.”--loginuid-immutable— “Make loginuids unchangeable once they are set.” Critical for attribution integrity: it prevents a compromised process from rewriting its ownauid.
A representative control preamble (the first lines of a typical hardened audit.rules):
-D # start clean: delete any existing rules
-b 8192 # raise the backlog limit
-f 1 # printk failures (use 2=panic only on locked-down hosts)
--backlog_wait_time 60000The auditd Daemon and the Dispatcher
auditd is “the userspace component to the Linux Auditing System. It’s responsible for writing audit records to the disk” (auditd(8)). At start it connects to NETLINK_AUDIT, sets itself as the audit recipient (the kernel’s kauditd thread thereafter unicasts records to its port-id), and loads rules. It is normally run under systemd with -n (“no fork … useful for running off of inittab or systemd”); -f keeps it foreground “for debugging.” It responds to signals as a control interface: SIGHUP “causes auditd to reconfigure … re-reads the configuration file,” SIGUSR1 “causes auditd to immediately rotate the logs,” SIGUSR2 “causes auditd to attempt to resume logging and passing events to plugins,” and SIGTERM “caused auditd to … write a shutdown audit event, and exit.”
Its behavior is governed by /etc/audit/auditd.conf (auditd.conf(5)). The directives that matter most:
log_file— “the full path name to the log file” (default/var/log/audit/audit.log).log_format— “raw and enriched.” Raw writes records exactly as the kernel produced them (numeric UIDs, syscall numbers); enriched resolves and appends human-readable fields (account names, syscall names) at write time so the log is interpretable even if/etc/passwdlater changes.flush— “none, incremental, incremental_async, data, and sync.”syncflushes both data and metadata on every record (safest, slowest);dataflushes data;incrementalflushes everyfreqrecords;nonelets the OS decide. Thefreqdirective is “how many records to write before issuing an explicit flush.”max_log_file/num_logs/max_log_file_action— log size cap in MiB, how many rotated logs to keep, and the action when the cap is hit (“ignore, syslog, exec, suspend, rotate and keep_logs”).rotateplusnum_logsis the normal rolling-log setup.space_left/space_left_action/admin_space_left/disk_full_action/disk_error_action— the low-disk escalation ladder. Actions range fromsyslog(warn) throughemail,exec,suspend,single(drop to single-user mode), up tohalt(stop the machine). On a compliance host these are set aggressively: if audit can’t be written, the system is required to stop accepting work.
The dispatcher / plugins: auditd can fan records out to consumers beyond the disk log. The plugin_dir directive (“the location that auditd will use to search for its plugin configuration files. The default directory is /etc/audit/plugins.d”) points at per-plugin config. Historically this was a separate audispd (audit dispatch daemon, the audisp prefix on audisp-syslog, audisp-remote); in the modern audit-userspace the dispatcher is integrated into auditd and plugins are configured under plugins.d. This is how audit feeds a SIEM: audisp-remote ships records to a central collector, audisp-syslog mirrors them into the system journal.
Uncertain
Verify: that
audispdis fully merged intoauditd(no standalone dispatcher binary) in audit-userspace 4.1.x, and the exact set of bundled plugins. Reason: the integration happened across the 2.8→3.0 transition andauditd.conf(5)here referencesplugin_dirbut the precise binary/topology was not confirmed against the 4.1.x source in this pass. To resolve: check the audit-userspace 4.1.x tree (audisp/directory andauditdplugin loading) and the installedaudisp-*binaries.
Persistent Rules — rules.d and augenrules
Rules loaded by auditctl are volatile — they vanish at reboot (unless -e 2 locked them, in which case they persist only until reboot anyway). Durable configuration goes in /etc/audit/rules.d/*.rules, fragment files an administrator drops in. augenrules (augenrules(8)) “merges all component audit rules files, found in the audit rules directory, /etc/audit/rules.d, placing the merged file in /etc/audit/audit.rules.” Ordering is deterministic: “The files are concatenated in order, based on their natural sort (see -v option of ls(1)) and stripped of empty and comment (#) lines,” with the control directives -D, -b, -f, and -e repositioned to their correct places (so -e 2 always lands last and -D first regardless of which fragment they appear in). The convention is to prefix fragment files with numbers — 10-base-config.rules, 30-pci-dss.rules, 99-finalize.rules — so the natural sort produces the intended order. augenrules --load regenerates audit.rules and loads it into the kernel; the auditd systemd unit runs augenrules --load at start.
Querying — ausearch and aureport
ausearch “is a tool that can query the audit daemon logs for events based on different search criteria” (ausearch(8)); critically, it reassembles the multiple records of one event (which share a timestamp:serial) and prints them together. Its useful options: -m/--message message-type (filter by record type, comma-separated), -ts/--start and -te/--end (time range, accepting keywords like today, boot, this-week), -k/--key (the rule key set with -k), -ui/--uid and -ue/--uid-effective and -ua/--uid-all (match by UID/EUID/AUID), -c/--comm and -x/--executable (by program), -sc/--syscall (by syscall name or number), -p/--pid, and -i/--interpret (“converts numeric entities (like UIDs) into readable text like account names”). Example:
ausearch -k etc_delete -ts today -ifinds every event tagged with the etc_delete key since midnight and interprets numeric fields into names. The -i flag is what turns uid=1000 into uid=alice and syscall=263 into syscall=unlinkat.
aureport “produces summary reports of the audit system logs” (aureport(8)) — it aggregates rather than lists individual events. Its report selectors: -au/--auth (“Report about authentication attempts”), -l/--login (“Report about logins”), -u/--user, -f/--file (“Report about files and af_unix sockets”), -s/--syscall, -x/--executable, and -k/--key (“Report about audit rule keys”). --summary “gives a total of the elements of the main report,” and -ts/-te bound the time range. A common pairing is aureport --summary to spot which keys or executables dominate the log, then ausearch -k <key> to drill into specific events. Both tools read /var/log/audit/audit.log by default but accept piped input, so ausearch --raw | aureport -f chains a search into a report.
Failure Modes
Rules don’t persist after reboot. You added them with auditctl -a but never wrote them to rules.d. Put fragments in /etc/audit/rules.d/ and run augenrules --load.
auditctl says “Operation not permitted” or “Audit is locked.” Either you lack CAP_AUDIT_CONTROL, or the configuration is locked with -e 2 and only a reboot will unlock it. Check auditctl -s.
Records are silently missing. Check auditctl -s for a non-zero lost counter (backlog overflow — raise -b), confirm auditd is running (systemctl status auditd), and confirm a syscall rule specifies the right arch (a 32-bit process won’t match a b64-implicit rule). Also note: a never rule earlier in the list can shadow a later always rule — order matters.
ausearch finds nothing for a key. The key is set per-rule with -k; if the rule never matched (wrong list, wrong arch, wrong field), no record carries the key. Confirm the rule loaded with auditctl -l and that the action is always not never.
Disk fills and the system halts. That is disk_full_action=halt (or single) doing its job — on a compliance host, an unwritable audit log is treated as a security failure. Tune max_log_file/num_logs/space_left_action for the environment.
Alternatives and When to Choose Them
auditctl rules are the right interface when you need the certified, kernel-mediated audit trail for compliance and forensics, with deterministic on-disk persistence and a query toolchain auditors recognize. Compared to programmable observability:
- eBPF tooling (Falco, Tetragon,
bpftrace): richer in-kernel filtering and aggregation, namespace-aware, lower overhead for selective monitoring — but a separate, newer stack with its own learning curve and less compliance pedigree. See BPF-LSM and the Linux eBPF MOC. Use eBPF for high-volume runtime detection; use audit rules for the legally-defensible log. systemd-journaldaudit ingestion: journald can subscribe to the audit multicast group (AUDIT_NLGRP_READLOG) and store records in the journal, giving unifiedjournalctlquerying — but it does not replaceauditd’s durable, rotation-managed, plugin-fed log; the two often run together.- Distro hardening baselines (CIS, DISA STIG): rather than hand-writing rules, most operators deploy a vetted
rules.dset. These are starting points, not alternatives — they are stillauditctlrules under the hood.
Production Notes
The packaged baseline rule files (/usr/share/audit/sample-rules/ in audit-userspace, and the CIS/STIG sets) follow a recognizable shape: a 10-base-config.rules that does -D, raises -b, sets -f; a body of -a exit,always rules watching execve, credential files, and privileged commands, each -k-tagged by control objective; and a 99-finalize.rules ending in -e 2. Because the lock makes rule edits require a reboot, operators stage changes in rules.d and reboot during maintenance windows. The auid/login-UID immutability (--loginuid-immutable, set via PAM pam_loginuid plus the audit feature flag) is the single most important integrity control — without it, attribution can be forged. For centralized collection, audisp-remote ships to a remote auditd over an authenticated channel; the receiving side typically runs log_format = enriched so the central store is self-describing. A perennial operational gotcha: high-volume -S all or execve rules on a busy host can generate gigabytes of log per day and, if flush = sync, measurably slow the system — tune flush/freq and scope rules tightly.
Uncertain
Verify: the exact path and contents of the bundled sample rule files in audit-userspace 4.1.x and which distros ship which baseline by default. Reason: sample-rule paths and distro packaging differ across releases and were not confirmed against an installed 4.1.x package in this pass. To resolve: inspect the installed package file list (
rpm -ql audit/dpkg -L auditd) and/usr/share/audit/sample-rules/on the target system.
See Also
- The Linux Audit Subsystem — the kernel half:
audit_context, the filter engine,kauditd,NETLINK_AUDIT, the record types these rules cause - SELinux — its
AVCdenials appear in the same log;ausearch -m AVCfinds them - The Linux Security Module Framework — the LSM hooks whose decisions audit rules let you record
- Process Credentials and struct cred — the
auid/uid/gidfields rules match on and records carry - Seccomp and seccomp-BPF — emits
SECCOMPrecords (ausearch -m SECCOMP) - Linux Security MOC — parent map; this note sits in §H, Observability and Hardening Knobs
- Linux eBPF MOC — the programmable-observability alternative (Falco, Tetragon)