Auditd: Track Who Changed Your Linux Server Files

Auditd: Track Who Changed Your Linux Server Files

Use auditd to trace changes to SSH settings and Docker Compose files on your Linux VPS, with persistent rules, a safe test, and practical investigation steps.

đź’ˇ Disclosure: This article contains affiliate links. If you make a purchase through these links, we may earn a small commission at no extra cost to you. This helps support the site and keeps the content free.

Your SSH settings changed. Your Compose file now exposes a database port. Was that you, an automation job, or someone who shouldn’t have been there? Auditd gives your Linux server a record of file operations, rather than leaving you to guess from modification timestamps.

I prefer a small audit trail over a giant pile of logs nobody reads. This guide watches two sensitive directories, makes the rules persistent, and gives you a harmless way to check that events actually arrive.

This is a host-level tutorial for a conventional Ubuntu or Debian VPS with systemd and a 64-bit Linux kernel. It is not a claim that auditd was deployed on the production server behind this blog. The rule syntax follows the upstream manual linked below; validate it on your own machine before relying on it.

Why file timestamps are not enough

A timestamp tells you that something changed. It does not tell you which executable performed the operation, which login session it came from, or whether a permission change happened alongside the write.

Git helps explain intended configuration changes, but it cannot account for someone editing a live file outside the repository. Shell history is also incomplete: background services do not politely write their actions into your terminal history.

Auditd receives events from the Linux kernel’s audit subsystem and writes records for investigation. With targeted rules, you can record writes and attribute changes to paths you care about. Think accountability, not antivirus.

I would start with SSH configuration and deployment definitions. I would not start by recording every read of every file on the server. That is a surprisingly efficient way to turn a useful security tool into a storage problem.

Before you install anything

Keep a working SSH session open and confirm that your provider offers console access. We are not changing SSH authentication here, but being able to recover a server is a good habit whenever you touch host security.

You also need administrator access, available disk space, and control of the host kernel. An ordinary Docker container is not the right place to install this: audit configuration needs host-level privileges, and a container shares its host’s kernel.

Some managed environments restrict audit access. If auditctl reports an operation is not permitted despite running with sudo, investigate your hosting environment rather than adding privileges to random containers.

For the underlying access controls, use the VPS hardening checklist. Audit records complement SSH keys and firewall rules; they do not replace them.

Install auditd and inspect the current policy

On a Debian or Ubuntu host:

sudo apt update
sudo apt install auditd
sudo systemctl enable --now auditd
sudo auditctl -s
sudo auditctl -l

The status command shows whether auditing is enabled, along with counters such as lost events and the current backlog. The rule listing tells you what is already active. Read both before adding your own policy.

If the enabled state is 2, the audit configuration is immutable until reboot. Do not attempt to work around an intentional security policy. Plan changes with whoever owns that policy and arrange a maintenance window.

Also look for a never,task rule. The upstream manual warns that this can cause new processes to skip audit processing. If it is present, identify the file that installed it in /etc/audit/rules.d/ and resolve that policy deliberately before expecting your rules to work.

Do not run auditctl -D as a casual troubleshooting step. It deletes active rules, including rules another administrator may depend on.

Choose a small set of directories

This example monitors /etc/ssh/ and a dedicated deployment directory, /opt/selfhost-config/. Substitute your real Compose directory if it already exists; the deployment path below is not where Docker automatically stores its configuration.

sudo install -d -m 0750 /opt/selfhost-config
sudo test -d /etc/ssh
sudo test -d /opt/selfhost-config

Keep this directory for Compose files, environment definitions, and deployment scripts. Do not put database volumes or busy application caches inside it if you want readable audit results.

Watching a directory is helpful when an editor saves by writing a replacement file and renaming it. Watching only one file can make file replacement harder to reason about. Directory auditing is recursive, but mount boundaries need separate consideration: do not assume a mounted subtree is covered automatically.

We will record writes and attribute changes, not every read. The audit permission filter uses w for writes and a for attributes such as ownership and permissions.

Add persistent syscall-based rules

Open a new rules file:

sudoedit /etc/audit/rules.d/70-selfhost-config.rules

Paste this configuration:

-a always,exit -F arch=b64 -F dir=/etc/ssh/ -F perm=wa -k ssh_config
-a always,exit -F arch=b64 -F dir=/opt/selfhost-config/ -F perm=wa -k deploy_config

The arch=b64 filter targets the native 64-bit syscall interface. The dir field scopes each rule, perm=wa selects the operations, and the key gives you a convenient search label.

You may see older tutorials using -w /path -p wa. The upstream auditctl manual marks that watch syntax as deprecated because of performance concerns and recommends syscall-based rules. I would use the modern form for a new setup.

On x86-64 hosts that also run 32-bit programs, consider matching arch=b32 rules as well. Do not copy those blindly onto another architecture: support depends on the kernel and compatibility interface. These two rules deliberately cover only the 64-bit interface.

Load and inspect the configuration:

