Migrating from CRS 3.3 to CRS 4.25 LTS — Part 2: Configuration

This is Part 2 of the CRS 3.3 → 4.25 LTS migration series. Part 1 provided an overview of the migration. This post covers the crs-setup.conf changes — the most immediately breaking part of the upgrade for most operators.

If you take one thing from this post: do not reuse your CRS 3 crs-setup.conf with CRS 4 without reviewing every variable in it. Some variables were renamed, some were removed, and several new ones are required for features that did not exist in CRS 3.

Every configuration variable matters

Every configuration variable matters Antonio Batinić on Pexels

The Migration Approach for Configuration

The recommended approach is to start with the CRS 4 crs-setup.conf.example as your new base and re-apply your customizations from your CRS 3 file. Copying your old file and patching it is possible but more error-prone — the structure and default values of several sections changed.

Open both files side by side. Work section by section.

Renamed Variables: Paranoia Level

The paranoia level variable was renamed in CRS 4. In CRS 3 you set tx.paranoia_level. In CRS 4 the same setting is tx.blocking_paranoia_level:

# CRS 3 (id:900000):
#   setvar:tx.paranoia_level=1

# CRS 4 (id:900000):
#   setvar:tx.blocking_paranoia_level=1

CRS 4 also introduces a companion variable, tx.detection_paranoia_level, that did not exist in CRS 3. It lets you execute rules from a higher paranoia level than tx.blocking_paranoia_level without having those rules contribute to the anomaly score. This is useful for exploring the impact of moving to a higher PL before committing to it:

#SecAction \
#    "id:900001,\
#    phase:1,\
#    pass,\
#    t:none,\
#    nolog,\
#    setvar:tx.detection_paranoia_level=2"

If you do not set it, tx.detection_paranoia_level defaults to the value of tx.blocking_paranoia_level. For migration, we recommend you do not set it and let it.

Anomaly Scoring Variables

The threshold variable names are unchanged:

VariableCRS 3CRS 4
Inbound block thresholdtx.inbound_anomaly_score_thresholdtx.inbound_anomaly_score_threshold
Outbound block thresholdtx.outbound_anomaly_score_thresholdtx.outbound_anomaly_score_threshold

The per-severity scoring increment variables (tx.critical_anomaly_score, tx.error_anomaly_score, tx.warning_anomaly_score, tx.notice_anomaly_score) also existed in CRS 3 and carry over unchanged at their default values (5, 4, 3, 2).

What changed is the internal score accumulation model and how scores are reported — this is covered in detail in Part 4.

New Variable: Reporting Level

CRS 4 introduces tx.reporting_level (rule id:900115), which controls how much detail the phase 5 reporting rules log beyond the blocking rule itself. There is no CRS 3 equivalent.

The six levels are:

LevelBehaviour
0Reporting disabled — only the blocking rule logs
1Report requests where the blocking anomaly score ≥ threshold
2Report requests where the detection anomaly score ≥ threshold
3Report requests where the blocking anomaly score > 0
4Report requests where the detection anomaly score > 0 (default)
5Report all requests

The default is 4. This is more verbose than CRS 3, where only blocked requests were reported. If your SIEM or log aggregation infrastructure is sensitive to log volume, consider starting with level 1 or 2 during the migration and raising it once you have confirmed your log pipeline can handle the volume.

New Variable: Early Blocking

CRS 4 introduces tx.early_blocking (rule 900120), which controls whether the anomaly score is evaluated at the end of phase 1 (before the request body is processed) and at the end of phase 3 (before the response body is processed).

The purpose of early blocking is to reduce the amount of work the web server and WAF have to do when traffic will be rejected anyway.

#SecAction \
#    "id:900120,\
#    phase:1,\
#    pass,\
#    t:none,\
#    nolog,\
#    setvar:tx.early_blocking=1"

When commented out (the default), early blocking is disabled and behaviour matches CRS 3 exactly — evaluation happens only at the end of phase 2 and phase 4.

For migration, leave this commented out. Enable it only after the rest of the migration is stable.

New Variable: Default Collections

CRS 4 introduces the tx.enable_default_collections flag. In CRS 3, the GLOBAL and IP collections were initialized by the core rule set for every request. In CRS 4, the core rules themselves no longer use these collections — but plugins may.

