Implement Apache web application firewall with ModSecurity 3 and OWASP Core Rule Set

Intermediate 45 min Apr 08, 2026 1,356 views
Ubuntu 24.04 Debian 12 AlmaLinux 9 Rocky Linux 9

Deploy ModSecurity 3 as an Apache module with OWASP Core Rule Set to protect web applications from common attacks. Configure real-time logging, custom rules, and automated threat detection for production environments.

Prerequisites

  • Apache HTTP server
  • Root or sudo access
  • Basic knowledge of web server configuration

What this solves

ModSecurity 3 provides web application firewall (WAF) capabilities that protect your applications from SQL injection, cross-site scripting (XSS), and other OWASP Top 10 vulnerabilities. This tutorial sets up ModSecurity with Apache and deploys the OWASP Core Rule Set for comprehensive threat detection and blocking.

Step-by-step installation

Update system packages

Start by updating your package manager to ensure you have the latest security updates.

sudo apt update && sudo apt upgrade -y
sudo dnf update -y

Install Apache and development tools

Install Apache web server and the development tools required for building ModSecurity.

sudo apt install -y apache2 apache2-dev build-essential git libcurl4-openssl-dev libgeoip-dev libyajl-dev libpcre2-dev libxml2-dev pkgconf libmaxminddb-dev
sudo dnf install -y httpd httpd-devel gcc gcc-c++ git libcurl-devel GeoIP-devel yajl-devel pcre2-devel libxml2-devel pkgconfig libmaxminddb-devel

Download and compile libmodsecurity

Clone the ModSecurity v3 library source code and compile it with optimizations for production use.

cd /opt
sudo git clone --depth 1 -b v3/master --single-branch https://github.com/SpiderLabs/ModSecurity.git
cd ModSecurity
sudo git submodule init
sudo git submodule update
sudo ./build.sh
sudo ./configure --with-yajl --with-geoip --with-pcre2
sudo make -j$(nproc)
sudo make install

Download and compile ModSecurity Apache connector

Install the connector that integrates ModSecurity library with Apache HTTP server.

cd /opt
sudo git clone --depth 1 https://github.com/SpiderLabs/ModSecurity-apache.git
cd ModSecurity-apache
sudo ./configure --with-libmodsecurity=/usr/local/modsecurity/
sudo make -j$(nproc)
sudo make install

Configure dynamic library loading

Update the library path so Apache can find the ModSecurity libraries at runtime.

echo '/usr/local/modsecurity/lib' | sudo tee /etc/ld.so.conf.d/modsecurity.conf
sudo ldconfig

Load ModSecurity Apache module

Enable the ModSecurity module in Apache configuration.

echo 'LoadModule security3_module /usr/lib/apache2/modules/mod_security3.so' | sudo tee /etc/apache2/mods-available/security3.load
sudo a2enmod security3
echo 'LoadModule security3_module modules/mod_security3.so' | sudo tee /etc/httpd/conf.modules.d/00-security3.conf

Create ModSecurity configuration directory

Set up the directory structure for ModSecurity configuration files with proper permissions.

sudo mkdir -p /etc/apache2/modsecurity.d
sudo chown root:root /etc/apache2/modsecurity.d
sudo chmod 755 /etc/apache2/modsecurity.d
sudo mkdir -p /etc/httpd/modsecurity.d
sudo chown root:root /etc/httpd/modsecurity.d
sudo chmod 755 /etc/httpd/modsecurity.d

Download OWASP Core Rule Set

Clone the latest OWASP Core Rule Set which contains rules for detecting common web application attacks.

cd /etc/apache2/modsecurity.d
sudo git clone https://github.com/coreruleset/coreruleset.git
sudo mv coreruleset/crs-setup.conf.example coreruleset/crs-setup.conf
cd /etc/httpd/modsecurity.d
sudo git clone https://github.com/coreruleset/coreruleset.git
sudo mv coreruleset/crs-setup.conf.example coreruleset/crs-setup.conf