sudo augenrules --check
sudo augenrules --load
sudo auditctl -l

augenrules assembles the files in the rules directory into the active configuration. Its check mode checks whether the generated rules need updating; it is not a substitute for successfully loading the rules and inspecting the result.

Confirm that both keys appear in the active listing. If loading fails, read the error, check that the watched paths exist, and revisit the immutable-policy check above. A file sitting in rules.d is not proof that the kernel accepted it.

Prove that the trail works without touching SSH

Use the deployment directory for a harmless test:

sudo touch /opt/selfhost-config/audit-probe
sudo chmod 0640 /opt/selfhost-config/audit-probe
sudo rm /opt/selfhost-config/audit-probe
sudo ausearch -k deploy_config -ts recent -i

This creates an empty probe, changes its permissions, removes it, and searches recent events tagged with the deployment key. Nothing in your actual SSH configuration changes.

Expect multiple records, not one tidy sentence. A single audit event can contain a syscall record, path records, and information about the process. Records sharing the event identifier belong together.

If nothing appears, do not declare victory. Confirm the active rules, auditd status, and watched directory, then repeat the test. Check for disabled task auditing and look for lost-event counters in auditctl -s.

This positive test is the difference between an installed package and a working control. Security checkboxes are cheap. Evidence takes one extra command.

Read an event without accusing the wrong person

Start with these fields:

  • exe and comm: the executable path and process name associated with the operation.
  • uid and euid: the real and effective user identities when the syscall occurred.
  • auid: the audit login identity, when a login session supplied one.
  • name: the file path in a path record.
  • success: whether the audited syscall succeeded.

The login identity can help connect a sudo action to the original session rather than just seeing root. But service processes can have an unset login identity, and a shared account limits attribution. Audit records are evidence about processes and sessions, not magical proof of a person’s intentions.

The -i flag interprets values into a more readable form. Preserve the raw records when investigating something serious; interpreted names can depend on the system’s current account database.

To investigate SSH configuration changes from today:

sudo ausearch -k ssh_config -ts today -i

To save raw deployment events for a private investigation file:

sudo ausearch -k deploy_config -ts today --raw \
  | sudo tee /root/deploy-audit-events.txt > /dev/null
sudo chmod 0600 /root/deploy-audit-events.txt

Keep exported logs private. Paths, process arguments, and other context can expose sensitive operational details. The ausearch manual documents time filters and output options when you need a narrower investigation.

Keep the audit logs from becoming the outage

Review /etc/audit/auditd.conf before expanding coverage. Pay attention to log size, rotation, remaining disk space thresholds, and the actions configured when space runs low. Your distribution’s defaults are a starting point, not a retention policy.

A busy directory can create far more records than a quiet configuration folder. Do not watch a database’s data directory just because the database is important. The result is likely to be a flood of routine writes rather than useful configuration evidence.

Check audit status periodically, including the lost-event counter. Also monitor the filesystem holding /var/log/audit/. If you already use Uptime Kuma, remember that HTTP availability checks alone do not prove your audit pipeline is healthy.

For higher-stakes hosts, send logs to a separate protected system using an appropriate audit forwarding setup. Local logs are useful, but an attacker with sufficient root access may alter or destroy local evidence. Auditd is not a tamper-proof recorder on a fully compromised machine.

Where Docker changes the picture

Watching your host’s Compose directory records operations against those host files. It does not automatically audit every file inside every container, and it does not explain a Docker API action just because a container restarted.

Keep the distinction clear: deployment configuration auditing, application logging, and Docker daemon access controls solve different problems. For the latter, read the Docker socket proxy guide.

Also keep application updates separate from host auditing. Auditd will not pull new images, apply patches, or roll back a broken Compose deployment. It helps you investigate the file operations that occurred around those changes.

A VPN is a separate layer, not an audit solution

For private administration, a VPN you control can reduce the number of management endpoints exposed publicly. A commercial VPN is a different tool: it can be useful for client-side traffic on untrusted networks, but it does not create this audit trail or automatically make a VPS private.

The following is an affiliate link. Purchasing through it may support this site; NordVPN is not required for any step in this tutorial.

🚀NordVPN

A commercial VPN option for client-side connections on untrusted networks, separate from your server audit policy.

Explore NordVPN →

Affiliate link — we may earn a commission at no extra cost to you.

What I would do next

Start with two quiet directories. Load the rules, run the probe, and make sure you can explain the resulting events before adding more coverage.

Then write down who reviews the logs, how long they are retained, and what happens when auditing stops. My opinion is simple: a small audit trail somebody checks beats an ambitious policy nobody can interpret.

Your next action: audit one real deployment directory and run the harmless probe today. If you cannot find that event, fix the pipeline before depending on it during an incident.

Stay in the loop 📬

Get self-hosting tutorials, tool reviews, and infrastructure tips delivered to your inbox. No spam, unsubscribe anytime.

Join 0 self-hosters. Free forever.