Home / Resources / Fix / ModSecurity 403: Fix One Public-Page False Positive
AI Search Intelligence

ModSecurity 403: Fix One Public-Page False Positive

The short answer

Find the rule and matched request field before changing access. In this tested nginx example, CRS rule 930130 blocks a public HTML tutorial because its URL contains package.json. A conditional exclusion restores that one host and GET/HEAD path while keeping the firewall active. The same test preserves other paths, write methods, SQL-injection detection and the origin's private-route response.

Copy the tested ModSecurity exclusion

This example applies only to the fictional public tutorial at docs.example/docs/package.json. Confirm the actual endpoint is intended for public use and contains no private application files. Save as exclusion.conf and load it before CRS; use a unique local rule ID in your installation.

# Fictional public tutorial. Confirm the real page contains no private files.
# Load before crs-setup.conf and rules/*.conf in this lab.
# Keep SecRuleEngine On. This grants no bot identity or authentication privilege.
SecRule REQUEST_HEADERS:Host "@streq docs.example" \
    "id:10010,phase:1,pass,nolog,t:none,chain"
    SecRule REQUEST_METHOD "@rx ^(?:GET|HEAD)$" "t:none,chain"
    SecRule REQUEST_FILENAME "@streq /docs/package.json" \
        "t:none,ctl:ruleRemoveTargetById=930130;REQUEST_FILENAME"
Version 1.0 · This removes one filename target from rule 930130 for the matching transaction. It does not authenticate a crawler, disable the engine or grant access to private files. Test your real host, normalization, methods and application contract before deploying.
Quick actions

What caused the tested 403?

The fictional origin serves an approved public HTML tutorial at /docs/package.json. That route returns explanatory text, not a package file read from disk. A direct origin request can succeed while the same request through the firewall is denied. The endpoint's public status is part of this teaching fixture; it is not a reason to expose a real application's manifests, credentials or source files.

The nginx access response and ModSecurity audit record identify one transaction. Rule 930130 reports a restricted-filename match against REQUEST_FILENAME. Rule 949110 then reports that the inbound anomaly score reached the blocking threshold. These are different roles: disabling the final threshold check would affect far more than the original filename match.

Start a real investigation with the final URL, method, host, response and corresponding event. Record the matching rule ID, variable and value, plus the blocking decision. Check whether the route is intentionally restricted. A search console 403 label, an allowed robots file or a missing citation does not independently identify ModSecurity as the cause.

What does the exclusion change?

The copied chain matches docs.example, GET or HEAD, and the exact /docs/package.json filename. Only after all three conditions match does it exclude REQUEST_FILENAME from rule 930130 for that transaction. The host is a scope condition, not an identity credential. A production host with a port, alias or different path normalization needs its own tested configuration.

The browser, Googlebot-labeled and unknown-agent requests all receive the same public tutorial after the correction. There is no User-Agent allowlist in the rule. Someone copying a crawler header gains no authentication privilege: this exact content was approved for any public reader. Separate bot-use preferences and other access controls still belong to their existing owners.

Keep the original CRS files unchanged. The small local exclusion is easier to inspect and remove than a forked vendor rule. Leave the engine in blocking mode and retain the existing threshold. This example is a specific response to a verified false positive, not a generic recommendation to exclude rule 930130 wherever package.json appears.

Why does placement matter?