Create main ModSecurity configuration

Configure ModSecurity with logging, rule processing, and security settings optimized for production.

# ModSecurity v3 Configuration
SecRuleEngine On
SecRequestBodyAccess On
SecRule REQUEST_HEADERS:Content-Type "^(?:application(?:/soap\+|/)|text/)xml" \n     "id:'200000',phase:1,t:none,t:lowercase,pass,nolog,ctl:requestBodyProcessor=XML"
SecRule REQUEST_HEADERS:Content-Type "^application/json" \n     "id:'200001',phase:1,t:none,t:lowercase,pass,nolog,ctl:requestBodyProcessor=JSON"
SecRequestBodyLimit 13107200
SecRequestBodyNoFilesLimit 131072
SecRequestBodyInMemoryLimit 131072
SecRequestBodyLimitAction Reject
SecRule REQBODY_ERROR "!@eq 0" \n"id:200002, phase:2,t:none,log,deny,status:400,msg:'Failed to parse request body.',logdata:'Error %{REQBODY_ERROR_MSG}',severity:2"
SecRule MULTIPART_STRICT_ERROR "!@eq 0" \n"id:200003,phase:2,t:none,log,deny,status:400, \nmsg:'Multipart request body failed strict validation: \nPE %{REQBODY_PROCESSOR_ERROR}, \nBQ %{MULTIPART_BOUNDARY_QUOTED}, \nBW %{MULTIPART_BOUNDARY_WHITESPACE}, \nDB %{MULTIPART_DATA_BEFORE}, \nDA %{MULTIPART_DATA_AFTER}, \nHF %{MULTIPART_HEADER_FOLDING}, \nLF %{MULTIPART_LF_LINE}, \nSM %{MULTIPART_MISSING_SEMICOLON}, \nIQ %{MULTIPART_INVALID_QUOTING}, \nIP %{MULTIPART_INVALID_PART}, \nIH %{MULTIPART_INVALID_HEADER_FOLDING}, \nFL %{MULTIPART_FILE_LIMIT_EXCEEDED}'"
SecRule MULTIPART_UNMATCHED_BOUNDARY "!@eq 0" \n"id:200004,phase:2,t:none,log,deny,status:44,msg:'Multipart parser detected a possible unmatched boundary.'"
SecPcreMatchLimit 1000
SecPcreMatchLimitRecursion 1000
SecResponseBodyAccess On
SecResponseBodyMimeType text/plain text/html text/xml
SecResponseBodyLimit 524288
SecResponseBodyLimitAction ProcessPartial
SecTmpDir /tmp/
SecDataDir /tmp/
SecAuditEngine RelevantOnly
SecAuditLogRelevantStatus "^(?:5|4(?!04))"
SecAuditLogParts ABDEFHIJZ
SecAuditLogType Serial
SecAuditLog /var/log/apache2/modsec_audit.log
SecArgumentSeparator &
SecCookieFormat 0
SecUnicodeMapFile unicode.mapping 20127
SecStatusEngine On
# ModSecurity v3 Configuration
SecRuleEngine On
SecRequestBodyAccess On
SecRule REQUEST_HEADERS:Content-Type "^(?:application(?:/soap\+|/)|text/)xml" \n     "id:'200000',phase:1,t:none,t:lowercase,pass,nolog,ctl:requestBodyProcessor=XML"
SecRule REQUEST_HEADERS:Content-Type "^application/json" \n     "id:'200001',phase:1,t:none,t:lowercase,pass,nolog,ctl:requestBodyProcessor=JSON"
SecRequestBodyLimit 13107200
SecRequestBodyNoFilesLimit 131072
SecRequestBodyInMemoryLimit 131072
SecRequestBodyLimitAction Reject
SecRule REQBODY_ERROR "!@eq 0" \n"id:200002, phase:2,t:none,log,deny,status:400,msg:'Failed to parse request body.',logdata:'Error %{REQBODY_ERROR_MSG}',severity:2"
SecRule MULTIPART_STRICT_ERROR "!@eq 0" \n"id:200003,phase:2,t:none,log,deny,status:400, \nmsg:'Multipart request body failed strict validation: \nPE %{REQBODY_PROCESSOR_ERROR}, \nBQ %{MULTIPART_BOUNDARY_QUOTED}, \nBW %{MULTIPART_BOUNDARY_WHITESPACE}, \nDB %{MULTIPART_DATA_BEFORE}, \nDA %{MULTIPART_DATA_AFTER}, \nHF %{MULTIPART_HEADER_FOLDING}, \nLF %{MULTIPART_LF_LINE}, \nSM %{MULTIPART_MISSING_SEMICOLON}, \nIQ %{MULTIPART_INVALID_QUOTING}, \nIP %{MULTIPART_INVALID_PART}, \nIH %{MULTIPART_INVALID_HEADER_FOLDING}, \nFL %{MULTIPART_FILE_LIMIT_EXCEEDED}'"
SecRule MULTIPART_UNMATCHED_BOUNDARY "!@eq 0" \n"id:200004,phase:2,t:none,log,deny,status:44,msg:'Multipart parser detected a possible unmatched boundary.'"
SecPcreMatchLimit 1000
SecPcreMatchLimitRecursion 1000
SecResponseBodyAccess On
SecResponseBodyMimeType text/plain text/html text/xml
SecResponseBodyLimit 524288
SecResponseBodyLimitAction ProcessPartial
SecTmpDir /tmp/
SecDataDir /tmp/
SecAuditEngine RelevantOnly
SecAuditLogRelevantStatus "^(?:5|4(?!04))"
SecAuditLogParts ABDEFHIJZ
SecAuditLogType Serial
SecAuditLog /var/log/httpd/modsec_audit.log
SecArgumentSeparator &
SecCookieFormat 0
SecUnicodeMapFile unicode.mapping 20127
SecStatusEngine On

