ModSecurity is an open-source web application firewall (WAF) that monitors HTTP traffic and blocks common attack patterns. If you run WordPress, phpMyAdmin, custom PHP applications, or any web service exposed to the internet, ModSecurity with the OWASP Core Rule Set provides protection against SQL injection, cross-site scripting (XSS), and command injection attacks without requiring application code changes.
By the end of this guide, you will have ModSecurity configured with the OWASP CRS protecting your Apache server, real-time request filtering that blocks malicious payloads before they reach your application, and configurable rule sets you can tune to reduce false positives. For production deployments, combine this setup with SSL/TLS encryption using Let’s Encrypt for complete transport security.
This guide covers Debian 13 (Trixie), 12 (Bookworm), and 11 (Bullseye). The Digitalwave repository provides up-to-date ModSecurity packages for all three releases.
ModSecurity Version Availability by Debian Release
The Digitalwave repository provides newer ModSecurity and CRS versions than Debian’s default repositories. This guide uses the Digitalwave repository for consistent, up-to-date packages across all supported Debian releases:
| Debian Release | Default ModSecurity | Digitalwave ModSecurity | CRS 4.x Compatible |
|---|---|---|---|
| Debian 13 (Trixie) | 2.9.11 | 2.9.11+ | Yes (default repos work) |
| Debian 12 (Bookworm) | 2.9.7 | 2.9.13pre | Yes |
| Debian 11 (Bullseye) | 2.9.3 | 2.9.13pre | Yes (with Digitalwave) |
CRS 4.x requires ModSecurity 2.9.6 or newer for the @within operator. Debian 11 users must use the Digitalwave repository to get CRS 4.x support. Debian 12 and 13 users can use either default repositories or Digitalwave, though Digitalwave provides newer backports.
Update Debian System Packages
Before installing new software, update your package lists and upgrade existing packages to their latest versions:
sudo apt update && sudo apt upgrade
Install Apache HTTP Server
ModSecurity requires Apache as the underlying web server. If Apache is not already installed, install it with:
sudo apt install apache2
Verify Apache installed correctly and check its status:
sudo systemctl status apache2 --no-pager
Expected output shows Apache as active and running:
● apache2.service - The Apache HTTP Server
Loaded: loaded (/usr/lib/systemd/system/apache2.service; enabled; preset: enabled)
Active: active (running) since Mon 2026-01-27 10:15:32 UTC; 5s ago
Main PID: 1234 (apache2)
...
For a detailed Apache setup, see our Apache installation guide for Debian.
Add the Digitalwave ModSecurity Repository
The Digitalwave repository provides up-to-date ModSecurity packages specifically packaged for Debian. While Debian’s default repositories include ModSecurity, the Digitalwave backports offer newer versions with additional features and security fixes.
Install Required Dependencies
Install the necessary packages for secure repository management:
sudo apt install lsb-release ca-certificates curl -y
Import the GPG Key
Download and store the repository GPG key to verify package authenticity:
curl -fsSL https://modsecurity.digitalwave.hu/archive.key | sudo gpg --dearmor -o /usr/share/keyrings/digitalwave-modsecurity.gpg
Add the Repository
The repository configuration depends on your Debian version. Debian 12 and 13 use the modern DEB822 .sources format, while Debian 11 uses the legacy .list format.
For Debian 13 (Trixie) and Debian 12 (Bookworm):
cat <<EOF | sudo tee /etc/apt/sources.list.d/digitalwave-modsecurity.sources
Types: deb
URIs: http://modsecurity.digitalwave.hu/debian/
Suites: $(lsb_release -cs) $(lsb_release -cs)-backports
Components: main
Architectures: $(dpkg --print-architecture)
Signed-By: /usr/share/keyrings/digitalwave-modsecurity.gpg
EOF
For Debian 11 (Bullseye):
echo "deb [signed-by=/usr/share/keyrings/digitalwave-modsecurity.gpg] http://modsecurity.digitalwave.hu/debian/ bullseye main" | sudo tee /etc/apt/sources.list.d/digitalwave-modsecurity.list
echo "deb [signed-by=/usr/share/keyrings/digitalwave-modsecurity.gpg] http://modsecurity.digitalwave.hu/debian/ bullseye-backports main" | sudo tee -a /etc/apt/sources.list.d/digitalwave-modsecurity.list
Configure APT Package Priorities
Create a pinning configuration to ensure APT prioritizes ModSecurity packages from the Digitalwave repository over default Debian packages:
cat <<EOF | sudo tee /etc/apt/preferences.d/99modsecurity
Package: *libapache2-mod-security2*
Pin: origin modsecurity.digitalwave.hu
Pin-Priority: 900
Package: *modsecurity-crs*
Pin: origin modsecurity.digitalwave.hu
Pin-Priority: 900
Package: *libmodsecurity*
Pin: origin modsecurity.digitalwave.hu
Pin-Priority: 900
EOF
Update the package list to include the new repository:
sudo apt update
Verify the repository priority is correctly applied:
apt-cache policy libapache2-mod-security2
The output should show the Digitalwave repository with priority 900. Here is example output from Debian 12:
libapache2-mod-security2:
Installed: (none)
Candidate: 2.9.13-1~pre1+0~20250805~bpo12+18cae50
Version table:
2.9.13-1~pre1+0~20250805~bpo12+18cae50 900
900 http://modsecurity.digitalwave.hu/debian bookworm-backports/main amd64 Packages
2.9.7-1+deb12u2 500
500 http://deb.debian.org/debian bookworm/main amd64 Packages
Install the ModSecurity 2 Module
Install the ModSecurity Apache module from the Digitalwave repository:
sudo apt install libapache2-mod-security2
This package automatically enables the security2 module in Apache. Verify the module is enabled:
sudo apachectl -M | grep security
Expected output:
security2_module (shared)
If the module is not listed, enable it manually:
sudo a2enmod security2
sudo systemctl restart apache2
Configure ModSecurity 2
ModSecurity comes with a recommended configuration file that you need to activate. Copy the sample configuration to create the active configuration file:
sudo cp /etc/modsecurity/modsecurity.conf-recommended /etc/modsecurity/modsecurity.conf
Open the configuration file for editing:
sudo nano /etc/modsecurity/modsecurity.conf
Enable Active Blocking Mode
By default, ModSecurity runs in DetectionOnly mode, which logs suspicious requests but does not block them. To actively block malicious traffic, change the rule engine setting near the top of the file (line 7):
Find this line:
SecRuleEngine DetectionOnly
Change it to:
SecRuleEngine On
Running in
DetectionOnlymode for a few days before switching toOnis recommended. This allows you to review logs and identify any false positives that might block legitimate traffic before enabling active blocking.
Configure Audit Log Settings
The default SecAuditLogParts setting does not log all useful information. Search for the SecAuditLogParts directive in the configuration file (around line 224):
SecAuditLogParts ABDEFHIJZ
Change it to include request body, response body, and matched rules:
SecAuditLogParts ABCEFHJKZ
Each letter represents a section of the audit log: A (audit log header), B (request headers), C (request body), E (response body), F (final response headers), H (audit log trailer with additional data), J (upload file information), K (matched rules), and Z (final boundary). This configuration provides comprehensive logging for analyzing blocked requests and troubleshooting false positives.
Save your changes (Ctrl+O to save, Ctrl+X to exit in nano) and restart Apache to apply the configuration:
sudo systemctl restart apache2
Install OWASP Core Rule Set
ModSecurity provides the firewall engine, but it needs rules to detect attacks. The OWASP Core Rule Set (CRS) is the industry-standard rule set that protects against common web attacks including SQL injection, XSS, and remote code execution.
You have two installation options:
- Option 1: Latest CRS 4.x from GitHub – Provides the most current security rules with automatic version detection.
- Option 2: CRS from Digitalwave Repository – Uses packaged CRS version (3.3.x) with automatic system updates via APT.
Both CRS branches are actively maintained. CRS 4.x includes newer detection rules and features, while CRS 3.x receives continued security updates and may have fewer false positives on some setups.
Option 1: Install Latest OWASP CRS from GitHub
This method automatically detects and downloads the latest CRS release from the official GitHub repository, providing access to CRS 4.x with the most current security rules.
Create the CRS directory:
sudo mkdir -p /etc/apache2/modsec
Download and install the latest CRS release automatically:
CRS_VERSION=$(curl -s https://api.github.com/repos/coreruleset/coreruleset/releases/latest | grep '"tag_name"' | cut -d'"' -f4)
echo "Downloading OWASP CRS $CRS_VERSION..."
curl -fSL "https://github.com/coreruleset/coreruleset/archive/refs/tags/${CRS_VERSION}.tar.gz" -o /tmp/crs.tar.gz
sudo tar xzf /tmp/crs.tar.gz -C /etc/apache2/modsec --strip-components=1
rm /tmp/crs.tar.gz
echo "OWASP CRS $CRS_VERSION installed to /etc/apache2/modsec/"
This script uses
curl -fSLto download the archive (-ffails silently on HTTP errors,-Sshows errors,-Lfollows redirects). The--strip-components=1flag removes the top-level directory from the archive so files extract directly into/etc/apache2/modsec/.
Copy the example configuration file to create the active CRS configuration:
sudo cp /etc/apache2/modsec/crs-setup.conf.example /etc/apache2/modsec/crs-setup.conf
Copy the rule exclusion templates so you can add custom exclusions later:
sudo cp /etc/apache2/modsec/rules/REQUEST-900-EXCLUSION-RULES-BEFORE-CRS.conf.example /etc/apache2/modsec/rules/REQUEST-900-EXCLUSION-RULES-BEFORE-CRS.conf
sudo cp /etc/apache2/modsec/rules/RESPONSE-999-EXCLUSION-RULES-AFTER-CRS.conf.example /etc/apache2/modsec/rules/RESPONSE-999-EXCLUSION-RULES-AFTER-CRS.conf
Now configure Apache to load the CRS rules. Edit the ModSecurity module configuration:
sudo nano /etc/apache2/mods-available/security2.conf
Replace the entire contents with:
<IfModule security2_module>
SecDataDir /var/cache/modsecurity
# Include ModSecurity base configuration
IncludeOptional /etc/modsecurity/*.conf
# Include OWASP CRS configuration and rules
Include /etc/apache2/modsec/crs-setup.conf
Include /etc/apache2/modsec/rules/*.conf
</IfModule>
Test the Apache configuration for syntax errors:
sudo apachectl configtest
Expected output:
Syntax OK
Restart Apache to load the rules:
sudo systemctl restart apache2
Option 2: Install CRS from Digitalwave Repository
This method uses the CRS version packaged in the Digitalwave repository. It provides automatic updates through APT and is simpler to maintain:
sudo apt install modsecurity-crs
This package automatically integrates with ModSecurity through the /usr/share/modsecurity-crs/*.load files that are already referenced in the default security2.conf.
Restart Apache to load the rules:
sudo systemctl restart apache2
Verify the CRS package is installed:
dpkg -l modsecurity-crs | tail -1
Expected output (version may vary):
ii modsecurity-crs 3.3.8-1~bpo12+1.1 all OWASP ModSecurity Core Rule Set
Verify ModSecurity Is Working
Test that ModSecurity correctly blocks malicious requests by sending a request that triggers a rule. Run:
curl -I "http://localhost/?exec=/bin/bash"
If ModSecurity is working correctly with the CRS, you should receive a 403 Forbidden response:
HTTP/1.1 403 Forbidden Date: Mon, 27 Jan 2026 10:30:00 GMT Server: Apache/2.4.65 (Debian) Content-Type: text/html; charset=iso-8859-1
If you receive a 200 OK response instead, verify that:
SecRuleEngineis set toOn(notDetectionOnly) in/etc/modsecurity/modsecurity.conf- The CRS rules are properly included in
security2.conf - Apache was restarted after configuration changes
You can also test via your web browser by visiting https://yourdomain.com/?exec=/bin/bash (replace with your actual domain). A 403 Forbidden error page confirms ModSecurity is actively protecting your server.
Understanding OWASP CRS Configuration
The CRS provides extensive configuration options in the crs-setup.conf file. This section covers the most important settings you should understand.
Open the CRS configuration file (path depends on which installation method you used):
# For GitHub installation (Option 1):
sudo nano /etc/apache2/modsec/crs-setup.conf
# For Digitalwave package (Option 2):
sudo nano /usr/share/modsecurity-crs/crs-setup.conf
Anomaly Scoring Mode
CRS operates in anomaly scoring mode by default. Instead of blocking on the first matched rule, each rule adds points to an “anomaly score.” When the score exceeds a threshold, the request is blocked. This approach reduces false positives by requiring multiple indicators before blocking.
The default thresholds are:
- Inbound anomaly threshold: 5 points (blocks requests with 5+ points)
- Outbound anomaly threshold: 4 points (blocks responses with 4+ points)
Higher thresholds allow more potentially suspicious traffic through but reduce false positives.
Paranoia Levels
CRS has four paranoia levels that control how aggressively rules detect threats:
- Paranoia Level 1 (default): Core rules with minimal false positives. Suitable for most websites.
- Paranoia Level 2: Enables additional rules. May require tuning for some applications.
- Paranoia Level 3: Strict rules intended for high-security applications. Expect false positives.
- Paranoia Level 4: Maximum security, high false positive rate. Only for specialized use cases.
Start with level 1 and only increase after monitoring your logs for a few weeks to understand your traffic patterns.
Handling False Positives
False positives (legitimate requests blocked incorrectly) are common when first deploying a WAF. The CRS provides several methods to handle them.
Enable Application-Specific Exclusions
CRS includes pre-built exclusions for popular applications. In crs-setup.conf, find and uncomment the exclusions for applications you use:
SecAction \
"id:900130,\
phase:1,\
nolog,\
pass,\
t:none,\
setvar:tx.crs_exclusions_wordpress=1,\
setvar:tx.crs_exclusions_phpmyadmin=1"
Available exclusions include: cpanel, dokuwiki, drupal, nextcloud, phpbb, phpmyadmin, wordpress, and xenforo.
Create Custom Rule Exclusions
For custom exclusions beyond the built-in profiles, edit the before-CRS exclusion file. If you used the GitHub installation method:
sudo nano /etc/apache2/modsec/rules/REQUEST-900-EXCLUSION-RULES-BEFORE-CRS.conf
Each exclusion rule needs a unique ID. Here are common exclusion patterns:
Disable rules for a specific URL path:
SecRule REQUEST_URI "@beginsWith /admin/api/" "id:1000,phase:1,pass,nolog,ctl:ruleRemoveById=941000-942999"
Whitelist an IP address from all rules:
SecRule REMOTE_ADDR "@ipMatch 192.168.1.100" "id:1001,phase:1,allow,nolog,ctl:ruleEngine=off"
Whitelist a subnet:
SecRule REMOTE_ADDR "@ipMatch 10.0.0.0/8" "id:1002,phase:1,allow,nolog,ctl:ruleEngine=off"
Disable a specific rule globally:
SecRuleRemoveById 942100
These exclusion rules integrate well with Fail2Ban for creating dynamic block lists based on repeated malicious behavior.
Monitor Logs for False Positives
To identify which rule is causing false positives, check the ModSecurity audit log:
sudo tail -f /var/log/apache2/modsec_audit.log
Look for lines containing the rule ID (e.g., [id "942100"]) that triggered the block. Create targeted exclusions for those specific scenarios rather than disabling rules globally.
After any exclusion changes, restart Apache to apply them:
sudo systemctl restart apache2
Configure Log Rotation
ModSecurity logs can grow large quickly on busy servers. Configure log rotation to manage disk space:
sudo nano /etc/logrotate.d/modsecurity
Add the following configuration:
/var/log/apache2/modsec_audit.log {
rotate 14
daily
missingok
compress
delaycompress
notifempty
create 640 root adm
}
This configuration keeps 14 days of logs, compresses old logs, and creates new log files with appropriate permissions.
Update OWASP CRS
If you installed CRS from GitHub (Option 1), periodically update to get new rules and security fixes. The following script automates this process:
sudo nano /usr/local/bin/update-owasp-crs.sh
Add the following content:
#!/bin/bash
# OWASP CRS Update Script for ModSecurity on Apache
set -e
CRS_DIR="/etc/apache2/modsec"
BACKUP_DIR="/etc/apache2/modsec-backup-$(date +%Y%m%d)"
# Get current and latest versions
CURRENT_VERSION=""
if [ -f "$CRS_DIR/CHANGES.md" ]; then
CURRENT_VERSION=$(grep -m1 "^## Version" "$CRS_DIR/CHANGES.md" | awk '{print $3}')
fi
LATEST_VERSION=$(curl -s https://api.github.com/repos/coreruleset/coreruleset/releases/latest | grep '"tag_name"' | cut -d'"' -f4)
echo "Current CRS version: ${CURRENT_VERSION:-Not installed}"
echo "Latest CRS version: $LATEST_VERSION"
if [ "$CURRENT_VERSION" = "${LATEST_VERSION#v}" ]; then
echo "CRS is already up to date."
exit 0
fi
echo "Updating to $LATEST_VERSION..."
# Backup current installation
if [ -d "$CRS_DIR" ]; then
echo "Backing up current installation to $BACKUP_DIR"
sudo cp -r "$CRS_DIR" "$BACKUP_DIR"
fi
# Preserve custom configuration
CUSTOM_SETUP=""
if [ -f "$CRS_DIR/crs-setup.conf" ]; then
CUSTOM_SETUP=$(cat "$CRS_DIR/crs-setup.conf")
fi
# Download and extract new version
curl -fSL "https://github.com/coreruleset/coreruleset/archive/refs/tags/${LATEST_VERSION}.tar.gz" -o /tmp/crs.tar.gz
sudo rm -rf "$CRS_DIR"/*
sudo tar xzf /tmp/crs.tar.gz -C "$CRS_DIR" --strip-components=1
rm /tmp/crs.tar.gz
# Restore or create configuration
if [ -n "$CUSTOM_SETUP" ]; then
echo "$CUSTOM_SETUP" | sudo tee "$CRS_DIR/crs-setup.conf" > /dev/null
echo "Restored custom crs-setup.conf"
else
sudo cp "$CRS_DIR/crs-setup.conf.example" "$CRS_DIR/crs-setup.conf"
fi
# Setup exclusion files
sudo cp "$CRS_DIR/rules/REQUEST-900-EXCLUSION-RULES-BEFORE-CRS.conf.example" "$CRS_DIR/rules/REQUEST-900-EXCLUSION-RULES-BEFORE-CRS.conf" 2>/dev/null || true
sudo cp "$CRS_DIR/rules/RESPONSE-999-EXCLUSION-RULES-AFTER-CRS.conf.example" "$CRS_DIR/rules/RESPONSE-999-EXCLUSION-RULES-AFTER-CRS.conf" 2>/dev/null || true
# Test Apache configuration
echo "Testing Apache configuration..."
if sudo apachectl configtest; then
echo "Configuration test passed. Reloading Apache..."
sudo systemctl reload apache2
echo "CRS updated successfully to $LATEST_VERSION"
else
echo "Configuration test failed! Restoring backup..."
sudo rm -rf "$CRS_DIR"/*
sudo cp -r "$BACKUP_DIR"/* "$CRS_DIR"/
echo "Backup restored. Please check the error above."
exit 1
fi
Make the script executable and run it:
sudo chmod +x /usr/local/bin/update-owasp-crs.sh
sudo /usr/local/bin/update-owasp-crs.sh
Expected output when an update is available:
Current CRS version: 4.21.0 Latest CRS version: v4.22.0 Updating to v4.22.0... Backing up current installation to /etc/apache2/modsec-backup-20260127 Configuration test passed. Reloading Apache... CRS updated successfully to v4.22.0
Run this script periodically (monthly is usually sufficient) to keep your rules current with the latest security updates.
Troubleshooting
Apache Fails to Start After Configuration Changes
If Apache fails to start, check the configuration syntax:
sudo apachectl configtest
Common errors include:
- Duplicate rule IDs: If you see “Found another rule with the same id”, you may have both GitHub CRS and packaged CRS configured. Use only one installation method.
- Missing include files: Verify the paths in
security2.confpoint to existing files - Syntax errors in exclusion rules: Each custom rule needs a unique
id:value
ModSecurity Not Blocking Attacks
If ModSecurity doesn’t block test attacks:
- Verify
SecRuleEngineis set toOnin/etc/modsecurity/modsecurity.conf - Check that CRS rules are being loaded:
sudo apachectl -t -D DUMP_INCLUDES | grep -i modsec - Review the error log for issues:
sudo tail /var/log/apache2/error.log - Ensure Apache was restarted after changes:
sudo systemctl restart apache2
Legitimate Requests Being Blocked
To identify why a request was blocked:
sudo grep "$(date +%d/%b/%Y)" /var/log/apache2/modsec_audit.log | grep -A 10 "403"
Look for the [id "XXXXXX"] pattern to identify the blocking rule, then create an exclusion as described in the false positives section.
High Server Load After Enabling ModSecurity
ModSecurity adds processing overhead. If you notice performance issues:
- Reduce the paranoia level to 1 if it’s higher
- Disable response body scanning in
modsecurity.conf:SecResponseBodyAccess Off - Limit which file types are scanned by configuring
SecRequestBodyLimitandSecResponseBodyLimit
Remove ModSecurity
If you need to remove ModSecurity from your system, follow these steps.
First, disable the ModSecurity module:
sudo a2dismod security2
sudo systemctl restart apache2
Remove the ModSecurity packages:
sudo apt remove --purge libapache2-mod-security2 modsecurity-crs
Remove configuration files and CRS rules:
The following commands permanently delete all ModSecurity configuration files and OWASP CRS rules. If you have custom rule exclusions you want to preserve, back them up first with
sudo cp -r /etc/apache2/modsec ~/modsec-backup.
sudo rm -rf /etc/modsecurity
sudo rm -rf /etc/apache2/modsec
sudo rm -f /etc/logrotate.d/modsecurity
sudo rm -f /usr/local/bin/update-owasp-crs.sh
sudo rm -f /etc/apt/sources.list.d/digitalwave-modsecurity.*
sudo rm -f /etc/apt/preferences.d/99modsecurity
sudo rm -f /usr/share/keyrings/digitalwave-modsecurity.gpg
Clean up unused dependencies:
sudo apt autoremove
Verify ModSecurity is removed:
sudo apachectl -M | grep security
This command should return no output if ModSecurity was successfully removed.
Frequently Asked Questions
Debian’s default repositories include ModSecurity, but the versions lag behind upstream releases. The Digitalwave repository provides backported packages with newer features and security fixes. For Debian 11, Digitalwave is essential because the default ModSecurity 2.9.3 doesn’t support CRS 4.x rules (which require the @within operator from 2.9.6+). Debian 12 and 13 users can use default repos but benefit from Digitalwave’s more current packages.
Both are valid choices. CRS 4.x from GitHub provides the latest detection rules and features, while CRS 3.x from Digitalwave offers automatic updates through APT with potentially fewer false positives on some setups. Both branches receive security updates. Choose GitHub if you want the newest rules and don’t mind manual updates; choose Digitalwave if you prefer automated package management.
Yes. Running SecRuleEngine DetectionOnly logs suspicious requests without blocking them. This is useful for monitoring traffic patterns, testing new rule sets, or auditing application behavior without risking false positives affecting users. Many administrators run detection-only mode during initial deployment to tune exclusions before switching to active blocking.
Conclusion
You now have ModSecurity with OWASP CRS protecting your Apache server on Debian. The web application firewall monitors all HTTP traffic and blocks common attack patterns including SQL injection, XSS, and command injection attempts. Regularly update your CRS rules using the provided update script (or through APT if you chose Option 2), monitor your audit logs for false positives, and tune the rule exclusions to match your application’s needs.
Not a bad article, everything is explained in detail, but some parts feel overly drawn out. I liked that it covers not only the installation of ModSecurity but also the setup of the OWASP Core Rule Set. This is useful for those just starting to explore web security. The section on paranoia levels and handling false positives stood out—it clearly highlights what to focus on to ensure stable system performance. One thing I wish it had is more practical examples for setting up exclusions for popular applications like WordPress or phpMyAdmin. Overall, it’s a solid resource, especially for beginners.
Thanks Alex, I could not cover too much more into OWASP core rule set as the needs a separate beginners guide and probably advanced guides, so many possibilities with the core rule set to customize, whitelist, create custom options and much more. The guide was just focused on installing it and doing a brief run through as you mentioned.
Appreciate the feedback.