# Default: off — collections not initialized unless needed
SecAction \
    "id:900130,\
    phase:1,\
    nolog,\
    pass,\
    t:none,\
    setvar:tx.enable_default_collections=0"

If you install any plugin that uses IP or GLOBAL collections (such as the fake-bot plugin or the auto-decoding plugin), set this to 1. If you run only the core rules with no plugins that require collections, leave it at 0.

Restricted Headers: Restructured Into Two Variables

CRS 3 managed restricted headers through a single tx.restricted_headers variable. CRS 4 splits this into two variables with different enforcement behaviour:

This split is itself a breaking change: if you customised tx.restricted_headers in CRS 3, you need to decide which of the two new variables your customisation belongs in.

New additions to the basic list

Several headers were added to the basic list that were not restricted in CRS 3:

HeaderReason
content-encodingWAF engines cannot inspect compressed bodies; blocking removes the bypass vector
x-http-method-override, x-http-method, x-method-overridePrevents HTTP method override attacks
x-middleware-subrequestCVE-2025-29927 (Next.js middleware bypass)
expectPrevents Expect-based HTTP desync attacks

All of these are blocked at PL1. If your application or any client sending requests to it uses these headers legitimately, you will see blocks immediately after the migration.

The extended list

The default extended list contains /accept-charset/. This header is deprecated and can be used to bypass the WAF on response rules, but it still appears in some legitimate clients, so it is restricted at higher paranoia levels rather than universally. If you run at PL2 or above, check whether any of your clients send Accept-Charset.

Adding exclusions

# Example: allow content-encoding on a specific upload endpoint
SecRule REQUEST_URI "@beginsWith /api/upload" \
    "id:1000,phase:1,pass,nolog,\
    ctl:ruleRemoveTargetById=920450;REQUEST_HEADERS:Content-Encoding"

X-HTTP-Method-Override in particular is used by JavaScript frameworks (Laravel, Rails, Symfony) — check your frontend if you use any of these.

New Variable: Method Override Parameter

CRS 4 adds tx.allow_method_override_parameter (rule 900210), which controls whether the _method query parameter used by many web frameworks for HTML form method override is allowed. By default this is blocked at PL2 and higher.

If your application uses a framework that relies on _method=DELETE or _method=PATCH in form submissions, set this to 1 or add a targeted exclusion. If you run at PL1 only, this is not triggered.

New Variable: Skip Response Analysis

CRS 4 adds tx.crs_skip_response_analysis (rule 900500). Response body analysis is enabled by default in CRS 4 (when SecResponseBodyAccess On is set in your engine config). A newly documented attack class — Request Filter Denial of Service (RFDoS) — can abuse response body inspection to exhaust WAF resources. Setting tx.crs_skip_response_analysis=1 disables response inspection entirely.

For migration, leave this at the default (response analysis enabled). Be aware of the trade-off if you are deploying in an environment where RFDoS is a concern.

HTTP Version Defaults

CRS 3 tolerated HTTP/0.9 requests. CRS 4 does not — rule 920430 blocks HTTP/0.9 requests outright. If your infrastructure passes HTTP/0.9 internally (rare, but seen in legacy load balancers), you will see blocks. The fix is to add back HTTP/0.9 in rule 900230.

The SecCollectionTimeout Removal

CRS 3 defined SecCollectionTimeout in crs-setup.conf. CRS 4 removed this setting from the core rule set because the core rules no longer work with collections directly. If you need a custom collection timeout, set it in your WAF’s main configuration or in a plugin’s configuration file.

Migration Checklist

Work through this list before reloading your WAF with CRS 4:

Interactive Migration Tool

Paste your CRS 3 crs-setup.conf into the tool below and click Migrate to CRS 4. It will generate a CRS 4 config with all renamed variables applied, new variables pre-populated with their defaults, and a colour-coded notes panel explaining every change.

CRS v3 → v4 Configuration Migrator

Paste the contents of your CRS 3 crs-setup.conf below. The tool extracts every variable you have set, applies all the CRS 4 renames and additions, and produces a ready-to-use CRS 4 config with inline notes on every change.

What’s Next

Part 3 covers the plugin architecture in depth — including the full mapping from CRS 3 application exclusion packages to CRS 4 plugins, and how to install them.

Related pages:

Felipe Zipitria