LUNAROPS · OPERATIONAL UPLINK 100% UPTIME 1,247d POSTS 893 JEFF.MOON@LUNAROPS.DEV UTC --:--:--

auditd: Linux's Syscall Logger

auditdlinuxsecurityloggingcompliancesiem

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:

  1. The kernel audit subsystem, which intercepts syscalls and generates audit records.
  2. 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.
  3. 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:

1
2
3
4
dnf install audit audit-libs     # RHEL / Fedora
apt install auditd audispd-plugins    # Debian / Ubuntu

systemctl enable --now auditd

Verify it’s running and the kernel is talking to it:

1
2
3
4
5
6
7
8
auditctl -s
# enabled 1
# failure 1
# pid 1234
# rate_limit 0
# backlog_limit 8192
# lost 0
# backlog 0

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; entry is 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 (-1 or 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

1
2
3
4
5
6
ausearch -k identity
ausearch -k identity -ts today
ausearch -k identity -ts 10:00:00 -te 10:05:00
ausearch -ua alice -ts today
ausearch -x /usr/bin/passwd
ausearch -m USER_LOGIN -ts today --success no
  • -k <key> — by rule key.
  • -ts / -te — time start / end. Accept now, 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

1
2
3
4
5
6
aureport --summary -ts today
aureport --failed --summary
aureport --auth --summary
aureport --file --summary
aureport --executable --summary --input-logs
aureport --login -ts today

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:

  1. 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
    
  2. audisp-remote: ships events to a remote auditd-aware collector using the audit protocol.

  3. Filebeat/Fluentd/Vector: tail /var/log/audit/audit.log with a parser that understands the key=value format. Wazuh, Splunk UF, Elastic Beats all have audit-aware modules.

  4. 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:

1
2
3
dnf install scap-security-guide
oscap xccdf generate fix --profile cis_server_l1 \
    /usr/share/xml/scap/ssg/content/ssg-rhel9-ds.xml > cis-fix.sh

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

  1. Rule after -e 2. The immutable flag must be last. Rules after it silently vanish.
  2. 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.
  3. Too-broad rules generating gigabytes/day. -a always,exit -F arch=b64 -S openat without filters will bury you. Scope by path, auid, or key.
  4. Not filtering auid=unset. System processes have no loginuid. Without this filter, you see every daemon syscall.
  5. Kernel dropping events (auditctl -s | grep lost nonzero). Backlog too small, or the kernel is producing faster than auditd reads. Increase backlog: -b 16384. Check CPU usage of auditd.
  6. 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.
  7. Logs rotated but never shipped. /var/log/audit/audit.log defaults to in-place rotation after 8 MB × 5 files. On a busy server this is 40 MB of history. Ship to SIEM continuously.
  8. Forgetting PAM sets loginuid. On minimal container or debug environments, pam_loginuid might not run, so auid stays unset and filtered rules miss events.
  9. 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.
  10. Running with -e 0 (mutable) in production. Lets an attacker disable rules. Use -e 2 for immutability.

Performance tuning

If auditd is causing measurable overhead:

  • Increase the backlog so the kernel doesn’t spin waiting for userspace: -b 16384 or higher.
  • Run auditd on a dedicated CPU core via systemd CPUAffinity=.
  • Reduce rule scope — the execve rule generating the most events is usually the culprit. Filter by key to confirm.
  • Write to a dedicated disk/var/log/audit on 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, and file_events tables (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:

  1. Install auditd. Enable at boot. Set -e 2 in rules.
  2. Deploy a standard rule set (CIS, STIG, or a company baseline).
  3. Ship audit events to SIEM via Laurel + Vector or equivalent.
  4. Monitor auditctl -s for dropped events — alert on lost > 0.
  5. Daily aureport summaries reviewed by security team.
  6. Log retention per compliance schedule (often 1 year minimum).
  7. 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