Create custom rules file

Set up a custom rules file for application-specific security rules and exceptions.

# Custom ModSecurity Rules
# Block requests with suspicious user agents
SecRule REQUEST_HEADERS:User-Agent "@contains sqlmap" \n    "id:1001,phase:1,block,msg:'SQL injection tool detected',logdata:'Matched Data: %{MATCHED_VAR} found within %{MATCHED_VAR_NAME}',severity:2,tag:'attack-sqli',tag:'OWASP_CRS'"

# Rate limiting rule - max 100 requests per minute per IP
SecAction "id:1002,phase:1,initcol:ip=%{REMOTE_ADDR},pass,nolog"
SecRule IP:REQUEST_COUNT "@gt 100" \n    "id:1003,phase:1,deny,status:429,msg:'Rate limit exceeded',logdata:'IP: %{REMOTE_ADDR}',setvar:ip.request_count=+1"
SecAction "id:1004,phase:5,pass,nolog,setvar:ip.request_count=+1,expirevar:ip.request_count=60"

# Block known bad IPs (example - customize for your needs)
SecRule REMOTE_ADDR "@ipMatch 198.51.100.0/24" \n    "id:1005,phase:1,deny,status:403,msg:'Blocked IP range',logdata:'IP: %{REMOTE_ADDR}'"

# Enhanced logging for security events
SecRule TX:ANOMALY_SCORE "@gt 0" \n    "id:1006,phase:5,pass,msg:'Inbound Anomaly Score Exceeded',logdata:'Total Score: %{TX.ANOMALY_SCORE}'"

# Whitelist legitimate traffic patterns (customize as needed)
SecRule REQUEST_URI "@beginsWith /api/health" \n    "id:1007,phase:1,pass,ctl:ruleRemoveById=920350,msg:'Allow health check endpoint'"

