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
sudoaccess - 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.