File integrity monitoring for a small online store
If you take card payments, you are probably already required to detect unauthorised changes to files on your server. Most small merchants do not, and most do not know they should.
File integrity monitoring watches a defined set of paths and alerts when their contents
change unexpectedly. For an online store the paths that matter are the web root, the payment
page and its scripts, system binaries, /etc configuration and authentication
files. PCI DSS Requirement 11.5.2 asks for this, and Requirement 10 asks you to retain
the logs. Installing it is the easy part. The hard part is scoping it so the alerts stay
meaningful and someone still reads them in month three.
Card skimming is the reason this rule exists
The typical attack on a small store makes no noise. Someone modifies the checkout page, or a script it loads, so that the modified code copies card details to a third party as the customer types them. Everything keeps working and orders still complete. Nothing looks wrong from the outside, sometimes for months, and the first sign is your acquirer telling you that fraud has been traced back to your store. File integrity monitoring exists because the change to that file is the earliest point in the attack that you can detect.
Paths to watch, and how closely
| Path | Reason | Sensitivity |
|---|---|---|
| Web root and application code | A web shell or a skimmer lands here. Any change here that you did not deploy is an incident until proven otherwise. | High |
| The payment page and its scripts | Card skimming works by modifying the page or the scripts it loads. This is the attack PCI DSS is written to catch. | High |
| System binaries and libraries | Changes here mean something with root has modified the system. It is rare, and serious when it happens. | High |
| Configuration under /etc | Covers SSH config, firewall rules, cron, scheduled tasks and web server config. Attackers usually set up persistence here. | High |
| Authentication files | Covers users, groups, sudoers and authorized_keys. A new key you did not add is the clearest signal you will get. | High |
| Uploads and cache directories | These change constantly by design. Alert on executable content appearing and ignore ordinary changes, or you will get more alerts than anyone can read. | Tuned |
Noise is the real problem
Most small FIM deployments end up abandoned, and for the same reason. The default configuration alerts on every change everywhere, a cache directory generates two hundred events an hour, and within a fortnight nobody reads any of it. You are then worse off than before, because you now believe you are covered.
The fix is to treat directories differently. Code, system and configuration paths should alert on any unexpected change. Directories that are supposed to change, such as uploads, caches and session storage, should alert only when executable content appears, because that is what matters there. Deployments also need to be a known event, so your own releases do not look like intrusions.
File integrity monitoring best practices
Most published lists stop at "establish a baseline" and "reduce noise". Below are the decisions behind those phrases on a real server, each with the setting or command that carries it out. The examples use Wazuh, which is what we run on managed machines. The reasoning applies to AIDE or any other FIM tool as well.
- Verify the machine before you take the baselineThe
first scan defines normal, so anything already on the machine becomes normal with it,
including a web shell. The AIDE manual says to build the database straight after
installation, before you connect the machine to a network. On a server that is already
live, check first:
dpkg --verifyon Debian and Ubuntu orrpm -Vaon the RHEL family for system files, and a comparison against your release or repository for the web root. The dpkg manual says its check should not be considered any kind of security verification, and the reference checksums do sit on the disk an intruder would control. Run it before the baseline, as an addition to it. In our testdpkg --verifyexited 0 even when it listed a modified binary, so read its output and ignore the exit code. - Add the paths the defaults leave outWazuh's stock agent
configuration watches
/etc,/usr/bin,/usr/sbin,/bin,/sbinand/boot. Your web root is not on that list, and it is where a skimmer lands. PCI DSS says the same thing. Tools usually arrive preconfigured for operating system files, and the merchant must define critical files for custom applications. Add the application code,/root/.sshand every user's.sshdirectory, and the user crontabs in/var/spool/cron/crontabs. Watch the whole.sshdirectory rather than one file, because OpenSSH also readsauthorized_keys2by default, and Debian and Ubuntu keep that default. - Exclude narrowly, and anchor every patternUploads and caches
change by design, but excluding them outright leaves the place attackers most like to hide
files unwatched. Watch them for files that can execute. Wazuh's
restrictattribute limits a directory entry to matching file names, and in our test it matched regardless of case, soshell.PHPalerted as well. Anchor every exclusion. The AIDE manual explains why. An unanchored pattern also matches a directory an attacker creates with a similar name, and everything inside it goes unseen. A plain Wazuhignorepath is unanchored in the same way, as the Gotchas below show, so write exclusions astype="sregex"patterns with^at the start and a trailing/or$. Wazuh's reference says ignored files are still scanned, but on 4.14.8 the agent never opened them during a scan. An ignored path is outside the baseline. - Real time where minutes matter, scheduled scans everywhereWazuh's
real-time mode uses inotify on Linux. Point it at directories. The reference says it works
only on directories, and although 4.14.8 accepted a single file, that watch stopped
reporting after the first edit that replaced the file, which is what
sed -iand most editors do. Use it for the web root, the payment page and the SSH key directories. Keep the scheduled full scan running too, because the inotify manual lists what real time misses: its event queue can overflow and drop events, it does not see changes made over network filesystems, and it cannot say which user or process made a change. Wazuh scans every 12 hours by default, well inside the weekly minimum in PCI DSS. When you need to know who changed a file, Wazuh'swhodatamode records the user and process. By default it does this through the Linux audit framework, so auditd has to be running. The Gotchas below show what happens when it is not. - Tie every legitimate change to a recordA server has two sources of
legitimate change: package updates and your own releases. dpkg and apt log package changes
with timestamps in
/var/log/dpkg.logand/var/log/apt/history.log, so a changed binary that matches an entry is patching and one that does not is an incident. Deploy code from a repository or build artefact so "was this ours?" has a mechanical answer. In a git checkout,git status --porcelainlists modified and untracked files and prints nothing when the tree matches the commit. Do not mute alerts during deploys, because nobody sees anything written while alerts are muted. Post each deploy to the channel the alerts go to instead. - Send alerts to a named person, and check the thresholdWazuh's
default server configuration has email switched off, and switching it on sends only alerts
of level 12 or above. The stock FIM rules are level 7 for a changed or deleted file and
level 5 for a new one, so turning email on is not enough. A modified checkout page still
sends nothing. The global
email_alert_levelalso overrides granular email settings, so a per-group email rule alone changes nothing. Lower the global level, or write a local rule for your critical paths with thealert_by_emailoption. PCI DSS 12.10.5 expects your incident response plan to cover these alerts, which on a small team means one named person and a written first step. - Protect the monitor from the machine it watchesAn attacker with
root can stop the agent, edit its configuration or rewrite its local database. Get each
change off the machine as it happens. The Wazuh agent reports what it finds to the Wazuh
server, and an attacker cannot take back an alert that has already left. The server's copy of the
file inventory follows whatever the agent reports, though, so it is not an independent
baseline. For one the server cannot touch, the AIDE manual recommends keeping the database,
the configuration and the binary on read-only media. Then alert when the agent goes silent. Stopping the
agent cleanly raises rule 506, agent stopped. An agent that vanishes without a clean stop raises rule 504,
agent disconnected, once the manager's
agents_disconnection_timehas passed, 15 minutes by default in 4.14. Both are level 3, far below the default email threshold of 12, so route both explicitly. In our test a clean stop never produced 504 at all. Without that routing, a monitor that has been switched off looks the same as a quiet week. - Keep evidence as well as the alertWith
report_changes, Wazuh keeps compressed copies of monitored text files and puts the diff in the alert. Instead of "checkout.js changed", you see the exact line that was added. List secrets such as private keys and database credentials undernodiffso their contents never travel in an alert. When something is found, copy the modified file and its timestamps before restoring from backup, because the restore erases the evidence of how they got in. PCI DSS 10.5.1 asks for 12 months of audit log history, with the latest three immediately available. - Review on a calendar, and prove it still firesWeekly, read the exclusion list and every alert that was explained away, and check each explanation still holds. Monthly, create a harmless file in the web root, confirm the alert reaches the person on call, then delete it and confirm the deletion alert as well. It takes two minutes and it is the only way to tell a quiet monitor from a broken one.
Points two, three, four and eight, as they look in a Wazuh agent configuration for a shop in
/var/www/shop:
<!-- Wazuh agent ossec.conf, inside <syscheck>. Example paths for a PHP shop. -->
<!-- Code and templates: any change alerts, with a diff of what changed -->
<directories realtime="yes" report_changes="yes">/var/www/shop/app,/var/www/shop/vendor</directories>
<directories realtime="yes" report_changes="yes" recursion_level="0">/var/www/shop</directories>
<!-- Uploads change all day: report only files that could execute -->
<directories realtime="yes" restrict=".php$|.phtml$|.phar$">/var/www/shop/uploads</directories>
<!-- Where an attacker adds a key or a schedule to stay in -->
<directories realtime="yes">/root/.ssh,/home/*/.ssh</directories>
<directories>/var/spool/cron/crontabs</directories>
<!-- Watch it, but never copy a secret's contents into an alert -->
<nodiff>/var/www/shop/.env</nodiff> The PCI DSS v4.0.1 requirements that apply
PCI SSC published PCI DSS v4.0.1 in June 2024, and it has been the only active version since the Council retired v4.0 on 31 December 2024. Many pages cite "Requirement 11.5". In v3.2.1 that number was the change-detection requirement itself. In v4.0.1, 11.5 is a heading that covers both intrusion detection (11.5.1) and change detection (11.5.2), so the requirement to quote is 11.5.2. These are the parts that touch file integrity:
| Requirement | The standard says | On one server |
|---|---|---|
| 11.5.2 | A change-detection mechanism alerts personnel to unauthorised modification, including changes, additions and deletions, of critical files, and performs critical file comparisons at least once weekly. | The minimum is weekly, and real time is not required. New and deleted files count, so leave new-file alerts switched on. |
| 10.3.4 | File integrity monitoring or change detection is used on audit logs, so existing log data cannot be changed without generating alerts. | The guidance says appended log lines should not alert. Watch rotated and archived logs instead of the live file. Wazuh's stock agent configuration ignores files ending in .log in every monitored directory, including ones you add, so give archives their own entry and check their names. In our test app.log.1 and app.log.2.gz alerted, and app-2026-08.log did not. |
| 11.6.1 | A change- and tamper-detection mechanism covers the security-impacting HTTP headers and script contents of payment pages as received by the consumer browser, at least weekly or at a frequency set by a targeted risk analysis. Mandatory since 31 March 2025. | Server-side FIM does not meet this on its own, because 11.6.1 looks at the page as the browser receives it. The guidance lists Content Security Policy violation reports and synthetic monitoring among the techniques. |
| 12.10.5 | The incident response plan includes monitoring and responding to alerts from change-detection mechanisms for critical files and from the payment page mechanism. | You need a named person and a written first step for an unexplained change. |
| 10.5.1 | Audit log history is retained for at least 12 months, with at least the most recent three months immediately available for analysis. | Store it off the server, so an attacker with root cannot shorten it. |
The standard defines critical files as those "that do not regularly change, but the modification of which could indicate a system compromise or risk of compromise". Its examples are system executables, application executables, configuration and parameter files, and centrally stored, historical or archived audit logs. It also says that critical files for custom applications "must be evaluated and defined by the entity". For a shop, the entity is you, and not the FIM vendor.
Which of these you are assessed on depends on your self-assessment questionnaire. SAQ A-EP for v4.0.1 (October 2024) includes 10.3.4, 11.5.2 and 11.6.1. SAQ A, as revised in January 2025, includes none of them. The revision removed 6.4.3 and 11.6.1 and replaced them with an eligibility criterion that the merchant has confirmed its site is not susceptible to attacks from scripts that could affect its e-commerce systems. PCI SSC's FAQ 1588 says that criterion applies to merchants who embed the processor's payment form in an iframe, and that it can be met with the techniques in 6.4.3 and 11.6.1 or with written confirmation from the processor. The Council also states that these SAQ changes "do not remove or diminish the underlying requirements within PCI DSS".
Limits of file integrity monitoring
FIM does not make you PCI DSS compliant. Compliance covers your policies, your people, your payment flow and your systems, and it is assessed against the scope your acquirer sets. FIM is one technical control within that, and one of the controls small merchants most often lack entirely. Implementing a control and certifying compliance are still different things, and anyone who treats them as the same is selling something. More on where that line sits.
Gotchas
- A restart accepts whatever changed while the agent was down, without an alert. We
stopped the agent, appended a skimmer line to
checkout.js, added a new PHP file and started it again. The first scan after the restart rebuilt the baseline and raised no alert for either, the manager's copy of the file inventory updated to match, and the only trace was rule 506, agent stopped, at level 3. Treat any unexplained agent stop as a reason to compare the web root with your release, not as noise. - A watched directory that appears later waits for the next full scan.
/root/.sshdid not exist when the agent started, and a new user's.sshwas created afterwards under/home/*/.ssh. Keys written to both raised nothing in real time. They alerted only at the next scheduled scan, up to 12 hours later by default, after which real time worked. Create/root/.sshbefore the agent starts, and restart the agent straight after adding a user. - A plain ignore path is a prefix. With
ignoreset to/opt/ign/cache, new PHP files in/opt/ign/cache2/and at/opt/ign/cache.phpraised nothing, while a control file beside them alerted. The sregex entry^/opt/ign/anch/silenced only that directory, and a file inanch2/still alerted. Write exclusions as anchored sregex patterns. - whodata without auditd falls back to real time. With
whodata="yes"and no auditd running, the agent started normally and logged two warnings, "Who-data engine cannot start because Auditd is not running" and "Switching who-data to real-time". Later alerts showed mode realtime with no user or process. Install and start auditd first, and checkossec.logfor warning 6923 after every agent start. - nodiff keeps a secret out of the alert, but the agent still stores it. The change to
.envalerted with "Diff truncated due to 'nodiff' configuration", but the agent still kept a gzip copy of the new contents, password included, under/var/ossec/queue/diff/file/, readable by root and the wazuh group. Treat that directory as holding secrets and keep it out of backups that leave the server. - git status does not show ignored files. In a checkout whose
.gitignorelistedvar/cache/, a web shell atvar/cache/evil.phpleftgit status --porcelainempty. Adding--ignoredshowed it, as!! var/. Run both forms, and make sure FIM covers every ignored path that can hold code.
How this was tested. On 23 September 2026 we ran the Wazuh
4.14.8 manager from the official Docker image (wazuh/wazuh-manager:4.14.8, Amazon
Linux 2023, without indexer or dashboard) and the wazuh-agent 4.14.8-1 package from
packages.wazuh.com on Debian 13 (trixie), in Docker on an Ubuntu 24.04 host. The configuration
sample above went into the agent's ossec.conf verbatim, over a fake shop in
/var/www/shop, and we read every result from the manager's
alerts.json. We checked email against an aiosmtpd 1.4.6 SMTP sink. We also
checked OpenSSH 10.0p1 on Debian 13 and 9.6p1 on Ubuntu 24.04, dpkg 1.22.22, rpm 4.16.1.3 and
git 2.47.3. Not tested: whodata with a working auditd, AIDE, inotify queue overflow, network
filesystems, and the PCI DSS and SAQ wording, which rests on the PCI SSC documents below. The
kernel audit interface refused these containers, even privileged ones.
Sources
- PCI DSS v4.0.1: Requirements and Testing Procedures, PCI Security Standards Council document library, June 2024 (Requirements 10.3.4, 10.5.1, 11.5.2, 11.6.1, 12.10.5)
- Just Published: PCI DSS v4.0.1, PCI Security Standards Council, 11 June 2024
- Self-Assessment Questionnaire A-EP for PCI DSS v4.0.1, PCI Security Standards Council document library, October 2024
- Important Updates Announced for Merchants Validating to Self-Assessment Questionnaire A, PCI Security Standards Council, 30 January 2025
- FAQ 1588: How does an e-commerce merchant meet the SAQ A eligibility criteria for scripts?, PCI Security Standards Council, February 2025
- Self-Assessment Questionnaire A-EP for PCI DSS v3.2.1, PCI Security Standards Council (Requirement 11.5 in the previous version)
- Syscheck configuration reference, Wazuh documentation
- How file integrity monitoring works, Wazuh documentation
- Alerts configuration reference (email_alert_level), Wazuh documentation
- Default agent configuration, default server configuration and rules 504, 506, 550, 553 and 554, Wazuh v4.14.0 source on GitHub
- AIDE manual, AIDE project
- inotify(7), Linux man-pages project
- dpkg(1) and rpm(8), Linux man-pages project
- git-status documentation, Git project
- sshd_config(5), OpenBSD manual pages (AuthorizedKeysFile default), and the Debian patch that keeps reading authorized_keys2, Debian OpenSSH packaging as shipped in Ubuntu 24.04
Questions
Does PCI DSS require file integrity monitoring?
Requirement 11.5.2 covers detection of unauthorised modification of critical files, and Requirement 10 covers logging and monitoring all access to system components and cardholder data. Your self-assessment questionnaire, or SAQ, decides exactly how these apply to you, so confirm your SAQ with your acquirer. If your own server touches the payment flow at all, you are generally not exempt from server-side controls.
I use Shopify or Stripe. Am I out of scope?
Your scope shrinks, but it does not necessarily disappear. If your own site builds the checkout page around the payment fields, you are typically SAQ A-EP and in scope for most server-side requirements, including file integrity monitoring. If you redirect to the processor or embed its form in an iframe, you are usually SAQ A, which still applies some requirements to the server doing the redirect or embed. Ask your acquirer which SAQ they expect from you. It is the most useful question you can ask, and it costs nothing.
Will this generate constant alerts?
It will if you monitor everything the same way, and that is why most small deployments get switched off within a month. Scope it instead. Alert on any change in code, system and configuration paths, and in directories that are supposed to change, alert only when executable content appears. A monitor nobody reads is worth less than no monitor, because it also makes you believe you are covered.
Can I do this myself?
Yes. Wazuh is open source and free to run, and its documentation maps its capabilities to PCI DSS, GDPR, HIPAA, NIST 800-53 and TSC. Installing it is the small part. The work is tuning the ruleset so alerts stay meaningful, and having someone who reads them. When people outsource this, they are paying for that ongoing attention more than for the software.
What are file integrity monitoring best practices?
Baseline from a state you have verified as clean, add your application paths to the tool defaults, exclude volatile directories narrowly with anchored patterns, use real-time monitoring for the web root and scheduled scans everywhere, tie every legitimate change to a deploy or package log, send alerts to a named person, keep the baseline where the server cannot change it, keep evidence and 12 months of logs, and test the monitor with a canary file on a schedule.
How often does PCI DSS require file integrity checks?
PCI DSS v4.0.1 Requirement 11.5.2 requires a change-detection mechanism that alerts personnel to unauthorised modification of critical files, including changes, additions and deletions, and performs critical file comparisons at least once weekly. Real-time monitoring is not mandatory, but weekly is the minimum. Requirement 10.3.4 adds change detection on audit logs, and Requirement 11.6.1 requires separate tamper detection for payment pages as the customer browser receives them.
A free review tells you what is monitored, what is exposed and what your logs would show after an incident. Pricing.
Related: How to secure a VPS you just bought · Backups that actually restore