# Block file upload to sensitive directories
SecRule FILES_NAMES "@rx \.(php|jsp|asp|aspx|sh|py|pl|rb)$" \n    "id:1008,phase:2,deny,status:403,msg:'Executable file upload attempt',logdata:'File: %{MATCHED_VAR}'"

# Geographic IP filtering (requires GeoIP database)
# SecRule REMOTE_ADDR "@geoLookup" "id:1009,phase:1,pass,nolog,setvar:tx.country_code=%{GEO.COUNTRY_CODE}"
# SecRule TX:COUNTRY_CODE "@in CN RU" "id:1010,phase:1,deny,status:403,msg:'Blocked country'"
# Custom ModSecurity Rules
# Block requests with suspicious user agents
SecRule REQUEST_HEADERS:User-Agent "@contains sqlmap" \n    "id:1001,phase:1,block,msg:'SQL injection tool detected',logdata:'Matched Data: %{MATCHED_VAR} found within %{MATCHED_VAR_NAME}',severity:2,tag:'attack-sqli',tag:'OWASP_CRS'"

# Rate limiting rule - max 100 requests per minute per IP
SecAction "id:1002,phase:1,initcol:ip=%{REMOTE_ADDR},pass,nolog"
SecRule IP:REQUEST_COUNT "@gt 100" \n    "id:1003,phase:1,deny,status:429,msg:'Rate limit exceeded',logdata:'IP: %{REMOTE_ADDR}',setvar:ip.request_count=+1"
SecAction "id:1004,phase:5,pass,nolog,setvar:ip.request_count=+1,expirevar:ip.request_count=60"

# Block known bad IPs (example - customize for your needs)
SecRule REMOTE_ADDR "@ipMatch 198.51.100.0/24" \n    "id:1005,phase:1,deny,status:403,msg:'Blocked IP range',logdata:'IP: %{REMOTE_ADDR}'"

# Enhanced logging for security events
SecRule TX:ANOMALY_SCORE "@gt 0" \n    "id:1006,phase:5,pass,msg:'Inbound Anomaly Score Exceeded',logdata:'Total Score: %{TX.ANOMALY_SCORE}'"

# Whitelist legitimate traffic patterns (customize as needed)
SecRule REQUEST_URI "@beginsWith /api/health" \n    "id:1007,phase:1,pass,ctl:ruleRemoveById=920350,msg:'Allow health check endpoint'"

# Block file upload to sensitive directories
SecRule FILES_NAMES "@rx \.(php|jsp|asp|aspx|sh|py|pl|rb)$" \n    "id:1008,phase:2,deny,status:403,msg:'Executable file upload attempt',logdata:'File: %{MATCHED_VAR}'"

# Geographic IP filtering (requires GeoIP database)
# SecRule REMOTE_ADDR "@geoLookup" "id:1009,phase:1,pass,nolog,setvar:tx.country_code=%{GEO.COUNTRY_CODE}"
# SecRule TX:COUNTRY_CODE "@in CN RU" "id:1010,phase:1,deny,status:403,msg:'Blocked country'"

Configure Apache virtual host with ModSecurity

Enable ModSecurity for your website by adding directives to Apache virtual host configuration.

