Technology

ModSecurity and OWASP CRS: False Positives and Performance

Imagine a checkout throwing errors for only some users, with no obvious application change. A Web Application Firewall false positive is one possible cause, but the logs need to confirm it. The goal is a WAF that stops malicious requests without disrupting legitimate traffic.

ModSecurity and the OWASP Core Rule Set (CRS) inspect HTTP traffic within the enabled rules, body processors and size limits. Application-specific inputs can require tuning. This guide covers false-positive diagnosis, narrowly scoped exclusions and measured performance changes, with configuration examples that must match the installed engine and connector.

The workflow separates observation, regression testing and production enforcement. Apache examples are identified explicitly; CRS scoring details below use CRS 4, and the upload-limit example uses ModSecurity 2.9. Check the corresponding documentation for other versions before adapting a directive or include path.

Define the WAF’s Role Before Tuning

A WAF policy should detect relevant attack patterns while allowing legitimate application flows. These goals require evidence from both kinds of request. An XSS or SQLi rule match is a lead for investigation; it does not establish malicious intent without request and application context.

Keep the WAF alongside application authorization, input validation, output encoding and appropriate edge controls. Evaluate a new policy in staging or a separate observation layer while retaining established production blocking. Silence, low latency and a successful login test alone do not establish adequate coverage.

The guide to layered WAF and bot protection with Cloudflare, ModSecurity and Fail2ban explains the wider architecture. This article focuses on the ModSecurity and CRS tuning decisions within it.

Separate the Engine, Rule Set and Application

ModSecurity supplies the inspection engine; CRS supplies rules and anomaly-scoring logic. Apache ModSecurity 2.9 and libModSecurity v3 with a webserver connector have different configuration and buffering details. Confirm the engine, connector and CRS versions, effective configuration scope and enabled body processors before using an example.

CRS examines enabled targets such as headers, URLs and parsed body fields. An application may legitimately accept template syntax, encoded data or terms that also appear in attacks. The full value, rule transformations and application handling determine the risk; a field name or a familiar endpoint does not make an input safe. Verify parsing before assuming that an alert needs an exclusion.

The OWASP Core Rule Set documentation describes rule groups, paranoia levels and tuning methods. Match its version-specific examples to the rule set actually loaded by the server.

Observe, Validate and Enable a New Policy

Step 1: Detection only, with anomaly scoring

Use DetectionOnly for the candidate configuration in staging or a separate observation layer, preserving existing production enforcement. It processes rules without applying disruptive actions; switching an established production engine to this mode would remove its blocking. Collect representative login, checkout, API and upload cases alongside controlled attack tests. An observation period is complete when the necessary flows are covered, not merely after a fixed number of days.

# Apache example in staging or a separate observation layer
# Preserve established production enforcement elsewhere.
SecRuleEngine DetectionOnly

