Implement HAProxy WAF integration with ModSecurity 3 for advanced threat protection

Advanced 45 min Apr 14, 2026 22 views
Ubuntu 24.04 Debian 12 AlmaLinux 9 Rocky Linux 9

Configure HAProxy with ModSecurity 3 using SPOE protocol to create a high-performance web application firewall that protects against OWASP Top 10 threats while maintaining load balancing capabilities.

Prerequisites

  • Root access to server
  • HAProxy already installed
  • Basic understanding of load balancing
  • Familiarity with web application security

What this solves

Modern web applications require both load balancing and advanced threat protection. This tutorial shows you how to integrate ModSecurity 3 with HAProxy using the Stream Processing Offload Engine (SPOE) protocol to create a comprehensive WAF solution that filters malicious traffic before it reaches your backend servers.

Step-by-step configuration

Update system packages

Start by updating your package manager and installing required dependencies for compilation and security tools.

sudo apt update && sudo apt upgrade -y
sudo apt install -y build-essential git cmake libcurl4-openssl-dev libgeoip-dev libyajl-dev libxml2-dev libpcre2-dev libssl-dev zlib1g-dev pkg-config libmodsecurity3 libmodsecurity-dev haproxy
sudo dnf update -y
sudo dnf groupinstall -y "Development Tools"
sudo dnf install -y git cmake libcurl-devel GeoIP-devel yajl-devel libxml2-devel pcre2-devel openssl-devel zlib-devel pkgconfig libmodsecurity3 libmodsecurity-devel haproxy

Install ModSecurity 3 SPOE agent

Clone and compile the ModSecurity SPOE agent that will handle WAF processing for HAProxy requests.

cd /opt
sudo git clone https://github.com/haproxy/spoe-modsecurity.git
cd spoe-modsecurity
sudo mkdir build && cd build
sudo cmake ..
sudo make -j$(nproc)
sudo make install

Download OWASP Core Rule Set

Install the OWASP Core Rule Set which provides protection against the OWASP Top 10 security risks and other common attack vectors.

sudo mkdir -p /etc/modsecurity
cd /etc/modsecurity
sudo git clone https://github.com/coreruleset/coreruleset.git
sudo cp coreruleset/crs-setup.conf.example coreruleset/crs-setup.conf

Configure ModSecurity main configuration

Create the main ModSecurity configuration file with recommended security settings and logging options.

SecRuleEngine On
SecRequestBodyAccess On
SecRequestBodyLimit 13107200
SecRequestBodyNoFilesLimit 131072
SecRequestBodyLimitAction Reject
SecRequestBodyInMemoryLimit 131072
SecResponseBodyAccess On
SecResponseBodyMimeType text/plain text/html text/xml
SecResponseBodyLimit 524288
SecResponseBodyLimitAction ProcessPartial
SecTmpDir /tmp/
SecDataDir /tmp/
SecAuditEngine RelevantOnly
SecAuditLogRelevantStatus "^(?:5|4(?!04))"
SecAuditLogParts ABIJDEFHZ
SecAuditLogType Serial
SecAuditLog /var/log/modsecurity/audit.log
SecArgumentSeparator &
SecCookieFormat 0
SecUnicodeMapFile unicode.mapping 20127
SecStatusEngine On

Create OWASP CRS configuration

Configure the Core Rule Set with custom paranoia level and exclusions for your application.

SecDefaultAction "phase:1,log,auditlog,pass"
SecDefaultAction "phase:2,log,auditlog,pass"

Set paranoia level (1-4, higher = more strict)

SecAction "id:900000,phase:1,nolog,pass,t:none,setvar:tx.paranoia_level=2"

Inbound and Outbound Anomaly Score Threshold

SecAction "id:900110,phase:1,nolog,pass,t:none,setvar:tx.inbound_anomaly_score_threshold=5" SecAction "id:900111,phase:1,nolog,pass,t:none,setvar:tx.outbound_anomaly_score_threshold=4"

