The Linux Audit Framework records security-relevant events directly from the kernel: who changed /etc/passwd, which commands were run as root, when the clock was changed or a kernel module was loaded. Unlike application logs, these records include the original login user (auid) even after sudo, which is why PCI DSS, HIPAA and CIS benchmarks ask for them. In this tutorial you will install auditd on Ubuntu 24.04, size its logs, write a persistent rule set grouped by keys, search the results with ausearch and aureport, and finally lock the rules so an attacker cannot silently disable them.
Prerequisites
To follow this tutorial you need:
- A server running Ubuntu 24.04 LTS, for example a CubePath VPS.
- A non-root user with
sudoprivileges. - At least 1 GB of free disk space for audit logs, ideally on a separate partition or volume for
/var/log/auditon production systems.
Step 1 - Installing auditd
Install the audit daemon and the plugin package, which is needed later for forwarding events to a remote server:
sudo apt update
sudo apt install auditd audispd-plugins
The service starts automatically. Check its state and the kernel audit status:
systemctl status auditd --no-pager
sudo auditctl -s
enabled 1
failure 1
pid 1234
rate_limit 0
backlog_limit 8192
lost 0
backlog 0
...
enabled 1 means auditing is active. lost counts events the kernel dropped because the queue was full; it should stay at 0.
The audit subsystem is made of a few pieces you will use in this guide:
| Component | Purpose |
|---|---|
auditd | Daemon that writes events to /var/log/audit/audit.log |
/etc/audit/auditd.conf | Daemon settings: log size, rotation, disk-full behavior |
/etc/audit/rules.d/*.rules | Persistent rules, merged at boot by augenrules |
auditctl | Loads rules and shows status at runtime |
ausearch, aureport | Search and summarize the audit log |
Step 2 - Sizing and rotating the audit log
auditd rotates its own log files; do not add them to logrotate. Open the daemon configuration:
sudo nano /etc/audit/auditd.conf
Find and adjust these settings. They keep up to 10 files of 50 MB each (500 MB in total) and react when the disk gets full:
log_format = ENRICHED
max_log_file = 50
num_logs = 10
max_log_file_action = ROTATE
space_left = 500
space_left_action = SYSLOG
admin_space_left = 100
admin_space_left_action = SUSPEND
disk_full_action = SUSPEND
max_log_fileis in megabytes.ROTATEkeepsnum_logsfiles and deletes the oldest. If your policy requires that no audit record is ever deleted locally, useKEEP_LOGSand ship or archive the files yourself.space_leftandadmin_space_leftare in megabytes of free disk. At the first threshold auditd writes a warning to syslog; at the second it suspends logging instead of filling the disk.ENRICHEDresolves UIDs, GIDs and syscall numbers into names at write time, which makes the logs readable on another machine.
auditd reloads its configuration on SIGHUP:
sudo kill -HUP "$(pidof auditd)"
Check that the daemon accepted it:
sudo journalctl -u auditd -n 5 --no-pager
Step 3 - Writing persistent audit rules
Rules loaded with auditctl on the command line disappear at reboot. Persistent rules live in /etc/audit/rules.d/, and augenrules concatenates those files in alphabetical order into /etc/audit/audit.rules. Numbered file names control the order.
There are two kinds of rules:
- Watch rules (
-w path -p perms -k key) monitor a file or directory. Permissions arer(read),w(write),x(execute) anda(attribute change). - Syscall rules (
-a always,exit -F arch=b64 -S syscall ... -k key) record specific system calls, optionally filtered by fields such asauid(the original login user).
Every rule gets a key with -k, which is how you will search for its events later.
First, a base file that clears existing rules and sets the kernel buffer:
sudo nano /etc/audit/rules.d/10-base.rules
-D
-b 8192
--backlog_wait_time 60000
-f 1
-D deletes any previous rules, -b sets the event backlog, and -f 1 makes the kernel log a message (rather than panic) if auditing fails.
Next, the rules that most compliance frameworks ask for:
sudo nano /etc/audit/rules.d/50-security.rules
## Changes to users, groups and passwords
-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
## Changes to sudo configuration
-w /etc/sudoers -p wa -k sudoers
-w /etc/sudoers.d/ -p wa -k sudoers
## Changes to SSH server configuration
-w /etc/ssh/sshd_config -p wa -k sshd
-w /etc/ssh/sshd_config.d/ -p wa -k sshd
## Scheduled tasks
-w /etc/crontab -p wa -k cron
-w /etc/cron.d/ -p wa -k cron
-w /var/spool/cron/ -p wa -k cron
## Login records
-w /var/log/lastlog -p wa -k logins
## Commands executed as root by a logged-in user (includes sudo)
-a always,exit -F arch=b64 -S execve -F euid=0 -F auid>=1000 -F auid!=unset -k root_cmds
-a always,exit -F arch=b32 -S execve -F euid=0 -F auid>=1000 -F auid!=unset -k root_cmds
## Permission and ownership changes by regular users
-a always,exit -F arch=b64 -S chmod,fchmod,fchmodat -F auid>=1000 -F auid!=unset -k perm_mod
-a always,exit -F arch=b64 -S chown,fchown,fchownat,lchown -F auid>=1000 -F auid!=unset -k perm_mod
## File deletion and renaming by regular users
-a always,exit -F arch=b64 -S unlink,unlinkat,rename,renameat -F auid>=1000 -F auid!=unset -k delete
## System time changes
-a always,exit -F arch=b64 -S adjtimex,settimeofday,clock_settime -k time_change
-w /etc/localtime -p wa -k time_change
## Kernel module loading and unloading
-a always,exit -F arch=b64 -S init_module,finit_module,delete_module -k modules
## Access to the audit configuration and logs themselves
-w /etc/audit/ -p wa -k audit_config
-w /var/log/audit/ -p wa -k audit_log
-F auid>=1000 -F auid!=unset limits syscall rules to actions traced back to a human login. System daemons (auid unset) and system accounts are excluded, which removes most of the noise. The b32 line for execve catches 32-bit programs, which could otherwise be used to bypass the 64-bit rule.
Load the rules and confirm they are active:
sudo augenrules --load
sudo auditctl -l | head -n 5
-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 sudoers
If augenrules prints an error, it names the file and line. A typical cause is a syscall name that does not exist for the given architecture.
Adding rules for your own data
Watch rules are the easiest way to cover application data that a standard asks you to track, such as a directory with cardholder data or exports of personal data:
sudo nano /etc/audit/rules.d/60-app.rules
-w /srv/myapp/exports/ -p rwa -k data_access
-w /etc/myapp/ -p wa -k app_config
Reading (r) generates many events on busy directories, so only use it on paths that are rarely accessed. Load the rules again with sudo augenrules --load.
Step 4 - Testing the rules
Generate an event for each key and check it was recorded. Create and remove a test user:
sudo useradd audittest
sudo userdel audittest
Search for events with the identity key from the last few minutes. -i translates numeric IDs into names:
sudo ausearch -k identity --start recent -i | tail -n 8
type=PROCTITLE msg=audit(09/25/2026 14:22:31.118:512) : proctitle=useradd audittest
type=PATH msg=audit(09/25/2026 14:22:31.118:512) : item=0 name=/etc/passwd inode=... nametype=NORMAL ...
type=SYSCALL msg=audit(09/25/2026 14:22:31.118:512) : arch=x86_64 syscall=rename success=yes exit=0 ... auid=your_user uid=root ... comm=useradd exe=/usr/sbin/useradd key=identity
Note auid=your_user next to uid=root: the record shows who really ran the command, even though it ran as root through sudo.
Now test the root command rule:
sudo cat /etc/hostname
sudo ausearch -k root_cmds --start recent -i | grep proctitle | tail -n 3
The output should include proctitle=cat /etc/hostname.
Step 5 - Reporting with aureport
aureport produces summaries that are useful for daily review and for auditors. A general summary:
sudo aureport --summary
Events grouped by key, which shows which rules fire most:
sudo aureport -k --summary
Key Summary Report
===========================
total key
===========================
412 root_cmds
37 identity
6 sudoers
Other useful reports:
sudo aureport -au --failed # failed authentication attempts
sudo aureport -l # logins
sudo aureport -x --summary # executables that generated events
sudo aureport --start today -m # account modifications today
If one key produces thousands of events a day that nobody reviews, narrow that rule (for example with an extra -F dir= or -F exe= filter) rather than keeping it. Too many events hide the important ones and can fill the backlog.
Step 6 - Forwarding events to a remote server
An attacker with root access can delete local logs, so production systems should also send audit events off the host. The audispd-plugins package includes the au-remote plugin.
On the receiving server (also running auditd), open /etc/audit/auditd.conf and enable the TCP listener:
tcp_listen_port = 60
tcp_client_ports = 1-65535
Allow the port only from your servers, then reload auditd:
sudo ufw allow from your_client_ip to any port 60 proto tcp
sudo kill -HUP "$(pidof auditd)"
On each client, point the plugin at the receiver:
sudo nano /etc/audit/audisp-remote.conf
remote_server = your_audit_server_ip
port = 60
transport = tcp
Then enable the plugin by setting active = yes:
sudo nano /etc/audit/plugins.d/au-remote.conf
active = yes
direction = out
path = /sbin/audisp-remote
type = always
format = string
Plugin changes are applied when auditd reloads:
sudo kill -HUP "$(pidof auditd)"
On the receiver, generate an event on the client and search for it with sudo ausearch -k identity --start recent -i; events from clients include node= with the client's hostname. This channel is not encrypted, so only use it over a private network or a VPN between your servers.
Step 7 - Making the rules immutable
Finally, lock the configuration so that the rules cannot be changed or auditing disabled without a reboot, which itself leaves a trace. Create a file that sorts last:
sudo nano /etc/audit/rules.d/99-finalize.rules
-e 2
Load the rules and confirm the new state:
sudo augenrules --load
sudo auditctl -s | grep enabled
enabled 2
WarningWith
enabled 2, any change to the rules requires a reboot. Finish and test your rule set before adding this file, and remember to remove or edit it before you change rules later.
Troubleshooting
audit: backlog limit exceededin the kernel log, orlostabove 0 inauditctl -s: the kernel produces events faster than auditd writes them. Increase-bin10-base.rules(for example to 16384) and narrow noisy rules.augenrules --loadhas no effect: the rules are locked with-e 2. Check withsudo auditctl -s; if it showsenabled 2, edit the files and reboot.systemctl restart auditdis refused: the unit setsRefuseManualStop=yeson purpose so audit logging cannot be stopped casually. Usekill -HUPto reload the configuration andaugenrules --loadfor rules.- Rules with
-Sfail withSyscall name unknown: the syscall does not exist for that architecture. Check the name withausyscall x86_64 <name>(orausyscall i386 <name>forb32).
Conclusion
auditd now records identity, privilege, configuration, time and kernel module changes with the real login user attached, rotates its own logs within a fixed disk budget, forwards events to a central server and cannot be disabled without a reboot. Review aureport -k --summary regularly to keep the rule set useful. As next steps, compare your rules with the CIS Ubuntu 24.04 benchmark, alert on high-value keys such as sudoers and modules from your central log platform, and document the retention period of the audit logs in your compliance policy.