<VirtualHost *:80>
    ServerAdmin webmaster@example.com
    DocumentRoot /var/www/html
    ServerName example.com

    # ModSecurity Configuration
    SecRuleEngine On
    modsecurity_rules_file /etc/apache2/modsecurity.d/modsecurity.conf
    modsecurity_rules_file /etc/apache2/modsecurity.d/coreruleset/crs-setup.conf
    modsecurity_rules_file /etc/apache2/modsecurity.d/coreruleset/rules/*.conf
    modsecurity_rules_file /etc/apache2/modsecurity.d/custom-rules.conf

    # Security headers
    Header always set X-Frame-Options DENY
    Header always set X-Content-Type-Options nosniff
    Header always set X-XSS-Protection "1; mode=block"
    Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains"
    Header always set Referrer-Policy "strict-origin-when-cross-origin"
    Header always set Content-Security-Policy "default-src 'self'"

    ErrorLog ${APACHE_LOG_DIR}/error.log
    CustomLog ${APACHE_LOG_DIR}/access.log combined
</VirtualHost>
<VirtualHost *:80>
    ServerAdmin webmaster@example.com
    DocumentRoot /var/www/html
    ServerName example.com

    # ModSecurity Configuration
    SecRuleEngine On
    modsecurity_rules_file /etc/httpd/modsecurity.d/modsecurity.conf
    modsecurity_rules_file /etc/httpd/modsecurity.d/coreruleset/crs-setup.conf
    modsecurity_rules_file /etc/httpd/modsecurity.d/coreruleset/rules/*.conf
    modsecurity_rules_file /etc/httpd/modsecurity.d/custom-rules.conf

    # Security headers
    Header always set X-Frame-Options DENY
    Header always set X-Content-Type-Options nosniff
    Header always set X-XSS-Protection "1; mode=block"
    Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains"
    Header always set Referrer-Policy "strict-origin-when-cross-origin"
    Header always set Content-Security-Policy "default-src 'self'"

    ErrorLog /var/log/httpd/error.log
    CustomLog /var/log/httpd/access.log combined
</VirtualHost>

Enable required Apache modules

Enable Apache modules needed for security headers and ModSecurity functionality.

sudo a2enmod headers
sudo a2enmod rewrite
echo 'LoadModule headers_module modules/mod_headers.so' | sudo tee -a /etc/httpd/conf.modules.d/00-base.conf
echo 'LoadModule rewrite_module modules/mod_rewrite.so' | sudo tee -a /etc/httpd/conf.modules.d/00-base.conf

Set up log rotation for ModSecurity logs

Configure logrotate to manage ModSecurity audit logs and prevent disk space issues.

/var/log/apache2/modsec_audit.log {
    daily
    rotate 14
    compress
    delaycompress
    missingok
    notifempty
    create 640 www-data adm
    postrotate
        /bin/systemctl reload apache2 > /dev/null 2>&1 || true
    endscript
}
/var/log/httpd/modsec_audit.log {
    daily
    rotate 14
    compress
    delaycompress
    missingok
    notifempty
    create 640 apache apache
    postrotate
        /bin/systemctl reload httpd > /dev/null 2>&1 || true
    endscript
}

Create ModSecurity monitoring script

Set up a monitoring script that checks for blocked attacks and sends alerts.

#!/bin/bash
# ModSecurity Monitoring Script

LOG_FILE="/var/log/apache2/modsec_audit.log"
if [ -f "/var/log/httpd/modsec_audit.log" ]; then
    LOG_FILE="/var/log/httpd/modsec_audit.log"
fi

ALERT_EMAIL="admin@example.com"
THRESHOLD=10
TIME_WINDOW=5

# Get current timestamp
CURRENT_TIME=$(date +%s)
START_TIME=$((CURRENT_TIME - (TIME_WINDOW * 60)))

# Count blocked requests in the last 5 minutes
BLOCKED_COUNT=$(awk -v start="$START_TIME" '
    /^--[a-f0-9]+-A--/ { 
        getline; 
        date_line = $0; 
        if (match(date_line, /\[([0-9]{2}\/[A-Za-z]{3}\/[0-9]{4}:[0-9]{2}:[0-9]{2}:[0-9]{2})/, arr))

Automated install script

Run this to automate the entire setup

¿Prefiere no gestionarlo usted mismo?

Gestionamos la infraestructura de empresas que dependen del tiempo de actividad. Totalmente gestionada, con un contacto fijo que conoce su entorno.

Tiene un contacto fijo que conoce su entorno

Róterdam 05:53 · accesible por mensaje, sin formulario de tickets