Application specific exclusions

Exclude SQL keywords in legitimate application data

SecRuleRemoveById 942100

Create ModSecurity rules include file

Create a master rules file that includes all OWASP CRS rules and custom configurations.

Include /etc/modsecurity/modsecurity.conf
Include /etc/modsecurity/coreruleset/crs-setup.conf
Include /etc/modsecurity/coreruleset/rules/*.conf

Configure SPOE agent configuration

Create the SPOE agent configuration that defines how ModSecurity processes requests from HAProxy.

[modsecurity]

spoe-agent modsecurity-agent
    messages check-request
    option var-prefix modsec
    timeout hello      10s
    timeout idle       30s
    timeout processing 15s
    use-backend spoe-modsecurity

spoe-message check-request
    args unique-id method path query body
    event on-frontend-http-request

backend spoe-modsecurity
    mode tcp
    timeout connect 5s
    timeout server 30s
    server modsec-spoe1 127.0.0.1:12345

Configure HAProxy with SPOE integration

Update HAProxy configuration to load the SPOE filter and process requests through ModSecurity before forwarding to backends.

global
    daemon
    chroot /var/lib/haproxy
    stats socket /run/haproxy/admin.sock mode 660 level admin
    stats timeout 30s
    user haproxy
    group haproxy
    
    # SSL Configuration
    ssl-default-bind-ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256
    ssl-default-bind-options ssl-min-ver TLSv1.2 no-tls-tickets

defaults
    mode http
    timeout connect 5000ms
    timeout client 50000ms
    timeout server 50000ms
    option httplog
    option dontlognull
    
frontend web_frontend
    bind *:80
    bind *:443 ssl crt /etc/ssl/certs/server.pem
    
    # Enable SPOE filter
    filter spoe engine modsecurity config /etc/haproxy/spoe-modsecurity.conf
    
    # Process ModSecurity response
    http-request lua.modsecurity-process-request
    
    # Block if ModSecurity detected threats
    http-request deny if { var(txn.modsec.code) -m int gt 0 }
    
    # Redirect HTTP to HTTPS
    http-request redirect scheme https unless { ssl_fc }
    
    default_backend web_servers

backend web_servers
    balance roundrobin
    option httpchk GET /health
    
    server web1 192.168.1.10:80 check
    server web2 192.168.1.11:80 check
    server web3 192.168.1.12:80 check
    
listen stats
    bind *:8404
    stats enable
    stats uri /stats
    stats refresh 30s
    stats admin if TRUE

Create ModSecurity SPOE service configuration

Create a systemd service file to manage the ModSecurity SPOE agent daemon.

[Unit]
Description=ModSecurity SPOE Agent
After=network.target
Requires=network.target

[Service]
Type=simple
User=haproxy
Group=haproxy
ExecStart=/usr/local/bin/spoe-modsecurity -f /etc/modsecurity/main.conf
Restart=always
RestartSec=10
KillMode=mixed
KillSignal=SIGTERM
TimeoutStopSec=5
PrivateTmp=true
ProtectSystem=strict
ProtectHome=true
NoNewPrivileges=true
ReadWritePaths=/tmp /var/log/modsecurity

[Install]
WantedBy=multi-user.target

Create log directories with proper permissions

Set up logging directories with correct ownership and permissions for security and functionality.

sudo mkdir -p /var/log/modsecurity
sudo chown haproxy:haproxy /var/log/modsecurity
sudo chmod 755 /var/log/modsecurity
Never use chmod 777. This gives every user on the system full access to your files. Instead, set specific ownership with chown and use minimal required permissions.

Configure logrotate for ModSecurity logs

Set up log rotation to prevent ModSecurity audit logs from consuming excessive disk space.

/var/log/modsecurity/*.log {
    daily
    missingok
    rotate 52
    compress
    delaycompress
    notifempty
    create 644 haproxy haproxy
    postrotate
        systemctl reload rsyslog > /dev/null 2>&1 || true
    endscript
}

Start and enable services

Enable and start both the ModSecurity SPOE agent and HAProxy with the new configuration.

sudo systemctl daemon-reload
sudo systemctl enable modsecurity-spoe
sudo systemctl start modsecurity-spoe
sudo systemctl restart haproxy
sudo systemctl status modsecurity-spoe
sudo systemctl status haproxy

Configure custom WAF rules

Create application-specific rules

Add custom ModSecurity rules for your specific application requirements and threat landscape.

# Block common attack patterns
SecRule ARGS "@detectSQLi" \
    "id:1001,phase:2,block,msg:'SQL Injection Attack Detected',logdata:'Matched Data: %{MATCHED_VAR} found within %{MATCHED_VAR_NAME}',severity:2,tag:'attack-sqli'"

Rate limiting rule

SecRule IP:bf_counter "@gt 20" \ "id:1002,phase:1,deny,status:429,msg:'Rate limit exceeded',expirevar:ip.bf_counter=60"

Block suspicious user agents

SecRule REQUEST_HEADERS:User-Agent "@pmFromFile /etc/modsecurity/bad-user-agents.txt" \ "id:1003,phase:1,deny,msg:'Suspicious User Agent Blocked'"

Geographic blocking (requires GeoIP)

SecRule GEO:COUNTRY_CODE "@pmFromFile /etc/modsecurity/blocked-countries.txt" \ "id:1004,phase:1,deny,msg:'Request from blocked country'"

Create threat intelligence feeds

Set up files for maintaining lists of malicious IPs, user agents, and other threat indicators.

sudo cat > /etc/modsecurity/bad-user-agents.txt << 'EOF'
sqlmap
nmap
niktowget
curl/7.0
EOF

sudo cat > /etc/modsecurity/blocked-countries.txt << 'EOF'
CN
RU
KP
EOF

Include custom rules in main configuration

Update the main ModSecurity configuration to include your custom rules and threat intelligence.

sudo echo "Include /etc/modsecurity/custom-rules.conf" >> /etc/modsecurity/main.conf
sudo systemctl restart modsecurity-spoe

Monitor and tune WAF performance

Set up performance monitoring

Create monitoring scripts to track ModSecurity performance impact and detection statistics.

#!/bin/bash

LOG_FILE="/var/log/modsecurity/audit.log"
STATS_FILE="/var/log/modsecurity/daily-stats.log"

DATE=$(date +%Y-%m-%d)
BLOCKED=$(grep -c "\[id \"" $LOG_FILE 2>/dev/null || echo 0)
SQL_INJECTION=$(grep -c "attack-sqli" $LOG_FILE 2>/dev/null || echo 0)
XSS_ATTACKS=$(grep -c "attack-xss" $LOG_FILE 2>/dev/null || echo 0)
RCE_ATTACKS=$(grep -c "attack-rce" $LOG_FILE 2>/dev/null || echo 0)

echo "$DATE: Total blocked: $BLOCKED, SQLi: $SQL_INJECTION, XSS: $XSS_ATTACKS, RCE: $RCE_ATTACKS" >> $STATS_FILE

Reset daily log

echo "" > $LOG_FILE

Create performance tuning configuration

Optimize ModSecurity settings for your traffic volume and performance requirements.

# Performance optimizations
SecRequestBodyInMemoryLimit 262144
SecRequestBodyNoFilesLimit 262144
SecResponseBodyLimit 1048576

Disable response body inspection for static content

SecRule REQUEST_FILENAME "@endsWith .css" "id:2001,phase:2,pass,nolog,ctl:responseBodyAccess=off" SecRule REQUEST_FILENAME "@endsWith .js" "id:2002,phase:2,pass,nolog,ctl:responseBodyAccess=off" SecRule REQUEST_FILENAME "@endsWith .png" "id:2003,phase:2,pass,nolog,ctl:responseBodyAccess=off" SecRule REQUEST_FILENAME "@endsWith .jpg" "id:2004,phase:2,pass,nolog,ctl:responseBodyAccess=off"

Skip processing for known good sources

SecRule REMOTE_ADDR "@ipMatchFromFile /etc/modsecurity/whitelist-ips.txt" "id:2005,phase:1,pass,nolog,ctl:ruleEngine=off"

Set up automated rule updates

Create a script to automatically update OWASP Core Rule Set to the latest version.

#!/bin/bash

cd /etc/modsecurity/
git -C coreruleset pull origin master

Test configuration

if /usr/local/bin/spoe-modsecurity -t -f /etc/modsecurity/main.conf; then systemctl restart modsecurity-spoe echo "$(date): OWASP rules updated successfully" >> /var/log/modsecurity/updates.log else git -C coreruleset reset --hard HEAD~1 echo "$(date): OWASP rules update failed, rolled back" >> /var/log/modsecurity/updates.log fi

Schedule automated monitoring and updates

Set up cron jobs for daily statistics collection and weekly rule updates.

sudo chmod +x /usr/local/bin/modsecurity-stats.sh
sudo chmod +x /usr/local/bin/update-owasp-rules.sh

sudo crontab -e

Add these lines:

Daily stats collection

0 1 * /usr/local/bin/modsecurity-stats.sh

Weekly rule updates

0 2 0 /usr/local/bin/update-owasp-rules.sh

Test WAF functionality

Test SQL injection protection

Verify that ModSecurity detects and blocks common SQL injection attempts.

# Test basic SQL injection
curl -X POST "http://example.com/login" -d "username=admin' OR '1'='1&password=test"

Test advanced SQL injection

curl "http://example.com/search?q=1' UNION SELECT * FROM users--"

Check logs for detection

sudo grep -i "sql injection" /var/log/modsecurity/audit.log

Test cross-site scripting protection

Verify XSS attack detection and blocking capabilities.

# Test reflected XSS
curl "http://example.com/search?q="

Test stored XSS attempt

curl -X POST "http://example.com/comment" -d "message="

Check detection logs

sudo grep -i "xss" /var/log/modsecurity/audit.log

Test rate limiting functionality

Verify that rate limiting rules are working correctly to prevent brute force attacks.

# Generate multiple requests to trigger rate limiting
for i in {1..25}; do
    curl "http://example.com/login" -d "username=admin&password=wrong" &
done
wait

Check if rate limiting was triggered

sudo grep -i "rate limit" /var/log/modsecurity/audit.log

Verify your setup

# Check service status
sudo systemctl status haproxy
sudo systemctl status modsecurity-spoe

Verify SPOE connection

ss -tlnp | grep :12345

Test configuration syntax

sudo haproxy -f /etc/haproxy/haproxy.cfg -c /usr/local/bin/spoe-modsecurity -t -f /etc/modsecurity/main.conf

Check recent blocked requests

sudo tail -n 50 /var/log/modsecurity/audit.log

Monitor real-time activity

sudo tail -f /var/log/haproxy.log

Common issues

SymptomCauseFix
SPOE agent won't startMissing ModSecurity librarysudo ldconfig and verify libmodsecurity3 installation
HAProxy can't connect to SPOESPOE agent not listeningCheck ss -tlnp | grep 12345 and restart modsecurity-spoe
All requests blockedParanoia level too highReduce paranoia level in /etc/modsecurity/coreruleset/crs-setup.conf
High CPU usageResponse body inspection enabledDisable for static content with ctl:responseBodyAccess=off
Permission denied on logsIncorrect log directory ownershipsudo chown -R haproxy:haproxy /var/log/modsecurity
Rules not updatingGit authentication issuesUse HTTPS clone instead of SSH for OWASP repository

Next steps

Automated install script

Run this to automate the entire setup

Need help?

Don't want to manage this yourself?

We handle infrastructure security hardening for businesses that depend on uptime. From initial setup to ongoing operations.