How to Add WAF Protection to Apache

Two approaches to protect Apache with a WAF. Use ModSecurity as a native Apache module, or replace Apache with Caddy+Coraza for a simpler, modern setup with the same OWASP CRS rules.

30-45 minutes intermediate 7 steps
Last updated: Jun 6, 2026

Apache has the longest history of any web server when it comes to WAF protection. ModSecurity was originally built as an Apache module in 2002, and the combination of Apache + ModSecurity + CRS has been the gold standard for open source WAF deployments for over two decades.

This guide covers two approaches:

  • Option A: ModSecurity as a native Apache module -- the traditional approach. Install mod_security2 directly into Apache. Mature, well-documented, battle-tested. Best if you have an existing Apache setup and want to add WAF protection without changing your architecture.
  • Option B: Replace Apache with Caddy+Coraza -- the modern approach. Use Caddy as both your web server and WAF, powered by Coraza (a Go reimplementation of ModSecurity). Same CRS rules, simpler config, automatic HTTPS, one container. Best for new deployments or if you are already considering a migration away from Apache.

Both approaches use the OWASP Core Rule Set (CRS) for attack detection. Your protection coverage is the same regardless of which engine runs the rules.

Prerequisites

  • An existing Apache setup you want to protect, or Docker for the Caddy+Coraza approach
  • Root or sudo access for the Apache ModSecurity approach
  • Basic familiarity with Apache configuration (.htaccess, VirtualHost blocks)

Step-by-Step Guide

1

Option A: ModSecurity as a Native Apache Module

This is the traditional approach. ModSecurity was originally built for Apache and integrates as a native module (mod_security2). It runs inside the Apache process, inspecting every request.

Install on Ubuntu/Debian:

sudo apt update sudo apt install -y libapache2-mod-security2 sudo a2enmod security2 sudo systemctl restart apache2

Install the OWASP CRS:

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

Configure Apache to use CRS:

Edit /etc/apache2/mods-enabled/security2.conf:

<IfModule security2_module> SecDataDir /var/cache/modsecurity SecRuleEngine On IncludeOptional /etc/modsecurity/modsecurity.conf IncludeOptional /etc/modsecurity/crs/crs-setup.conf IncludeOptional /etc/modsecurity/crs/rules/*.conf </IfModule>

Restart Apache: sudo systemctl restart apache2

For a complete walkthrough, see our ModSecurity provider page.

Tip: On CentOS/RHEL, the package is called mod_security and is configured through /etc/httpd/conf.d/mod_security.conf.
2

Option B: Replace Apache with Caddy+Coraza

If you want to simplify your stack, Caddy+Coraza can replace Apache entirely. You get a web server, WAF, and automatic HTTPS in one binary. The WAF uses Coraza (a Go reimplementation of ModSecurity) with the same CRS rules.

The key advantage: no C dependencies, no compiling modules, no complex Apache config. A Caddyfile that replaces a typical Apache VirtualHost is 10-15 lines.

3

Apache to Caddy Directive Mapping

Here is how common Apache directives translate to Caddy:

Basics

# Apache                              -> Caddy DocumentRoot /var/www/html             -> root * /srv DirectoryIndex index.html              -> (automatic with file_server) Options -Indexes                       -> file_server { browse }  (or omit for no listing) ServerName example.com                 -> example.com { ... }

Reverse Proxy

# Apache                              -> Caddy ProxyPass / http://app:3000/           -> reverse_proxy app:3000 ProxyPassReverse / http://app:3000/    -> (automatic in Caddy) ProxyPreserveHost On                   -> (automatic in Caddy) ProxyTimeout 60                        -> reverse_proxy app:3000 { read_timeout 60s }

Headers

# Apache                              -> Caddy Header set X-Frame-Options DENY        -> header X-Frame-Options DENY Header set X-Content-Type-Options ...  -> header X-Content-Type-Options nosniff ServerTokens Prod                      -> (off by default in Caddy)

Redirects and Rewrites

# Apache                              -> Caddy Redirect 301 /old /new                 -> redir /old /new permanent RewriteRule ^/blog(.*)$ /news$1 [R=301]-> redir /blog* /news{path} permanent RewriteCond %{HTTPS} off               -> (automatic with Caddy auto-HTTPS) RewriteRule (.*) https://%{HTTP_HOST}   -> (automatic)

TLS

# Apache                              -> Caddy SSLEngine on                           -> (automatic with domain name) SSLCertificateFile /path/cert.pem      -> tls /path/cert.pem /path/key.pem SSLCertificateKeyFile /path/key.pem    -> (or just use a domain name for auto-HTTPS) SSLProtocol all -SSLv3 -TLSv1 ...      -> (TLS 1.2+ by default)

.htaccess

# Apache .htaccess is not supported in Caddy. # Move all .htaccess rules into the Caddyfile directly. # This is actually an advantage: no per-request filesystem checks.
4

Write the Caddyfile with WAF

Here is a Caddyfile that replaces a typical Apache VirtualHost with WAF protection:

{ order coraza_waf first }
yourdomain.com { coraza_waf { load_owasp_crs directives ` Include @coraza.conf-recommended Include @crs-setup.conf.example Include @owasp_crs/*.conf SecRuleEngine On ` }
root * /srv file_server encode gzip
header { X-Content-Type-Options nosniff X-Frame-Options DENY } }

Compare this to the Apache equivalent:

<VirtualHost *:443> ServerName yourdomain.com DocumentRoot /var/www/html
SSLEngine on SSLCertificateFile /etc/letsencrypt/live/yourdomain.com/fullchain.pem SSLCertificateKeyFile /etc/letsencrypt/live/yourdomain.com/privkey.pem
<IfModule security2_module> SecRuleEngine On IncludeOptional /etc/modsecurity/modsecurity.conf IncludeOptional /etc/modsecurity/crs/crs-setup.conf IncludeOptional /etc/modsecurity/crs/rules/*.conf </IfModule>
Header set X-Content-Type-Options nosniff Header set X-Frame-Options DENY
<Directory /var/www/html> Options -Indexes AllowOverride None Require all granted </Directory> </VirtualHost>

The Caddyfile is roughly one-third the size. TLS is automatic, and the WAF configuration is simpler because Coraza bundles CRS directly.

5

Build, Run, and Test

Build the Caddy+Coraza image and run it:

# Build docker build -t caddy-coraza -f Dockerfile .
# Run (static files) docker run -d -p 80:80 -p 443:443 \ -v ./Caddyfile:/etc/caddy/Caddyfile:ro \ -v ./site:/srv:ro \ -v caddy_data:/data \ caddy-coraza

Test normal access and WAF blocking:

# Normal request - should return 200 curl -o /dev/null -w "%{http_code}\n" http://localhost/
# SQL injection - should return 403 curl -o /dev/null -w "%{http_code}\n" "http://localhost/?id=1%20OR%201=1"
# XSS - should return 403 curl -o /dev/null -w "%{http_code}\n" "http://localhost/?q=%3Cscript%3Ealert(1)%3C/script%3E"
6

What Does Not Map 1:1 from Apache

Some Apache features work differently or are not available in Caddy:

Works differently

  • .htaccess: not supported. Move all rules into the Caddyfile. This is actually faster because Caddy does not scan the filesystem for .htaccess files on every request.
  • mod_rewrite with complex conditions: Caddy uses matchers instead of RewriteCond/RewriteRule. Most patterns translate, but complex chains need rethinking.
  • <Directory> / <Location> blocks: use Caddy's handle and matcher directives instead.
  • Authentication (mod_auth_basic): use Caddy's basicauth directive.

Not available in Caddy

  • mod_php: Caddy does not embed PHP. Use PHP-FPM as a backend with php_fastcgi.
  • mod_perl / mod_python: no equivalent. Run your app as a separate process and reverse proxy to it.
  • mod_cache: Caddy has a cache-handler plugin but it is less mature than Apache's caching modules.
7

Use AI to Convert Your Apache Config

For complex Apache configurations with many VirtualHosts, use this prompt with any AI assistant:

Convert the following Apache VirtualHost configuration to a Caddy (Caddyfile) configuration. Also add Coraza WAF protection using the coraza-caddy plugin with OWASP CRS.
Requirements: - Use the coraza_waf directive with load_owasp_crs - Include @coraza.conf-recommended, @crs-setup.conf.example, and @owasp_crs/*.conf - Set SecRuleEngine On - Use "order coraza_waf first" in the global options block - Convert all .htaccess rules into the Caddyfile directly - Preserve all redirects, headers, and proxy targets - Replace mod_rewrite rules with Caddy matchers - Add comments explaining each section - Flag anything that does not have a direct Caddy equivalent
Here is my Apache config:
[paste your Apache VirtualHost config here]

Review the output carefully, especially mod_rewrite conversions and any .htaccess rules that were inlined.

Conclusion & Next Steps

Apache has excellent WAF support through its native ModSecurity module. If you are happy with Apache and just want to add WAF protection, Option A (ModSecurity) is the straightforward path.

If you are looking to modernize your stack, Caddy+Coraza offers the same CRS-based protection with a simpler configuration, automatic HTTPS, and no C dependency chain. The same rules that run on ModSecurity run on Coraza, so your protection level is identical.

Summary:

  • Existing Apache setup, just want WAF -> install mod_security2 + CRS
  • New deployment or ready to simplify -> Caddy+Coraza in Docker
  • Complex Apache config with mod_rewrite, .htaccess, mod_php -> keep Apache + ModSecurity, or migrate incrementally

Troubleshooting

ModSecurity blocks legitimate requests on Apache

Check the Apache error log or ModSecurity audit log (/var/log/apache2/modsec_audit.log) to find which rule is firing. Disable specific rules with SecRuleRemoveById in your Apache config.

Apache is slow after enabling ModSecurity

ModSecurity adds overhead for each request. Reduce it by lowering the CRS paranoia level in crs-setup.conf (default is 1, which is the lightest). Also check that you are not logging every request in full audit mode (SecAuditEngine RelevantOnly instead of On).

.htaccess rules do not work in Caddy

Caddy does not support .htaccess files. All rules need to go in the Caddyfile. Search your .htaccess files for RewriteRule, Redirect, Header, and auth directives and translate them using the mapping table in this guide.

Frequently Asked Questions

Should I stick with Apache + ModSecurity or switch to Caddy + Coraza?

If Apache is working well for you and you just need WAF protection, stick with Apache + ModSecurity. It is the most mature open source WAF combination available. Switch to Caddy + Coraza if you want a simpler config, automatic HTTPS, Docker-native deployment, or are already planning to move away from Apache.

Are the CRS rules the same on ModSecurity and Coraza?

Yes. Both engines run the OWASP Core Rule Set. CRS v4 is tested against ModSecurity v2, ModSecurity v3, and Coraza on every release. The protection coverage is identical. The difference is the engine that evaluates the rules (C for ModSecurity, Go for Coraza), not the rules themselves.

Can I migrate my custom ModSecurity rules to Coraza?

In most cases, yes. Coraza supports the SecLang rule language that ModSecurity uses. Your custom SecRule directives, rule exclusions, and CRS tuning will work on Coraza without modification. Some advanced ModSecurity features have partial compatibility in Coraza, so test your custom rules before switching production traffic.

What about Apache modules like mod_php?

Caddy does not embed PHP like Apache's mod_php. Instead, use PHP-FPM (FastCGI Process Manager) as a separate process and configure Caddy with the php_fastcgi directive. This is actually the recommended approach even in Apache for performance. The conversion is straightforward for standard PHP applications like WordPress, Laravel, or Drupal.

Related Guides