Our tested rule runs in phase 1, so its exclusion must take effect before that rule executes. In the lab, exclusion.conf is included before crs-setup.conf and rules/*.conf. The nginx configuration keeps modsecurity on and loads the complete ModSecurity rules file. Module enablement and the engine's blocking setting are separate configuration controls.

Moving the identical exclusion below the CRS includes did not repair the request. The filename rule had already contributed its score. The later exclusion did not undo that earlier contribution. This failed placement is included in the executed matrix so the handoff specifies order as well as the copied text.

The local rule ID 10010 is an example. Check for an existing ID before adapting it. Run the server's configuration test, then use the deployment and reload procedure appropriate to your installation. The lab starts isolated processes for each variant; it does not test your production reload mechanism or hosting control panel.

What did the real firewall tests show?

On September 13, 2026, we executed 12 request cases in each of four configurations: before, corrected, late exclusion and rollback. The fixture used nginx 1.24.0, ModSecurity 3.0.12, connector 1.0.3 and the full CRS 4.29.0 ruleset. nginx and the fictional origin listened only on loopback; requests actually passed through the firewall engine.

Each response was correlated with its JSON audit transaction. Successful GET cases had to return the exact tutorial body, while HEAD had to return no body. The SQL-like query still triggered rule 942100 after the filename exception, with no 930130 match in that corrected transaction. The origin's private endpoint retained its 401 response.

Rollback removed the exclusion and reproduced the before-state status, body checks and rule IDs for all 12 cases. That is an executed local restoration check. It does not establish a production rollback, authentic crawler visit, DNS/TLS behavior, indexing or improved traffic.

Request groupBeforeCorrectedLate / rollback
Public tutorial GET: browser, Googlebot label, unknown agent403200; exact HTML403 / 403
Public tutorial HEAD: OAI-SearchBot label403200; empty body403 / 403
POST, other host/path, plain or encoded path suffix403403403 / 403
SQL-like query on the public tutorial403403; 942100 retained403 / 403
Private origin endpoint401401401 / 401
Ordinary public start page200200200 / 200

How do you reproduce and adapt the example?

The repository fixture includes exclusion.conf, the pinned runtime manifest, setup.py and run.py. On an Ubuntu 24.04 amd64-compatible Linux system with Python 3.12 and dpkg-deb, run python3 test/fixtures/modsecurity_delivery/run.py from a checkout. The setup downloads checksum-pinned packages into the fixture directory; it does not install or start a system nginx service. The runner prints all observations and closes its temporary servers.

The first run requires network access to the listed Ubuntu and CRS release URLs. The exact package revisions and archive hashes are retained with the results. This is a reproducible teaching environment, not a recommendation to freeze a production server indefinitely at those versions. Recheck compatible supported packages and repeat the controls when upgrading.

For the real issue, replace the fictional host and route only after confirming the endpoint's public contract and its matched variable. Inspect path aliases, encoded forms and trailing segments. Keep unrelated requests in the negative set. If your logs identify another rule or enforcement service, write and test that different correction instead of adapting this exclusion by guesswork.

What belongs in the operator handoff?

Completed fictional handoff: retain the public tutorial, add the one chained exclusion before CRS, and leave the engine and remaining rules active. Attach the original 930130/949110 event, exact proposed configuration, successful body check, protected controls, failed late-placement example and restoration result. Private endpoints and unrelated filenames remain outside the exception.

For production, name the hosting operator, approver, deployment window and rollback trigger. Save the before configuration and verify the served response after deployment. Stop or restore the prior version if the intended body remains unavailable, the matched scope widens or a negative control changes. A real package or credential file that should remain private must remain blocked.

Use the remediation ticket to assign firewall work to the hosting team. RankEcho's Fix Engine supports the reviewed page-change workflow when the investigation also identifies a supported owned-page correction. The sample fix previews that handoff; RankEcho is not presented here as a native ModSecurity administrator. Record later crawler and search observations separately from the access repair.

Frequently asked questions

Should I disable ModSecurity to fix a crawler 403?

Identify the actual rule first. This example restored one approved public route while retaining the engine, scoring threshold and other rules. It does not justify a global disable or an exception for private files.

Did Googlebot or OpenAI visit the test site?

No. The client supplied those User-Agent strings over loopback. The test establishes firewall behavior for controlled requests; genuine provider identity and arrivals remain separate evidence.

Why did the same exclusion fail below CRS?

In the executed configuration, the phase-1 filename rule had already added its score. Moving the exclusion after it did not undo that contribution. The tested working placement precedes the CRS includes.

Will this fix improve rankings or AI citations?

The local tests establish access behavior, preserved controls and restoration. They do not establish indexing, rankings, citations, referrals or a causal traffic effect.

Sources reviewed

Material technical claims below were checked against primary provider documentation. The sources support the documented control or signal, not a guarantee of indexing, ranking, an AI impression, or a citation.

5 claim-level source records
Checked 2026-09-13 · Primary-source technical documentation review · Confidence is recorded per claim.
Claim reviewedOfficial sourceReview record
CRS supports conditional rule exclusions and documents placement before the rules they affect.OWASP CRS: false positives and tuningChecked 2026-09-13 · Primary source checked September 13, 2026 · The exact exclusion ran before and after the complete CRS includes. The late placement left the original block in place. · Confidence: High
CRS accumulates anomaly scores and evaluates whether they reach the configured blocking threshold.OWASP CRS: anomaly scoringChecked 2026-09-13 · Primary source checked September 13, 2026 · The fixture retained its configured defaults and blocking engine. Rule 930130 identified the filename match; 949110 recorded the resulting inbound block. · Confidence: High
In CRS 4.29.0, rule 930130 checks REQUEST_FILENAME against restricted file patterns in phase 1.OWASP CRS 4.29.0: restricted file ruleChecked 2026-09-13 · Primary source checked September 13, 2026 · Actual nginx/ModSecurity requests to a fictional public tutorial named package.json triggered the rule. No production file was exposed or inspected. · Confidence: High
The nginx connector enables ModSecurity, loads a rules file and supports a correlated transaction identifier.ModSecurity: nginx connectorChecked 2026-09-13 · Primary source checked September 13, 2026 · nginx 1.24.0, ModSecurity 3.0.12 and connector 1.0.3 served the loopback fixture with CRS 4.29.0. Responses were matched to actual JSON audit records. · Confidence: High
Google documents verifying crawler requests through published address information or forward/reverse DNS checks.Google: verify crawler requestsChecked 2026-09-13 · Primary source checked September 13, 2026 · Googlebot and OAI-SearchBot headers in the lab are controlled labels, not verified provider arrivals. No crawler identity was authenticated. · Confidence: High
Explore the Fix Engine →
Last updated 2026-09-13 · RankEcho · Operated by Nexus Decision Systems LLC