How to Set Up ModSecurity WAF with Apache on RHEL 7

ModSecurity is the most widely deployed open-source Web Application Firewall (WAF), and it integrates natively with Apache as a dynamic module. When paired with the OWASP Core Rule Set (CRS), ModSecurity can detect and block a broad range of web attacks including SQL injection, cross-site scripting (XSS), remote file inclusion, and HTTP protocol anomalies — all without changing a single line of your application code. On RHEL 7, both Apache and the ModSecurity module are available through the standard repositories, making installation straightforward. This tutorial covers installing ModSecurity, writing a baseline configuration, integrating the OWASP CRS, testing the WAF against a simulated SQL injection request, and reviewing the audit log.

Prerequisites

  • RHEL 7 server with sudo access
  • Apache (httpd) installed and running
  • EPEL repository enabled: sudo yum install -y epel-release
  • Internet access to download the OWASP CRS tarball from GitHub
  • SELinux awareness — some ModSecurity paths may require context adjustments

Step 1: Install ModSecurity for Apache

The mod_security package is available in the EPEL repository. It includes the ModSecurity engine and a default configuration file.

sudo yum install -y mod_security mod_security_crs

# Verify the module was installed
ls /etc/httpd/modules/ | grep security
# Expected: mod_security2.so

# Check the package-provided configuration files
rpm -ql mod_security | grep conf

After installation, Apache will have two new configuration files dropped into /etc/httpd/conf.d/:

  • /etc/httpd/conf.d/mod_security.conf — Main ModSecurity configuration
  • /etc/httpd/modsecurity.d/ — Directory for additional rule files

Step 2: Review and Edit the Main ModSecurity Configuration

Open the primary ModSecurity configuration file to understand its defaults and make the key changes required for active enforcement.

sudo vi /etc/httpd/conf.d/mod_security.conf

The most critical directive is SecRuleEngine. By default it may be set to DetectionOnly, which logs rule matches but does not block requests. Change it to On to enable active protection:

# /etc/httpd/conf.d/mod_security.conf (key directives)

LoadModule security2_module modules/mod_security2.so

<IfModule mod_security2.c>
    # Turn on the WAF engine — DetectionOnly logs only; On blocks
    SecRuleEngine On

    # Allow ModSecurity to inspect the request body
    SecRequestBodyAccess On

    # Maximum request body size (bytes); reject oversized bodies
    SecRequestBodyLimit 13107200
    SecRequestBodyNoFilesLimit 131072

    # Allow inspection of the response body (needed for some CRS rules)
    SecResponseBodyAccess On
    SecResponseBodyLimit 524288

    # Temporary storage for request/response body data
    SecTmpDir /var/lib/mod_security
    SecDataDir /var/lib/mod_security

    # Audit log settings
    SecAuditEngine RelevantOnly
    SecAuditLogRelevantStatus "^(?:5|4(?!04))"
    SecAuditLogParts ABIJDEFHZ
    SecAuditLogType Serial
    SecAuditLog /var/log/httpd/modsec_audit.log

    # Default action: block with HTTP 403
    SecDefaultAction "phase:2,deny,log,status:403"
</IfModule>

Create the data directory and set permissions:

sudo mkdir -p /var/lib/mod_security
sudo chown apache:apache /var/lib/mod_security

Step 3: Download and Install the OWASP Core Rule Set

The OWASP CRS is a set of generic attack-detection rules that work with ModSecurity. The version packaged with EPEL may be outdated; it is better to download the latest stable release directly from GitHub.

# Download the latest CRS release (check https://github.com/coreruleset/coreruleset/releases)
CRS_VERSION="3.3.5"
curl -L "https://github.com/coreruleset/coreruleset/archive/v${CRS_VERSION}.tar.gz" 
     -o /tmp/crs.tar.gz

# Extract to the ModSecurity rules directory
sudo tar -xzf /tmp/crs.tar.gz -C /etc/httpd/modsecurity.d/

# Rename for convenience
sudo mv /etc/httpd/modsecurity.d/coreruleset-${CRS_VERSION} 
        /etc/httpd/modsecurity.d/owasp-crs

# Copy the example configuration to the active configuration
sudo cp /etc/httpd/modsecurity.d/owasp-crs/crs-setup.conf.example 
        /etc/httpd/modsecurity.d/owasp-crs/crs-setup.conf

Step 4: Include the OWASP CRS in ModSecurity

Create an include file in /etc/httpd/conf.d/ that loads the CRS setup and all rule files in the correct order:

sudo tee /etc/httpd/conf.d/mod_security_crs.conf << 'EOF'
<IfModule mod_security2.c>
    # Include the CRS setup configuration
    Include modsecurity.d/owasp-crs/crs-setup.conf

    # Include all CRS rule files
    Include modsecurity.d/owasp-crs/rules/*.conf
</IfModule>
EOF

Test the Apache configuration and reload:

sudo apachectl configtest
# Expected: Syntax OK

sudo systemctl reload httpd

Step 5: Test the WAF with a Simulated SQL Injection Request

With SecRuleEngine On and the OWASP CRS loaded, ModSecurity should now block requests containing SQL injection payloads. Test from the command line:

# Attempt a classic SQL injection in a query parameter
curl -v "http://localhost/?id=1%20UNION%20SELECT%201,2,3--"

# Expected response: HTTP/1.1 403 Forbidden

# Also test a classic XSS payload
curl -v "http://localhost/?q=<script>alert(1)</script>"
# Expected: HTTP/1.1 403 Forbidden

A 403 response confirms ModSecurity is actively blocking the request. You should also see an entry in the audit log:

sudo tail -50 /var/log/httpd/modsec_audit.log

Step 6: Understanding the SecAuditLog Output

The audit log records detailed information about every transaction that matched a rule. A typical blocked entry looks like this:

--abc123-A--
[17/May/2026:10:22:01 +0000] V8KuZn8AAAEAAAqjAAAAAAAB 127.0.0.1 54321 127.0.0.1 80

--abc123-B--
GET /?id=1%20UNION%20SELECT%201,2,3-- HTTP/1.1
Host: localhost
User-Agent: curl/7.29.0

--abc123-F--
HTTP/1.1 403 Forbidden
Content-Length: 199
Content-Type: text/html; charset=iso-8859-1

--abc123-H--
ModSecurity: Access denied with code 403 (phase 2).
Matched "Operator `detectSQLi' with parameter ... "
[id "942100"] [msg "SQL Injection Attack Detected via libinjection"]
[data "1 UNION SELECT 1,2,3--"] [severity "CRITICAL"]

Step 7: Tuning ModSecurity — Handling False Positives

The OWASP CRS uses a scoring system called the Paranoia Level and an Anomaly Scoring threshold. A request accumulates a score as rules match; if the score exceeds the inbound threshold (default: 5), the request is blocked. To whitelist a specific rule that causes false positives, create an exclusion file:

sudo tee /etc/httpd/modsecurity.d/exclusions.conf << 'EOF'
# Disable rule 920350 (Host header is a numeric IP address) for all requests
SecRuleRemoveById 920350

# Disable rule 941100 only for the /api/upload endpoint
<Location "/api/upload">
    SecRuleRemoveById 941100
</Location>
EOF

Add the include for this file before the CRS rules include in mod_security_crs.conf, then reload Apache.

Step 8: Adjusting Paranoia Level and Anomaly Scoring

Edit /etc/httpd/modsecurity.d/owasp-crs/crs-setup.conf to tune the detection aggressiveness:

# In crs-setup.conf

# Paranoia Level: 1 (default) = fewer false positives
# Paranoia Level: 4 = most strict, many false positives
SecAction 
  "id:900000, 
   phase:1, 
   pass, 
   t:none, 
   nolog, 
   tag:'OWASP_CRS', 
   ver:'OWASP_CRS/3.3.5', 
   setvar:tx.paranoia_level=1"

# Inbound anomaly score threshold (default: 5)
# Lower = more aggressive blocking
SecAction 
  "id:900110, 
   phase:1, 
   pass, 
   t:none, 
   nolog, 
   setvar:tx.inbound_anomaly_score_threshold=5"

Step 9: Verify SELinux Compatibility

On RHEL 7 with SELinux enforcing, ModSecurity’s data and log directories must have the correct context:

sudo semanage fcontext -a -t httpd_log_t "/var/lib/mod_security(/.*)?"
sudo restorecon -Rv /var/lib/mod_security

# Verify
ls -lZ /var/lib/mod_security
# Reload to confirm everything still works cleanly
sudo systemctl reload httpd
sudo systemctl status httpd

Deploying ModSecurity with the OWASP CRS on RHEL 7 provides a powerful, rule-based layer of protection that sits in front of your application and intercepts malicious requests before they reach your code. Starting in DetectionOnly mode during the initial rollout, reviewing the audit log for false positives, and then switching to SecRuleEngine On is the safest path to production. Combined with regular CRS updates and careful tuning of the anomaly score threshold, ModSecurity can significantly reduce the attack surface of any Apache-hosted application on RHEL 7.