Web Application Firewall (WAF) Setup for Websites
WAF analyzes HTTP traffic and blocks requests matching known attack patterns: SQL injections, XSS, LFI/RFI, SSRF, directory scanning. Works at L7 level, before web server or application.
WAF Options
Cloud WAF:
- Cloudflare WAF — built into Cloudflare, OWASP CRS rules + custom
- AWS WAF — integration with ALB, CloudFront, API Gateway
- Imperva — enterprise level
Self-hosted:
- ModSecurity — module for Nginx/Apache, open standard
- Coraza — Go implementation of ModSecurity, compatible with OWASP CRS
- BunkerWeb — Docker-ready Nginx + WAF
ModSecurity + OWASP CRS on Nginx
Installation on Ubuntu/Debian:
apt install libnginx-mod-security2
# or build from source for latest ModSecurity v3
Configuration:
# /etc/nginx/nginx.conf
modsecurity on;
modsecurity_rules_file /etc/nginx/modsecurity/modsecurity.conf;
server {
listen 443 ssl;
# ...
modsecurity on;
modsecurity_rules_file /etc/nginx/modsecurity/main.conf;
}
# /etc/nginx/modsecurity/modsecurity.conf
SecRuleEngine On # DetectionOnly for start — logs only
SecRequestBodyAccess On
SecResponseBodyAccess Off # Enable only if response checking needed
SecAuditEngine RelevantOnly
SecAuditLog /var/log/modsecurity/audit.log
OWASP Core Rule Set
# Download OWASP CRS
git clone https://github.com/coreruleset/coreruleset /etc/nginx/modsecurity/crs
# main.conf
Include /etc/nginx/modsecurity/modsecurity.conf
Include /etc/nginx/modsecurity/crs/crs-setup.conf
Include /etc/nginx/modsecurity/crs/rules/*.conf
CRS contains hundreds of rules split by category: injections (941xxx), XSS (941xxx), LFI (930xxx), RCE (932xxx), scanners (913xxx).
CRS Protection Level Setup
# crs-setup.conf
SecDefaultAction "phase:1,log,auditlog,pass"
# Paranoia level: 1 (minimum false positives) — 4 (maximum rules)
SecAction \
"id:900000, \
phase:1, \
nolog, \
pass, \
t:none, \
setvar:tx.paranoia_level=2"
Start at level 1 in DetectionOnly mode, analyze false positives, gradually increase.
Cloudflare WAF: Custom Rules
# Rule: block requests with suspicious User-Agent
(http.user_agent contains "sqlmap") or
(http.user_agent contains "nikto") or
(http.user_agent contains "masscan")
→ Action: Block
# Block directory traversal attempts
(http.request.uri.path contains "../") or
(http.request.uri.path contains "..%2F")
→ Action: Block
# JS Challenge for high-frequency /api requests
(http.request.uri.path matches "^/api/") and
(rate(http.request.method eq "POST") > 50)
→ Action: JS Challenge
AWS WAF
# Terraform
resource "aws_wafv2_web_acl" "main" {
name = "main-waf"
scope = "CLOUDFRONT"
default_action { allow {} }
rule {
name = "AWSManagedRulesCommonRuleSet"
priority = 1
override_action { none {} }
statement {
managed_rule_group_statement {
name = "AWSManagedRulesCommonRuleSet"
vendor_name = "AWS"
}
}
visibility_config {
cloudwatch_metrics_enabled = true
metric_name = "CommonRuleSetMetric"
sampled_requests_enabled = true
}
}
}
Working with False Positives
False positives — main WAF issue. Strategy:
- Run in DetectionOnly / Log mode for 1–2 weeks
- Analyze audit.log — find rules blocking legitimate traffic
- Create exceptions for specific URIs and rules
# ModSecurity — exclude rule for specific path
SecRule REQUEST_URI "@beginsWith /admin/editor" \
"id:1001, phase:1, pass, nolog, ctl:ruleRemoveById=941100"
WAF Monitoring
- ModSecurity audit.log → parse via GoAccess or Filebeat → Elasticsearch
- Cloudflare Analytics → Security Events → filter by rules
- Alert on sharp blocking increase — sign of starting attack or broken application
Implementation Timeline
- Cloudflare WAF with ready managed rules: 4–8 hours
- ModSecurity + OWASP CRS + exception setup: 3–5 days
- AWS WAF via Terraform + monitoring: 2–3 days







