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

Intermediate 45 min Apr 08, 2026 1,360 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

Nie chcesz zarządzać tym samodzielnie?

Zarządzamy infrastrukturą firm, które zależą od dostępności. W pełni zarządzana, z jednym stałym kontaktem, który zna Twoje środowisko.

Macie jednego stałego opiekuna, który zna Waszą konfigurację

Rotterdam 05:53 · dostępny w wiadomości, bez formularza zgłoszeń