yum upgrades for production use, this is the repository for you.
Active subscription is required.
In this comprehensive guide, we explain how to install, configure, and test the Open Web Application Security Project Core Rule Set (OWASP CRS) with the NGINX ModSecurity module on Rocky Linux, AlmaLinux, and RHEL 9/10. The OWASP CRS is one of the most effective web application firewall (WAF) rule sets available, providing enterprise-grade security against the most common web attacks.
The NGINX Extras repository provides both the NGINX ModSecurity module (nginx-module-security) and ready-to-use OWASP CRS packages. Together, they protect your web applications against SQL Injection (SQLi), Remote Code Execution (RCE), Local File Inclusion (LFI), Cross-Site Scripting (XSS), and many other attacks listed in the OWASP Top 10.
What is the OWASP Core Rule Set?
The OWASP Core Rule Set (CRS) is a set of generic attack detection rules designed to work with ModSecurity or compatible web application firewalls. Unlike signature-based detection systems that require constant updates to catch new threats, CRS uses anomaly scoring and generic attack patterns to identify malicious requests—even zero-day exploits.
Key Features of OWASP CRS 4.x
The latest OWASP CRS version 4.x brings significant improvements over previous versions:
- Anomaly Scoring Mode: Instead of blocking on the first matched rule, CRS accumulates a threat score across multiple rules. This dramatically reduces false positives while maintaining strong security.
- Paranoia Levels: Four configurable paranoia levels (PL1 through PL4) allow you to balance security strictness against false positive rates. PL1 is suitable for most applications, while PL4 provides maximum protection for high-security environments.
- OWASP Top 10 Coverage: Comprehensive protection against all categories in the OWASP Top 10, including injection attacks, broken authentication patterns, and security misconfigurations.
- Reduced False Positives: CRS 4.x includes extensive testing and tuning to minimize false positives in production environments.
- Plugin Architecture: Extend functionality with official and community plugins for specific applications like WordPress, Drupal, and Nextcloud.
Prerequisites
Before you begin, ensure you have:
- Rocky Linux 9/10, AlmaLinux 9/10, or RHEL 9/10
- Root or sudo access
- An active GetPageSpeed Extras subscription for production use
Installing the Repository
First, install EPEL and the GetPageSpeed Extras repository:
sudo dnf -y install epel-release
sudo dnf -y install https://extras.getpagespeed.com/release-latest.rpm
Installing NGINX with ModSecurity and OWASP CRS
Next, install NGINX, the ModSecurity connector module, and the OWASP CRS package in a single command:
sudo dnf -y install nginx nginx-module-security nginx-owasp-crs
This command installs the following packages:
- nginx: The high-performance NGINX web server
- nginx-module-security: The ModSecurity v3 NGINX connector module
- nginx-owasp-crs: The OWASP CRS rule set pre-configured for NGINX
Configuring NGINX with ModSecurity
Step 1: Load the ModSecurity Module
Edit your main NGINX configuration file /etc/nginx/nginx.conf and add the module load directive at the very top, before any other directives:
load_module modules/ngx_http_modsecurity_module.so;
Step 2: Enable ModSecurity in Your Server Block
The OWASP CRS package provides a ready-to-use configuration file at /etc/nginx/modsec_includes.conf. To enable ModSecurity for a site, add these directives to your server block:
server {
listen 80;
server_name example.com;
modsecurity on;
location / {
modsecurity_rules_file /etc/nginx/modsec_includes.conf;
root /var/www/html;
index index.html index.php;
}
}
Step 3: Test and Reload NGINX
Finally, verify your configuration is valid, then reload NGINX:
sudo nginx -t
sudo systemctl reload nginx
Understanding the OWASP CRS Configuration
The OWASP CRS configuration is organized into several key files:
Main Configuration Files
| File | Purpose |
|---|---|
/etc/nginx/modsecurity.conf |
Core ModSecurity settings (rule engine, body access, limits) |
/etc/nginx/modsec_includes.conf |
Master include file that loads all CRS components |
/etc/nginx/owasp-modsecurity-crs/crs-setup.conf |
CRS-specific settings (paranoia level, anomaly thresholds) |
Rule Categories
The CRS includes specialized rule files for different attack types:
| Rule File | Protection Against |
|---|---|
REQUEST-913-SCANNER-DETECTION.conf |
Automated vulnerability scanners |
REQUEST-920-PROTOCOL-ENFORCEMENT.conf |
HTTP protocol violations |
REQUEST-921-PROTOCOL-ATTACK.conf |
HTTP smuggling, request splitting |
REQUEST-930-APPLICATION-ATTACK-LFI.conf |
Local file inclusion attacks |
REQUEST-931-APPLICATION-ATTACK-RFI.conf |
Remote file inclusion attacks |
REQUEST-932-APPLICATION-ATTACK-RCE.conf |
Remote code execution (OS commands) |
REQUEST-933-APPLICATION-ATTACK-PHP.conf |
PHP-specific injection attacks |
REQUEST-941-APPLICATION-ATTACK-XSS.conf |
Cross-site scripting (XSS) |
REQUEST-942-APPLICATION-ATTACK-SQLI.conf |
SQL injection attacks |
REQUEST-943-APPLICATION-ATTACK-SESSION-FIXATION.conf |
Session fixation attacks |
RESPONSE-950-DATA-LEAKAGES.conf |
Information disclosure in responses |
RESPONSE-955-WEB-SHELLS.conf |
Web shell detection |
Testing Your WAF Configuration
Creating a Test Application
To test the WAF without affecting production, create a simple test site. First, create the file /etc/nginx/conf.d/test-waf.conf:
server {
listen 8099;
server_name localhost;
modsecurity on;
location / {
modsecurity_rules_file /etc/nginx/modsec_includes.conf;
root /usr/share/nginx/html;
index index.html;
}
}
Then, create a simple HTML file and reload NGINX:
echo "<html><body>Hello World</body></html>" | sudo tee /usr/share/nginx/html/index.html
sudo nginx -s reload
Now you can verify the setup with a normal request:
curl http://localhost:8099/
You should see: <html><body>Hello World</body></html>
Testing Attack Detection
Let’s verify that the WAF blocks various attack types. First, try sending a SQL injection attack:
curl "http://localhost:8099/?id=1'%20UNION%20SELECT%20*%20FROM%20users--"
The CRS should return a 403 Forbidden response, indicating the attack was blocked.
Next, verify XSS detection works correctly:
curl "http://localhost:8099/?input=%3Cscript%3Ealert(1)%3C/script%3E"
Additionally, check that Local File Inclusion attempts are blocked:
curl "http://localhost:8099/?file=../../../etc/passwd"
Furthermore, confirm Remote Code Execution pattern detection:
curl "http://localhost:8099/?cmd=;cat%20/etc/passwd"
Finally, verify Scanner Detection using a Nikto user agent:
curl -H "User-Agent: Nikto" "http://localhost:8099/"
All of these requests should return 403 Forbidden, confirming the CRS is protecting your application.
Using Nikto for Comprehensive Testing
Nikto is a popular vulnerability scanner that generates thousands of malicious requests. Therefore, it’s excellent for testing your WAF configuration:
sudo dnf -y install nikto
nikto -h localhost:8099
Without the CRS enabled, Nikto typically reports hundreds of potential vulnerabilities. However, with CRS enabled, most of these requests are blocked, drastically reducing the reported items.
Understanding ModSecurity Logs
When ModSecurity blocks a request, it logs detailed information to the NGINX error log. A typical blocked request entry looks like this:
ModSecurity: Access denied with code 403 (phase 2). Matched "Operator `Ge' with parameter `5' against variable `TX:BLOCKING_INBOUND_ANOMALY_SCORE' (Value: `20' ) [file "/etc/nginx/owasp-modsecurity-crs/rules/REQUEST-949-BLOCKING-EVALUATION.conf"] [line "222"] [id "949110"] [msg "Inbound Anomaly Score Exceeded (Total Score: 20)"] [ver "OWASP_CRS/4.22.0"] [tag "anomaly-evaluation"] [tag "OWASP_CRS"]
The log contains several key pieces of information:
- Total Score: 20: The accumulated anomaly score (threshold is 5)
- id “949110”: The blocking evaluation rule that enforced the threshold
- ver “OWASP_CRS/4.22.0”: OWASP CRS version
Tuning OWASP CRS for Production
Adjusting the Paranoia Level
The paranoia level controls how aggressively the CRS detects threats. To change it, uncomment and edit the SecAction in /etc/nginx/owasp-modsecurity-crs/crs-setup.conf:
SecAction \
"id:900000,\
phase:1,\
pass,\
t:none,\
nolog,\
tag:'OWASP_CRS',\
ver:'OWASP_CRS/4.22.0',\
setvar:tx.blocking_paranoia_level=1"
Paranoia Level Guidelines:
| Level | Use Case | False Positive Rate |
|---|---|---|
| PL1 | Standard websites, general protection | Low |
| PL2 | E-commerce, user-facing applications | Moderate |
| PL3 | Banking, healthcare applications | Higher |
| PL4 | Maximum security environments | Highest |
For most websites, PL1 (default) provides excellent protection with minimal false positives.
Configuring Anomaly Score Thresholds
CRS uses anomaly scoring to determine when to block requests. By default, the inbound threshold is 5, meaning a request must accumulate 5 or more anomaly points to be blocked. You can adjust this in crs-setup.conf:
SecAction \
"id:900110,\
phase:1,\
pass,\
t:none,\
nolog,\
setvar:tx.inbound_anomaly_score_threshold=5,\
setvar:tx.outbound_anomaly_score_threshold=4"
Keep in mind that:
- Lower thresholds = stricter blocking (more false positives)
- Higher thresholds = more lenient blocking (potentially less security)
Creating Rule Exclusions
If legitimate requests are being blocked, you should create exclusions rather than disabling rules entirely. Add custom exclusions to /etc/nginx/owasp-modsecurity-crs/rules/REQUEST-900-EXCLUSION-RULES-BEFORE-CRS.conf:
# Example: Exclude a specific parameter from SQL injection checks
SecRule REQUEST_URI "@beginsWith /api/search" \
"id:1000,\
phase:1,\
pass,\
t:none,\
nolog,\
ctl:ruleRemoveTargetById=942100;ARGS:query"
Advanced Configuration Topics
Enabling IP Reputation with Project Honeypot
CRS supports IP reputation checking via Project Honeypot. To enable this feature, add your API key to crs-setup.conf:
SecHttpBlKey your_api_key_here
SecAction "id:900500,phase:1,pass,t:none,nolog,setvar:tx.block_suspicious_ip=1"
GeoIP-Based Blocking
With the libmaxminddb library (automatically installed), you can enable country-based blocking:
SecGeoLookupDB /usr/share/GeoIP/GeoLite2-Country.mmdb
SecRule REMOTE_ADDR "@geoLookup" "chain,id:900600,phase:1,deny,log"
SecRule GEO:COUNTRY_CODE "@pm CN RU" "t:none"
Rate Limiting with ModSecurity
You can also protect against brute force and DoS attacks using rate limiting:
SecAction "id:900700,phase:1,pass,t:none,nolog,setvar:ip.request_count=+1,expirevar:ip.request_count=60"
SecRule IP:REQUEST_COUNT "@gt 100" "id:900701,phase:1,deny,status:429,log,msg:'Rate limit exceeded'"
Keeping OWASP CRS Updated
The OWASP CRS is actively maintained with regular updates that include new attack signatures and reduced false positives. Therefore, you should update your installation regularly:
sudo dnf update nginx-owasp-crs
sudo systemctl reload nginx
The GetPageSpeed repository ensures you receive the latest stable CRS releases with proper packaging for RHEL-based systems.
Troubleshooting Common Issues
False Positives on Legitimate Requests
If you encounter false positives, follow these steps:
- Check the error log for the rule ID that triggered
- Evaluate if the rule is appropriate for your application
- Create a targeted exclusion rather than disabling the rule
Performance Considerations
ModSecurity with CRS adds some processing overhead. To optimize performance, consider:
- Setting appropriate
SecRequestBodyLimitvalues - Disabling response body inspection if not needed:
SecResponseBodyAccess Off - Using
SecRuleRemoveByIdto disable unnecessary rules
ModSecurity Not Blocking Attacks
If attacks are not being blocked, verify the following:
SecRuleEngine Onis set in/etc/nginx/modsecurity.conf- The
modsecurity on;directive is in your server block - The
modsecurity_rules_filepath is correct - NGINX was reloaded after configuration changes
- You’re serving actual content (not using
returndirectives which bypass phase 2 checks)
Conclusion
The OWASP Core Rule Set combined with ModSecurity provides robust protection for NGINX-powered web applications against a wide range of attacks. By following this guide, you’ve implemented enterprise-grade WAF security using packages from the GetPageSpeed Extras repository.
In summary, here are the key takeaways:
- OWASP CRS 4.x offers superior attack detection with reduced false positives through anomaly scoring
- Pre-packaged installation via
nginx-owasp-crssimplifies deployment on RHEL-based systems - Paranoia levels allow you to balance security strictness with usability
- Regular updates ensure protection against emerging threats
For more information about installing the ModSecurity module, see How to install the ModSecurity NGINX module in Rocky Linux/AlmaLinux/RHEL 9.
Frequently Asked Questions
Q: What’s the difference between ModSecurity and OWASP CRS?
ModSecurity is the WAF engine that processes requests, while OWASP CRS is the rule set that tells ModSecurity what attacks to detect. You need both for effective protection.
Q: Will OWASP CRS slow down my website?
With proper configuration, the impact is minimal (typically 1-5ms per request). As a result, the security benefits far outweigh the minor performance cost.
Q: How do I know if attacks are being blocked?
Monitor /var/log/nginx/error.log for ModSecurity denial messages. Additionally, consider setting up alerting for high-severity blocks.
Q: Can I use OWASP CRS with NGINX Plus?
Yes, the same module and rules work with both open-source NGINX and NGINX Plus.
Q: What attacks does OWASP CRS block?
CRS blocks SQL injection, XSS, local/remote file inclusion, remote code execution, HTTP protocol attacks, scanner detection, session fixation, and many more attack categories covered in the OWASP Top 10.
Q: How does anomaly scoring work?
Each rule that matches a suspicious pattern adds points to an anomaly score. At the end of request processing, if the total score exceeds the threshold (default: 5), the request is blocked. Consequently, this approach allows multiple low-confidence indicators to combine into high-confidence attack detection.
Q: What is the recommended paranoia level for WordPress sites?
For most WordPress sites, Paranoia Level 1 (default) provides excellent protection. If you experience false positives with certain plugins, create targeted exclusions rather than lowering security. Furthermore, WordPress-specific plugins for CRS are available to reduce common false positives.
Q: How can I test if ModSecurity is working without blocking legitimate traffic?
Set SecRuleEngine DetectionOnly in /etc/nginx/modsecurity.conf. This logs potential attacks without blocking them, allowing you to identify false positives before enabling enforcement mode.
Security Best Practices
When deploying OWASP CRS in production, follow these security best practices:
Start in Detection Mode
Before enabling blocking mode, run CRS in detection mode for at least a week while monitoring logs. This approach helps identify legitimate traffic that might trigger false positives.
Monitor and Alert
Set up log monitoring and alerting for ModSecurity events. High anomaly scores often indicate real attacks that warrant investigation.
Keep Rules Updated
Subscribe to the OWASP CRS mailing list and update your rules regularly. Since new attack techniques are constantly discovered, rule updates provide protection against emerging threats.
Implement Defense in Depth
OWASP CRS is one layer of security. Therefore, combine it with:
- Input validation in your application code
- Parameterized database queries
- Content Security Policy headers
- Regular security audits and penetration testing
Document Your Exclusions
When creating rule exclusions, document why each exclusion was necessary. This documentation helps future administrators understand the security implications and revisit exclusions during security reviews.
Related Resources
For additional security hardening, consider these complementary articles:
- How to install the ModSecurity NGINX module in Rocky Linux/AlmaLinux/RHEL 9
- NGINX Extras Repository – Premium NGINX modules
- GetPageSpeed Repository Subscription – Access all packages