# Load the engine configuration first; adapt these example paths.
Include /etc/modsecurity/crs/crs-setup.conf
Include /etc/modsecurity/crs/rules/*.conf

Why anomaly scoring? It aggregates rule matches before making a blocking decision. A single match can reach the threshold, and legitimate input can trigger several rules. Review the matched variables and application context rather than treating multiple matches as proof of an attack.

Step 2: Set a blocking threshold and monitor

For standard CRS 4 anomaly scoring, the default inbound blocking threshold is 5 and the separate outbound threshold is 4. One critical inbound match can score 5. Verify the effective configuration and expected test outcomes before enabling a new blocking deployment. Raising thresholds can let attacks through, so first investigate confirmed false positives at the rule and target level.

# Engine configuration after successful validation
SecRuleEngine On

# Reference values inside the existing CRS threshold action:
# setvar:tx.inbound_anomaly_score_threshold=5
# setvar:tx.outbound_anomaly_score_threshold=4

The threshold lines are comments and configure nothing. In CRS 4, inspect the complete existing threshold SecAction with ID 900110 in crs-setup.conf and its defaults. If overriding values, edit or enable that existing action; do not duplicate its ID or paste bare setvar fragments as directives. Load CRS setup before its rules. Prepare monitoring and a rollback to the last validated configuration before changing production enforcement.

Step 3: Nudge the paranoia level gradually

Higher paranoia levels run additional rules and may require more false-positive tuning. In CRS 4, blocking_paranoia_level and detection_paranoia_level separate the level used for blocking from additional evaluation. A higher detection level can be evaluated while the tuned lower blocking level remains active; detection must not be below blocking, and additional rules still cost processing time. Validate representative legitimate requests and controlled attack cases before raising the blocking level. The CRS paranoia levels guide explains the version-specific behavior.

Narrow Exclusions and Their Coverage Tradeoffs

Disabling broad rule groups can suppress alerts by removing inspection. For a confirmed false positive, identify the exact path, rule and target that need an exception. Other controls can remain active, but the excluded rule no longer inspects that target in the matched context.

1) Build a short feedback loop

Use the audit log to find the rule ID, matched variable such as ARGS or REQUEST_HEADERS, and available matched-data context. Correlate it with the request method, path, content type and application behavior. Reproduce the legitimate failure in a controlled environment before choosing an exception; protect sensitive values in any retained evidence.

2) Exclude by surgical path or parameter

Suppose a review endpoint intentionally accepts markup in a comment field and rule 941100 produces a confirmed false positive. Check the application’s validation and context-appropriate output encoding first. The following runtime exclusion removes only ARGS:comment from that rule for the matched URI:

# ModSecurity runtime exclusion, loaded before the affected CRS rules
# Choose a rule ID unique to the local configuration; install one copy.
SecRule REQUEST_URI "^/api/review$" "id:10010,phase:1,pass,t:none,ctl:ruleRemoveTargetById=941100;ARGS:comment"

This is ModSecurity syntax, not a native Nginx directive. REQUEST_URI includes query data, so the exact expression matches /api/review but not /api/review?x=1. With t:none it does not normalize encoded or alternative forms. Test actual application routing before adapting the match; widening it changes the exception’s scope. Install one locally unique rule before the CRS rule it affects. Other paths and targets retain their existing inspection.

3) Understand the limits of an admin-route bypass

Admin dashboards often include features—file uploads, embedded HTML, complex filters—that can trigger CRS. Authentication alone does not make uploaded content or admin inputs safe. Prefer exclusions for confirmed false positives in specific rules and parameters, retaining other inspection. The bypass below is an exceptional tradeoff, not routine tuning: require independently enforced access controls and application-side upload validation, and verify which inspection phases your Apache integration still runs.

# Apache-style example for a very specific admin uploader path
<LocationMatch "^/admin/uploader$">
    SecRuleEngine Off
</LocationMatch>

In Nginx with the ModSecurity connector, any equivalent bypass should use an exact-match location and preserve the route’s existing handler and access controls. A broad prefix location could disable inspection for additional routes; prefer narrow rule exclusions instead.

4) Tackle common hot spots: SQLi and XSS alerts on legit inputs

Legitimate SQL terms or angle brackets can appear in application inputs, but a match depends on the full value and enabled rules. Confirm the context and inspect the matched target before changing SQLi or XSS coverage. The earlier comment-field example applies only to its exact path and rule; it is not a general exemption for review forms or markup.

Confirm that JSON bodies are parsed correctly, then use the audit log to identify the specific SQLi rule and parameter involved. For a confirmed false positive, exclude only that rule’s target on the affected path. Internal access or authentication alone does not justify removing SQLi checks.

5) Be kind to uploads

Large uploads require explicit body limits, temporary-storage capacity and timeout controls. The following example is for Apache with ModSecurity 2.9. It sets a total request-body limit of 12.5 MiB and a 128 KiB limit excluding file portions, including the non-file parts of multipart requests. These values are illustrative and do not create a multipart-only allowance. In v2, the in-memory threshold can spill multipart bodies to a temporary file; it is not a total worker or server memory cap. libModSecurity v3 uses webserver/connector buffering controls instead. Check the exact engine and connector version before adapting the example.

# Apache with ModSecurity 2.9; illustrative body limits
SecRequestBodyAccess On
SecRequestBodyLimit 13107200
SecRequestBodyNoFilesLimit 131072
SecRequestBodyInMemoryLimit 131072
SecRequestBodyLimitAction Reject

Test legitimate uploads, non-file fields and over-limit requests through every layer. With engine enforcement active, Reject prevents an over-limit body from being accepted through this limit policy; ProcessPartial inspects only the allowed prefix. DetectionOnly automatically uses partial processing and does not establish the same rejection guarantee. At the application layer, verify actual file content, size, authorization and storage rules rather than trusting a client-supplied MIME type. These checks complement the WAF and do not automatically reduce CRS false positives. Budget temporary disk and aggregate memory for concurrent uploads.

6) Don’t forget outbound rules

CRS response rules can detect some information leaks or unwanted response content. Their body checks require appropriate response buffering and MIME-type coverage. Disabling response-body access removes that coverage and is a separate decision from upload buffering. Evaluate new response rules in staging or a separate observation layer while retaining established production enforcement; a global switch to DetectionOnly would also stop inbound blocking. Measure the cost and false positives before changing response thresholds or coverage.

Measure Performance Before Narrowing Inspection

WAF overhead depends on the workload and configuration. Potential hotspots include broad rule coverage, response-body inspection, and verbose debug logging. Measure their contribution before narrowing inspection.

Verify routing before reducing inspection

A URL ending in .png, .css or .js is not proof that a static file will be served: rewrites, fallback handlers and applications may process it dynamically. Requests for static resources also have methods, headers and parameters worth checking. Keep the rule engine enabled while measuring the actual cost. Consider any reduction only after verifying the serving route and documenting which controls remain; health checks also need an explicitly defined scope.

Compare latency, CPU, blocked requests and rule matches under representative load. A lower rule count is not useful if it removes a required check. If a confirmed false positive remains, prefer a documented rule-and-target exception for the affected request context, and test the excluded and non-excluded paths.

Right-size your PCRE and logging

Check the regex library and build options used by your installed engine; JIT availability and benefit vary. Change PCRE limits only with representative tests and inspect limit-exceeded events, since a limit can stop a regex evaluation. In ModSecurity 2.9, the match and recursion limits belong in global server configuration, not inside a VirtualHost. The v3 manual documents SecPcreMatchLimit from 3.0.10 and does not support SecPcreMatchLimitRecursion. The commented values below retain the earlier illustration, not recommended defaults.

# Audit policy example; retain configured log path and parts
SecDebugLogLevel 0
SecAuditEngine RelevantOnly
SecAuditLogRelevantStatus "^(?:4|5)"
# Optional ModSecurity 2.9 global-only examples, after measurement:
# SecPcreMatchLimit 100000
# SecPcreMatchLimitRecursion 100000

This example selects 4xx and 5xx status codes for audit relevance; rule auditlog actions and warning/error events can also produce records regardless of status. It does not mean only failed HTTP responses will be logged. Keep a valid log destination and useful audit parts, restrict access to sensitive request data, and plan rotation and storage. Debug level 0 does not turn off the audit log. Raise debugging temporarily only when investigating a specific issue, then return to the agreed setting.

Tune request phases and transformations

Phase 1 runs before the request body is available; phase 2 can inspect body data as well as other request targets. Rare use of bodies on a path does not justify skipping phase 2. First establish the allowed methods, content types, body processors and parser-error handling, then verify what remains covered by every rule change. Transformations such as decoding are part of attack detection; do not remove them merely because they cost CPU. Any narrow target exclusion needs a confirmed application reason and regression checks.

Scope your configuration to your traffic

Document which paths serve APIs, dynamic pages and verified static files, including rewrite and fallback behavior. Use that map to evaluate inspection requirements and changes. A more detailed configuration may add maintenance or processing cost; compare the measured result and remaining controls rather than assuming that narrower scope is always faster.

An Illustrative Tuning Scenario: Checkout XSS Alerts

Suppose a checkout loyalty field triggers an XSS rule when a user submits a coupon code containing template-like strings. Inspect the matched data and confirm that the input is legitimate. Before considering an exclusion, verify server-side validation and context-appropriate output encoding wherever that value is rendered.

For a confirmed false positive, consider excluding that field from only the matched XSS rule on the checkout endpoint. Authentication alone is not a safety guarantee. Monitor anomalies on that endpoint and test legitimate and malicious inputs after the change. Other rules remain active, but the excluded rule no longer inspects that field; any performance benefit must be measured.

Keep the confirmed legitimate case and controlled attack variants as regression fixtures. Repeat them when the loyalty feature, routing or CRS changes; the original reason for an exception may no longer apply.

Build Representative Regression Cases

1) Define expected application outcomes

List the important login, checkout, search, API and upload flows. Include accepted methods, content types and representative values. Record the expected application result as well as HTTP status: a 200 response containing an application error is not a successful checkout. Run candidate-policy checks in staging or a separate observation layer while established production blocking remains active.

2) Include controlled attack cases

For each changed rule or target, include authorized test requests that should still be detected or blocked. Record the expected rule match or enforcement result and compare it with the actual audit record. Test both the excluded context and nearby contexts that must remain covered. A lower alert count does not prove better accuracy.

3) Test routing boundaries

Exercise query strings, trailing slashes, encoded forms and fallback routes accepted by the application. Check whether each reaches the same handler and whether the exclusion matches it as intended. Include unrelated paths and parameters to detect an overly broad exception. Do not widen a path match solely to make a failed test disappear.

4) Test body-processing boundaries

Include valid and malformed JSON, multipart requests with non-file fields, and requests on either side of the configured size limits. Compare WAF, webserver and application outcomes. Verify parser-error handling, rejection behavior and temporary-storage use under concurrent uploads; a successful small upload does not validate the limit policy.

5) Compare performance with equivalent traffic

Use comparable request mixes, concurrency and cache conditions for baseline and candidate measurements. Record latency distribution, CPU, errors, body-processing and regex-limit events, plus the expected detections. Identify which component changed before attributing the difference to a WAF directive. Keep measurements with the tested configuration version.

Record Release Evidence and Rollback Criteria

Identify the exact configuration change

Record the engine, connector and CRS versions, affected scope, effective thresholds and local rule IDs. Keep a reviewable configuration difference and the last validated version. Name an owner for each exception and state the application behavior that requires it.

Decide whether the evidence is sufficient

Map the regression cases to the changed paths and controls. Resolve unexpected legitimate failures, missing expected detections and unexplained parsing errors before expanding the release. An untested critical flow is a coverage gap even when existing logs are quiet. Approve a higher blocking paranoia level only after its representative legitimate and controlled attack results have been reviewed.

Define an executable rollback

Specify who can restore the last validated configuration, how to confirm it loaded, and which functional and detection tests establish recovery. Define the relevant error, latency and missed-detection triggers in advance. Disabling the entire WAF is not a substitute for reverting the candidate change while maintaining the established policy.

Recheck after release and application changes

Confirm the intended configuration is active, inspect audit and application outcomes, and run the agreed production-safe checks. Revisit exceptions after changes to application validation, plugins, routing or CRS. Remove an exception when its documented reason no longer applies, after verifying that the original legitimate case still works.

Check the WAF’s Place in Layered Defense

Combine the WAF with application authorization, input validation, context-appropriate output encoding and suitable edge controls. Each has a defined responsibility. Application validation does not automatically justify relaxing WAF rules; a confirmed false positive still needs a narrow, documented exception and tests of remaining coverage. Check whether traffic can reach the origin without the intended edge controls, and keep the origin policy effective for those paths.

Evaluate the layers together through legitimate requests, controlled attack tests and failure scenarios. Track response time, errors, false positives and missed expected detections. The aim is an understood and maintainable policy; faster pages or exclusively meaningful alerts are outcomes to measure, not guaranteed consequences of adding layers.

Investigate and Document an Audit-Log Match

Start an audit investigation with the matched rule ID, rule tag and available matched-data context. Correlate the request path, method, content type and application behavior. Treat logged values as sensitive and incomplete evidence: a SQLi or XSS tag does not establish malicious intent, and a familiar JSON endpoint can still receive attacks.

For a confirmed false positive, document the request context, rule ID, target name, reason, owner and regression cases. Prefer the smallest necessary target exception over disabling a rule for an entire endpoint. Runtime ctl exclusions must run before the affected rules; configure-time rule removal/update directives belong after them. Keep local changes outside vendor rule files and retest them when CRS or application behavior changes.

Use Documentation for the Installed Versions

The OWASP Core Rule Set documentation covers rule behavior and tuning concepts. The ModSecurity reference manual covers engine directives and variables; select the manual for the installed engine version. The CRS paranoia levels guide explains the additional rules and evaluation settings available at higher levels.

Make the Next Change Verifiable

Begin with one confirmed false positive and a reproducible legitimate request. Test the smallest justified change in staging or a separate observation layer while retaining established production blocking. Keep its remaining coverage, expected results and rollback method explicit.

Raise a blocking paranoia level only after representative legitimate traffic and controlled attack cases meet the expected outcomes. Measure any performance change under comparable load, and repeat the relevant tests when the application or rule set changes. Neither quiet logs nor fewer rules establish a successful tuning result.

Frequently Asked Questions

Start in staging or a separate observation layer using DetectionOnly, while preserving existing production blocking. Review representative legitimate traffic and controlled attack cases, then tune confirmed false positives. Verify effective anomaly thresholds before enabling blocking with monitoring and a rollback plan. Evaluate higher paranoia levels in staging or through your CRS version’s separate detection level without disabling established enforcement.

Confirm the false positive using audit logs and application context, then exclude only the necessary rule target for the affected parameter and path. Document the rule ID, target, reason, owner, and regression cases. Runtime exclusions must execute before the affected CRS rules; configure-time removals and updates belong after them. Test remaining coverage, and do not treat authentication alone as justification for a bypass.

Measure latency, CPU, rule matches, and errors under representative load before reducing inspection. Review body limits, buffering, parser errors, logging, and regex-limit events for your engine and connector version. Keep debugging low outside investigations while retaining useful audit records. Do not bypass requests merely because their URLs look static, disable response-body coverage casually, or assume PCRE JIT and limit changes will improve performance.
↑