Osquery File Integrity Monitoring with Evented Tables
Scheduled queries sample state on a timer. Evented tables record file changes the instant they happen. Here is how osquery FIM works, how to configure it, and where it falls short.


The problem with most endpoint monitoring is not that it misses the loud events. It is that it samples on a timer, and attackers do not schedule their moves around your timer.
Osquery file integrity monitoring solves a specific version of that problem. A scheduled query reads state at the moment it runs, then sleeps. If someone edits a sensitive file and puts the original back before the next run, the snapshot shows nothing. FIM works differently. It subscribes to the operating system and records the change when the change happens, not when the clock next ticks. That difference is the whole point, and it is also where teams get the configuration wrong.
This is a practical guide to the evented tables that power FIM in osquery: what they watch, how to turn them on, the config that decides signal from noise, and the honest limits you should know before you promise anyone a tamper alert.
How a file change becomes a logged event
A snapshot query samples state on a timer. An evented table subscribes to the kernel and records the change the moment it happens.
What is osquery file integrity monitoring?
Osquery file integrity monitoring is a set of evented tables that report creation, modification, and deletion of files inside paths you choose. Unlike the snapshot tables that most people start with, these tables do not answer "what does this file look like right now." They answer "what has happened to this file since I last looked," and they build that answer continuously in the background.
The tables map to a native facility on each operating system. On Linux, file_events uses the kernel inotify subsystem, and process_file_events uses the Audit subsystem to tie a change to the process that made it. On Windows, ntfs_journal_events reads the NTFS change journal. On macOS, file_events is built on Apple's FSEvents. Same idea on every platform: let the operating system tell you what changed, then present it as SQL rows you already know how to query. The full behavior is documented in the osquery file integrity monitoring guide.
If snapshot tables are new to you, start with our overview of what osquery is and the most useful osquery tables, then come back. FIM is a layer on top of the basics, and it makes more sense once the snapshot model is familiar.
Why does a snapshot miss what FIM catches?
Picture a scheduled query that reads /etc/sudoers every five minutes and logs the hash. An attacker with root adds a line, runs one command, and reverts the file. Elapsed time: forty seconds. Your next snapshot reads the reverted file, computes the original hash, and reports that nothing changed. The query did its job perfectly and told you a lie, because the question it asks is only true at the instant it runs.
FIM does not sample. The moment /etc/sudoers is written, inotify fires, osquery records the event with the file hash at that moment, and the row waits in the buffer to be logged. The revert produces a second event. You now have two rows that tell the real story: the file was modified, then modified again, inside a minute. That is the signal a snapshot structurally cannot produce.
The tradeoff is volume, and it is real. Evented tables on a busy host produce far more rows than a snapshot does, which is why the guidance is always to watch narrow, high value paths rather than everything. More on that below, and on keeping the agent light in our guide to osquery performance tuning.
How do you turn on FIM in osquery?
FIM is off by default, and it takes two switches plus a path list to turn on. First, enable the eventing engine. Then enable the specific table for your platform.
Next, tell osquery which paths to watch and schedule a query that reads the resulting rows. The config below watches SSH keys, everything under /etc, and /tmp, while excluding a noisy directory. The wildcard rules are simple: a single % matches one level, and %% matches recursively.
One detail that trips people up: the interval does not control when changes are detected. Changes are recorded the instant they occur. The interval, 300 seconds here, only controls how often the already recorded events get flushed to your logger. A category name in exclude_paths has to match a category in file_paths or osquery drops it silently, which is a common reason an exclusion appears to do nothing.
What does a file event actually contain?
Each row in file_events carries the action, the category you named, the target path, a timestamp, and the file hashes computed at the time of the change. A modification to an SSH key looks like this in the log:
The hash matters more than it looks. Two events on the same path with the same resulting hash tell you a file was touched but its content is back to a known state. Two events with different hashes tell you the content actually changed. That single field is the difference between a metadata blip and a real modification, and it is why FIM output is worth correlating rather than alerting on blindly. This is the same discipline that separates real risk from noise in configuration drift and vulnerability risk.
What should you actually watch?
The instinct to watch everything is the fastest way to a useless deployment. Watch the paths where a change is rare and meaningful. A short starting list for a Linux server: SSH authorized keys, cron directories, /etc/passwd and /etc/sudoers, the web root, and the directories your service binaries live in. On Windows, watch startup folders, scheduled task definitions, and key application directories. These are the places persistence gets installed and configuration gets tampered with.
Do the arithmetic before you deploy. Watching four narrow path groups across 5,000 hosts, where a legitimate change happens maybe twice a week per host, gives you roughly 40,000 events a week. That is a reviewable stream. Point the same config at /usr recursively and you will generate that volume before lunch on day one, most of it from package updates nobody needs an alert about. Narrow beats broad every time.
Exclusions are the other half of a clean stream, and they take iteration. Every environment has a handful of paths inside a watched directory that churn constantly for reasons nobody cares about: a lock file, a cache, a log that rotates. Watch the parent directory, then add those noisy children to exclude_paths under a matching category. The first week of a FIM deployment is mostly this: read the log, find the ten paths generating ninety percent of the volume, decide which are noise, and exclude them. Skip that week and your detection logic drowns before it ever runs.
There is also a compliance driver worth naming. PCI DSS version 4.0 requirement 11.5.2 requires a change detection mechanism that alerts on unauthorized modification of critical system files, configuration files, and content files, with comparisons run at least weekly, and version 4.0 extended that scope to cloud and containerized components. Per the PCI Security Standards Council, this is not optional for anyone in a cardholder data environment. Osquery FIM can satisfy the detection half of that requirement without a dedicated commercial agent.
How do you turn file events into detection?
Raw file events are not alerts. A change to /etc/hosts during a maintenance window is routine. The same change at 3am from a shell that logged in ten seconds earlier is not. Turning events into detection means adding two things the file_events table does not carry on its own: who made the change, and whether anyone expected it.
For the who, use process_file_events on Linux, which ties a change to the process and user that caused it. Running both tables gives you "what changed" and "who changed it" in the same stream, which is the difference between an event you can investigate and one you can only stare at. For the whether, baseline. Most watched paths change on a rhythm: deploys touch the web root, package updates touch /usr, admins touch /etc on ticketed work. Log a week of events, learn the normal pattern, then alert only on the changes that break it.
The practical shape is a short list of expected changes per path category and an alert on everything else. Keep that alerting logic where it belongs, in your SIEM or detection layer, and keep osquery doing the one thing it is good at: reporting exactly what happened, in order, with hashes. A modification to authorized_keysoutside a provisioning run is worth a page. The same file rewritten by your config management tool every hour is worth nothing. Only your baseline knows the difference, so build it before you wire up a single alert.
Where does osquery FIM fall short?
FIM in osquery is useful and rough at the edges in equal measure. Know the sharp parts before you rely on it.
On Linux, inotify has hard limits. Each watched path consumes a watch descriptor, and each descriptor holds kernel memory that cannot be swapped. Watch a directory with a very large number of subdirectories and you can exhaust the available handles, at which point you silently stop receiving events. The fix is to raise the kernel limits and, more importantly, to be specific with your paths.
A few more behaviors that surprise people. If a watched file is deleted, inotify stops watching that path, so a delete then recreate can leave you blind to the new file. inotify does not track changes made through hard links. And many applications replace a file rather than editing it in place, which means watching the file directly can miss the replacement. Watch the containing directory instead. One more constraint on Linux: osquery cannot share the Audit subsystem with auditd, so if you run both, one of them loses. These are documented in the osquery known issues list, and none of them are dealbreakers. They are reasons to test your config against a real change before you trust it.
The deeper limit is not technical. FIM tells you a file changed. It does not tell you whether the change was a routine deploy, an admin fixing something at 2am, or an intruder installing persistence. Answering that question means correlating the event with who was logged in, what process wrote the file, and whether the change fits a known pattern. That is judgment, and raw events do not supply it. This is the exact gap Artemes AI is built to close: we pair deep endpoint context with an analysis layer that reads the surrounding system state to decide whether a change is benign or worth waking someone up for. Collection is the easy half. Deciding what a change means is the half that costs you analysts.
Frequently asked questions
Is osquery FIM real time or interval based?
Both, and the distinction matters. Detection is real time: the event is recorded the moment the file changes. Logging is interval based: recorded events are flushed to your logger on the schedule you set, 300 seconds by default. You are never waiting on the interval to catch a change, only to see it in the log.
Does FIM slow down the endpoint?
It can, if you watch too much. The cost scales with the number of watched paths and the rate of change inside them. A tight path list is cheap. A recursive watch on a busy directory tree is expensive and can exhaust inotify handles on Linux. Scope narrowly and the overhead stays negligible.
Can osquery FIM replace a dedicated FIM product for compliance?
For the detection requirement in frameworks like PCI DSS 4.0, yes, it can generate the required change alerts. What it does not include is the reporting, ticketing, and attestation workflow that commercial products wrap around the data. Many teams use osquery for collection and build the workflow themselves or in their SIEM.
What is the difference between file_events and process_file_events?
file_events tells you a file changed. process_file_events, which uses the Linux Audit subsystem, ties the change to the process and user that made it. The second is more valuable for investigation and more expensive to run. Start with file_events and add process auditing where the extra context earns its cost.
The takeaway
If your monitoring plan is a set of scheduled hash checks, you have a tamper detector that only works when the tampering is slow. Turn on file_events for a handful of paths that should almost never change, watch the log for a week, and tune the exclusions until the stream is reviewable. Then decide what fires an alert and what merely gets recorded. The queries are easy. The path list and the exclusions are where the value is, and the analysis of what a change actually means is where you should expect to spend real effort. For the wider deployment picture, our definitive osquery handbook covers scaling this across a fleet.
See which of your findings actually matter
Artemes AI combines deep endpoint context with AI-driven analysis to show which findings are actually exploitable on your hosts—not just everything a scanner can list. We are onboarding early access teams now.

Chris Seymour
Chris writes about vulnerability prioritization, exploitability, AI-assisted remediation, and the engineering realities of turning scanner output into remediation decisions.

