auditd: Linux's Syscall Logger
auditd is the Linux audit daemon — the part of the kernel and userspace that logs security-relevant events at the syscall level. File access, privilege escalation, network changes, authentication events, module loading, all with process ancestry and user identity attached. It’s a mandatory tool for PCI DSS, HIPAA, FedRAMP, CMMC, and most other compliance frameworks, and a genuinely useful security tool even outside compliance contexts.
It’s also confusing, verbose, and the default config ships empty. Nothing gets logged until you write rules, and once you do, you can easily generate gigabytes of noise that buries the signal you actually wanted. This post covers auditd as an operational tool: the kernel-userspace split, writing rules that produce useful logs, searching with ausearch/aureport, shipping to a SIEM, and the compliance-recipe templates for common regulatory requirements.
The mental model
The Linux audit subsystem has three parts:
- The kernel audit subsystem, which intercepts syscalls and generates audit records.
auditd, the userspace daemon that reads records from the kernel via netlink, writes them to/var/log/audit/audit.log, and forwards them to dispatchers.auditctl, the rule-management tool that tells the kernel what to audit.
Rules live in /etc/audit/rules.d/*.rules, merged at boot into /etc/audit/audit.rules, loaded into the kernel. You can add/remove rules at runtime with auditctl, but persistent rules go in the .rules files.
Each record is an audit.log line with a numeric type (SYSCALL, EXECVE, PATH, LOGIN, etc.), a timestamp, and key=value fields. One “event” may span multiple lines sharing an audit ID — e.g., a single execve() generates a SYSCALL record, an EXECVE record with the argv, and one PATH record per path involved. ausearch reassembles them for you.
Installing and enabling
Most enterprise Linux ships it by default. If not:
|
|
Verify it’s running and the kernel is talking to it:
|
|
enabled 1 means the kernel audit is on. failure 1 means “panic(?) on audit failure” — 0=silent, 1=printk, 2=panic. Keep 1 unless you have a specific reason. lost should always be 0; non-zero means the kernel dropped events because the backlog was full.
Rule syntax basics
There are three kinds of rules:
1. Control rules
Configure the subsystem itself:
-D # delete all existing rules
-b 8192 # backlog limit
-f 1 # failure mode (0/1/2)
-e 2 # enable and immutable until reboot
-e 2 locks the rules until reboot. Good for production — nobody can tamper with audit mid-flight. -e 1 keeps them mutable.
2. Watches (file system)
Monitor file access:
-w /etc/passwd -p wa -k identity
Watches /etc/passwd for writes (w) and attribute changes (a), tags matching events with key identity. p flags: r (read), w (write), x (execute), a (attribute change). The key makes events searchable.
Watches are syntactic sugar — under the hood they become syscall rules that match open, openat, unlink, etc., with a path filter. For most file-audit needs, the watch syntax is cleaner.
3. Syscall rules
The full form, for fine-grained auditing:
-a always,exit -F arch=b64 -S execve -F auid>=1000 -F auid!=unset -k user-commands
Decoded:
-a always,exit— always audit, at syscall exit (the common choice;entryis deprecated).-F arch=b64— 64-bit binaries. For 32-bit,arch=b32. If you don’t specify, only native arch is matched; on a modern system that means 32-bit binaries escape auditing. Always add both arches for full coverage.-S execve— the syscall to match.-F auid>=1000— loginuid ≥ 1000 (real users, not system accounts).-F auid!=unset— exclude events where auid isn’t set (-1or the sentinel value). Required to avoid logging root-owned services.-k user-commands— search key.
Multiple -S on one line is an OR. Multiple -F filters are AND.
You always want both architectures if you have a 64-bit kernel:
-a always,exit -F arch=b64 -S execve -k exec
-a always,exit -F arch=b32 -S execve -k exec
Filters worth knowing
pid=<n>/ppid=<n>— exact match on process or parent PID.uid=<n>/auid=<n>— uid (effective) / auid (loginuid, the original login user).gid=<n>.exe=<path>— exact executable path.comm=<name>— first 16 chars of the process name (careful: truncated).path=<path>/dir=<path>— filesystem paths.success=yes|no— only log successes or failures.exit=<code>— exit code of the syscall.key=<name>— already tagged with this key.
loginuid (auid) is critical
auid (audit-uid, stored as loginuid in the kernel) is the UID of the user who logged in originally. It persists across su, sudo, and setuid transitions. This is what lets audit logs answer “which human caused this?” even after they became root.
auid != unset (a.k.a. auid != -1 or auid != 4294967295) filters out system events where no human login is responsible. Essential for cutting noise.
Make sure PAM is configured to set loginuid. Most distros do by default via pam_loginuid.so in /etc/pam.d/login, sshd, etc. Without it, auid is unset and your rules that filter by auid don’t work.
ausearch and aureport
ausearch: interactive search
|
|
-k <key>— by rule key.-ts/-te— time start / end. Acceptnow,today,recent(last 10 min),boot, or absolute times.-ua— by auid.-x— by executable path.-m— by message type.
The output is denormalized: related records reassembled into event blocks. ausearch -i interprets numeric fields (UIDs → names, syscall numbers → names) for readability.
Most common mistake: running ausearch without a time filter on a busy system. Default time range is “everything”. On systems with weeks of audit logs, this is slow. Always add -ts today or similar.
aureport: summaries
|
|
Gives counts and top-N summaries by category. Good for daily compliance reviews.
Shipping to external tools
ausearch/aureport work fine for interactive use. For fleet-scale analysis, forward to a SIEM. Options:
-
audisp-syslog plugin: forwards records to syslog, then your existing syslog pipeline handles them.
Enable in
/etc/audit/plugins.d/syslog.conf:active = yes direction = out path = builtin_syslog type = builtin args = LOG_INFO format = string -
audisp-remote: ships events to a remote auditd-aware collector using the audit protocol.
-
Filebeat/Fluentd/Vector: tail
/var/log/audit/audit.logwith a parser that understands the key=value format. Wazuh, Splunk UF, Elastic Beats all have audit-aware modules. -
Laurel: a purpose-built enricher that turns the wire format into structured JSON with process ancestry resolved inline. Excellent for SIEM ingest.
The pattern that works best in 2026 is: auditd writes local, Laurel parses and enriches, Vector or Fluent Bit ships JSON to the SIEM. Cleaner than parsing the raw audit format downstream.
Starter rule sets
Rule 1: Identity files
-w /etc/passwd -p wa -k identity
-w /etc/group -p wa -k identity
-w /etc/shadow -p wa -k identity
-w /etc/gshadow -p wa -k identity
-w /etc/sudoers -p wa -k identity
-w /etc/sudoers.d/ -p wa -k identity
Catches any modification to authentication files. Key identity makes them easy to search.
Rule 2: Privileged commands
-a always,exit -F arch=b64 -S execve -C uid!=euid -F euid=0 -k privilege-escalation
-a always,exit -F arch=b32 -S execve -C uid!=euid -F euid=0 -k privilege-escalation
Every execve where the effective UID becomes 0 but the real UID isn’t — i.e., every sudo/setuid-to-root. -C is the “comparison between fields” operator.
Rule 3: Time / timezone changes
-a always,exit -F arch=b64 -S adjtimex -S settimeofday -S clock_settime -k time-change
-a always,exit -F arch=b32 -S adjtimex -S settimeofday -S clock_settime -k time-change
-w /etc/localtime -p wa -k time-change
Clock manipulation is a classic anti-forensics move.
Rule 4: Network configuration
-w /etc/hosts -p wa -k network
-w /etc/resolv.conf -p wa -k network
-w /etc/network/ -p wa -k network
-w /etc/nftables.conf -p wa -k network
-w /etc/sysconfig/network -p wa -k network
Rule 5: Module load/unload
-w /sbin/insmod -p x -k modules
-w /sbin/rmmod -p x -k modules
-w /sbin/modprobe -p x -k modules
-a always,exit -F arch=b64 -S init_module -S delete_module -S finit_module -k modules
Module loading can rootkit a system. Audit it.
Rule 6: Shell profile files
-w /etc/profile.d/ -p wa -k shell-init
-w /etc/profile -p wa -k shell-init
-w /etc/bashrc -p wa -k shell-init
-w /root/.bashrc -p wa -k shell-init
Classic persistence vector.
Rule 7: Filesystem mount/unmount
-a always,exit -F arch=b64 -S mount -S umount2 -F auid>=1000 -F auid!=unset -k mount
Non-system users mounting filesystems is usually worth knowing about.
Rule 8: Failed file access
-a always,exit -F arch=b64 -S open -S openat -F exit=-EACCES -F auid>=1000 -F auid!=unset -k access
-a always,exit -F arch=b64 -S open -S openat -F exit=-EPERM -F auid>=1000 -F auid!=unset -k access
Failed opens are noisy but can reveal probing. Filter on user context to reduce noise.
Rule 9: Persistent final lock
-e 2
At the end of your rules file. Rules are immutable until reboot. This MUST be the last line. Anything after it is silently dropped.
Compliance recipes
Every major regulation imposes audit requirements. The rule sets below are commonly accepted starting points. Treat them as templates, not final word; your assessor may have specific additions.
PCI DSS
PCI DSS 10.2 requires logging:
- Individual user access to cardholder data.
- Actions by root/admin users.
- Use of authentication mechanisms.
- Initialization of audit logs.
- Creation/deletion of system-level objects.
Rules covering these:
# 10.2.1 - individual accesses to cardholder data (adjust paths)
-w /srv/cardholder-data -p rwa -k cardholder-access
# 10.2.2 - actions by root/admin
-a always,exit -F arch=b64 -S execve -F euid=0 -F auid>=1000 -F auid!=unset -k admin-actions
# 10.2.3 - access to audit logs themselves
-w /var/log/audit/ -p wa -k audit-log-access
# 10.2.4 - invalid logical access attempts
-a always,exit -F arch=b64 -S open -S openat -F exit=-EACCES -F auid>=1000 -F auid!=unset -k access-denied
# 10.2.5 - use of authentication mechanisms
-w /etc/pam.d/ -p wa -k pam
-w /etc/nsswitch.conf -p wa -k nsswitch
# 10.2.7 - creation/deletion of system-level objects
-w /etc -p wa -k system-config
-w /usr/bin -p wa -k system-binaries
-w /usr/sbin -p wa -k system-binaries
HIPAA
HIPAA’s audit control requirements (§164.312(b)) are less specific than PCI. The conservative approach is to audit all access to ePHI data directories plus the standard identity/privilege ruleset:
-w /srv/ehr -p rwa -k ephi-access
-w /srv/ehr-backups -p rwa -k ephi-access
# plus the identity, privilege-escalation, time-change rules from above
CIS Benchmarks
CIS publishes detailed audit rule sets in their benchmarks (e.g., CIS RHEL 9 Section 4.1). The scap-security-guide package on RHEL installs them in /usr/share/doc/scap-security-guide/ and you can deploy them directly or with OpenSCAP tooling:
|
|
This generates a shell script that includes CIS-compliant audit rules.
FedRAMP / NIST 800-53
NIST 800-53 AU-2 enumerates audit event types. Practically, combine CIS rules with DoD STIG audit rules. The Linux STIG ships audit rules as part of its artifact package and is a useful reference.
Common pitfalls
- Rule after
-e 2. The immutable flag must be last. Rules after it silently vanish. - Only 64-bit rules on 64-bit kernels. 32-bit binaries running on 64-bit kernels (container userspaces, old proprietary tools) bypass audit. Duplicate every syscall rule with
arch=b32. - Too-broad rules generating gigabytes/day.
-a always,exit -F arch=b64 -S openatwithout filters will bury you. Scope by path, auid, or key. - Not filtering auid=unset. System processes have no loginuid. Without this filter, you see every daemon syscall.
- Kernel dropping events (
auditctl -s | grep lostnonzero). Backlog too small, or the kernel is producing faster than auditd reads. Increase backlog:-b 16384. Check CPU usage of auditd. - Parsing the raw format in downstream tools. It’s not JSON, it’s not CSV, it’s a custom key=value format with quoting rules that surprise. Use Laurel or a purpose-built parser.
- Logs rotated but never shipped.
/var/log/audit/audit.logdefaults to in-place rotation after 8 MB × 5 files. On a busy server this is 40 MB of history. Ship to SIEM continuously. - Forgetting PAM sets loginuid. On minimal container or debug environments,
pam_loginuidmight not run, so auid stays unset and filtered rules miss events. - Disabling auditd for “performance”. The overhead is a few percent in worst cases, usually imperceptible. Compliance auditors do not accept “performance” as a reason to turn it off.
- Running with
-e 0(mutable) in production. Lets an attacker disable rules. Use-e 2for immutability.
Performance tuning
If auditd is causing measurable overhead:
- Increase the backlog so the kernel doesn’t spin waiting for userspace:
-b 16384or higher. - Run auditd on a dedicated CPU core via systemd
CPUAffinity=. - Reduce rule scope — the
execverule generating the most events is usually the culprit. Filter by key to confirm. - Write to a dedicated disk —
/var/log/auditon its own volume prevents audit from competing with other log writes. - Buffer with local rate limiting:
-r <msgs/sec>in control rules caps the per-second rate to avoid flooding.
Integration with the broader security stack
- Falco can consume syscalls directly via eBPF, largely overlapping auditd. Falco’s rule language is more expressive; auditd is standardized and certified for compliance. Running both is common — Falco for detection, auditd for compliance logging.
- osquery can query auditd events via its
socket_events,process_events, andfile_eventstables (backed by the audit subsystem on Linux). - Wazuh’s manager parses audit.log natively and ships correlated events.
- Tetragon (Cilium’s security observability) is eBPF-first but can augment auditd logs.
auditd is the compliance baseline. Other tools build on or complement it.
An operating posture
For a production Linux server that needs to pass audits:
- Install auditd. Enable at boot. Set
-e 2in rules. - Deploy a standard rule set (CIS, STIG, or a company baseline).
- Ship audit events to SIEM via Laurel + Vector or equivalent.
- Monitor
auditctl -sfor dropped events — alert on lost > 0. - Daily aureport summaries reviewed by security team.
- Log retention per compliance schedule (often 1 year minimum).
- Document rule decisions — why this rule, what threat it catches. Your auditor will ask.
When auditd alone isn’t enough
- You need real-time detection and response: auditd is after-the-fact logging. Pair with Falco or EDR.
- Application-level audit: database query logs, web app audit trails are the application’s job, not the kernel’s.
- Network flow analysis: auditd sees syscalls, not packets. Use NetFlow/IPFIX/Zeek for network visibility.
When auditd is non-negotiable
- Regulated environment: every major compliance framework specifies audit logging at roughly the level auditd provides.
- Forensic-ready systems: after an incident, audit logs are often the difference between “we know what happened” and “we don’t”.
- Insider threat programs: auditd answers “which user did this?” more reliably than any other single source.
auditd is verbose and old-feeling and the config language is idiosyncratic. But it’s also standardized, comprehensive, and every Linux sysadmin eventually needs it. Start with a small rule set keyed by purpose, ship the events to someplace searchable, and resist the temptation to audit everything. Good audit logs are curated, not exhaustive — they tell a story that a human can follow when something goes wrong.
Comments