Subject: Notes on How to Install Coraza Web Application Firewall (WAF) + OWASP CRS on Debian 13.7.0 Linux Server
Good day from Singapore, Author: Mr. Turritopsis Dohrnii Teo En Ming Date: 27 Sep 2026 Sunday 12.47 AM Country: Singapore Install Debian 13.7.0 ====================== nano /etc/apt/sources.list #deb cdrom:[Debian GNU/Linux 13.7.0 _Trixie_ - Official amd64 DVD Binary-1 with firmware 20260912-09:36]/ trixie contrib main non-free-firmware apt update apt full-upgrade -y apt install -y curl wget git ca-certificates gnupg unzip tar jq reboot cat /etc/os-release Test connectivity to your existing HTTPS server ================================================= curl -vk https://192.168.88.8 # (This is a VMware ESXi 8.0 Update 3e Server) Install Go ============ apt install -y golang-go go version Then install xcaddy: GOBIN=/usr/local/bin go install github.com/caddyserver/xcaddy/cmd/xcaddy@latest xcaddy version Build Caddy with Coraza =========================== mkdir -p /usr/local/src/caddy-coraza cd /usr/local/src/caddy-coraza xcaddy build --with github.com/corazawaf/coraza-caddy/v2 ./caddy version ./caddy list-modules | grep -i waf install -m 755 ./caddy /usr/bin/caddy /usr/bin/caddy version Create Caddy user/directories ================================ groupadd --system caddy 2>/dev/null || true useradd --system \ --gid caddy \ --create-home \ --home-dir /var/lib/caddy \ --shell /usr/sbin/nologin \ caddy 2>/dev/null || true mkdir -p /etc/caddy mkdir -p /etc/coraza mkdir -p /etc/coraza/crs mkdir -p /var/log/caddy mkdir -p /var/log/coraza chown -R root:caddy /etc/caddy chown -R root:caddy /etc/coraza chown -R caddy:caddy /var/log/caddy chown -R caddy:caddy /var/log/coraza chmod 750 /etc/caddy chmod 750 /etc/coraza Download OWASP Core Rule Set =============================== cd /opt git clone https://github.com/coreruleset/coreruleset.git coreruleset cd /opt/coreruleset git status Install the CRS files ======================== cp -a /opt/coreruleset/. /etc/coraza/crs/ ls -la /etc/coraza/crs ls -la /etc/coraza/crs/rules | head -30 chown -R root:caddy /etc/coraza find /etc/coraza -type d -exec chmod 750 {} \; find /etc/coraza -type f -exec chmod 640 {} \; Create Coraza base configuration ==================================== nano /etc/coraza/coraza.conf SecRuleEngine DetectionOnly SecRequestBodyAccess On SecResponseBodyAccess Off SecRequestBodyLimit 13107200 SecRequestBodyNoFilesLimit 131072 SecAuditEngine RelevantOnly SecAuditLogParts ABIJDEFHZ SecAuditLogType Serial SecAuditLog /var/log/coraza/audit.log Configure CRS ================ cp /etc/coraza/crs/crs-setup.conf.example \ /etc/coraza/crs/crs-setup.conf Configure Caddy + Coraza ========================= nano /etc/caddy/Caddyfile { order coraza_waf first } https://192.168.88.7 { coraza_waf { directives ` Include /etc/coraza/coraza.conf Include /etc/coraza/crs/crs-setup.conf Include /etc/coraza/crs/rules/*.conf ` } reverse_proxy https://192.168.88.8 { transport http { tls tls_insecure_skip_verify } } log { output file /var/log/caddy/access.log } } ***NOTICE: Please note that 192.168.88.7 is the Coraza WAF and 192.168.88.8 is the HTTPS web server it is protecting.*** Better solution: trust the self-signed certificate ==================================================== openssl s_client \ -connect 192.168.88.8:443 \ -showcerts </dev/null nano /etc/coraza/backend.crt -----BEGIN CERTIFICATE----- ---snipped--- -----END CERTIFICATE----- chmod 644 /etc/coraza/backend.crt Validate Caddy configuration =============================== caddy validate \ --config /etc/caddy/Caddyfile \ --adapter caddyfile Create systemd service ========================= nano /etc/systemd/system/caddy.service [Unit] Description=Caddy with Coraza WAF Documentation=https://caddyserver.com/ After=network-online.target Wants=network-online.target [Service] Type=notify User=caddy Group=caddy ExecStart=/usr/bin/caddy run \ --environ \ --config /etc/caddy/Caddyfile ExecReload=/usr/bin/caddy reload \ --config /etc/caddy/Caddyfile \ --force TimeoutStopSec=5s LimitNOFILE=1048576 PrivateTmp=true ProtectSystem=full AmbientCapabilities=CAP_NET_ADMIN CAP_NET_BIND_SERVICE CapabilityBoundingSet=CAP_NET_ADMIN CAP_NET_BIND_SERVICE [Install] WantedBy=multi-user.target chown -R root:caddy /etc/coraza find /etc/coraza -type d -exec chmod 750 {} \; find /etc/coraza -type f -exec chmod 640 {} \; mkdir -p /var/log/caddy chown -R caddy:caddy /var/log/caddy chmod 750 /var/log/caddy chown caddy:caddy /var/log/caddy/access.log chmod 640 /var/log/caddy/access.log mkdir -p /var/log/coraza chown -R caddy:caddy /var/log/coraza chmod 750 /var/log/coraza chown caddy:caddy /var/log/coraza/audit.log chmod 640 /var/log/coraza/audit.log systemctl daemon-reload systemctl enable caddy systemctl start caddy systemctl status caddy --no-pager -l journalctl -u caddy -n 100 --no-pager Confirm ports ============== ss -lntp | grep -E ':80|:443' LISTEN 0 4096 *:443 *:* users:(("caddy",pid=11197,fd=7)) LISTEN 0 4096 *:80 *:* users:(("caddy",pid=11197,fd=9)) Test normal website traffic ============================ Open https://192.168.88.7 (Coraza WAF) in a Google Chrome web browser. The site should work normally. Then watch Coraza/Caddy: journalctl -u caddy -f tail -f /var/log/coraza/audit.log Test SQL injection detection ================================ While still in: SecRuleEngine DetectionOnly send a harmless test request: curl -k 'https://192.168.88.7/?id=1%27%20OR%20%271%27=%271' tail -100 /var/log/coraza/audit.log You should see CRS alerts associated with SQL injection. Test XSS detection ==================== curl -k 'https://192.168.88.7/?q=%3Cscript%3Ealert(1)%3C%2Fscript%3E' tail -100 /var/log/coraza/audit.log Again, in DetectionOnly mode the request isn't supposed to be blocked; you're checking that CRS recognizes it. Turn blocking on ================== nano /etc/coraza/coraza.conf Change to SecRuleEngine On caddy validate \ --config /etc/caddy/Caddyfile \ --adapter caddyfile systemctl reload caddy Now repeat the SQLi test. Open https://192.168.88.7/?id=1%27%20OR%20%271%27=%271 in Google Chrome web browser. Access to 192.168.88.7 was denied You don't have authorization to view this page. HTTP ERROR 403 Automatically update OWASP CRS =============================== <EMPTY> Create CRS update script ========================== nano /usr/local/sbin/update-coraza-crs.sh #!/bin/bash set -euo pipefail WORKDIR="/var/tmp/coraza-crs-update" INSTALLDIR="/etc/coraza/crs" BACKUPDIR="/etc/coraza/crs-backup" rm -rf "$WORKDIR" git clone --depth 1 \ https://github.com/coreruleset/coreruleset.git \ "$WORKDIR" # Preserve local CRS configuration if [ -f "$INSTALLDIR/crs-setup.conf" ]; then cp "$INSTALLDIR/crs-setup.conf" \ "$WORKDIR/crs-setup.conf" else cp "$WORKDIR/crs-setup.conf.example" \ "$WORKDIR/crs-setup.conf" fi # Backup existing CRS rm -rf "$BACKUPDIR" cp -a "$INSTALLDIR" "$BACKUPDIR" # Install candidate rules rm -rf "${INSTALLDIR}.new" cp -a "$WORKDIR" "${INSTALLDIR}.new" chown -R root:caddy "${INSTALLDIR}.new" chmod -R g+rX "${INSTALLDIR}.new" # Temporarily switch directories mv "$INSTALLDIR" "${INSTALLDIR}.old" mv "${INSTALLDIR}.new" "$INSTALLDIR" # Validate complete Caddy/Coraza configuration if /usr/bin/caddy validate \ --config /etc/caddy/Caddyfile \ --adapter caddyfile then systemctl reload caddy rm -rf "${INSTALLDIR}.old" logger -t coraza-crs-update \ "OWASP CRS successfully updated" else logger -t coraza-crs-update \ "CRS update FAILED validation; rolling back" rm -rf "$INSTALLDIR" mv "${INSTALLDIR}.old" "$INSTALLDIR" exit 1 fi rm -rf "$WORKDIR" Make executable: chmod 750 /usr/local/sbin/update-coraza-crs.sh Automate it with systemd ========================== Instead of cron, use a systemd timer. nano /etc/systemd/system/coraza-crs-update.service [Unit] Description=Update OWASP Core Rule Set for Coraza [Service] Type=oneshot ExecStart=/usr/local/sbin/update-coraza-crs.sh nano /etc/systemd/system/coraza-crs-update.timer [Unit] Description=Daily OWASP CRS update check [Timer] OnCalendar=*-*-* 03:30:00 Persistent=true RandomizedDelaySec=30m [Install] WantedBy=timers.target This checks approximately once per day around 03:30. systemctl daemon-reload systemctl enable --now coraza-crs-update.timer systemctl list-timers | grep coraza Test the updater manually first ==================================== Do not wait until 03:30 for the first run. systemctl start coraza-crs-update.service systemctl status coraza-crs-update.service journalctl \ -u coraza-crs-update.service \ -n 100 \ --no-pager systemctl status caddy Then access the website. https://192.168.88.7 (Coraza WAF) Automatic Debian security updates ================================== You should also keep Debian patched. apt install -y unattended-upgrades dpkg-reconfigure unattended-upgrades Select Yes. systemctl status unattended-upgrades That's all. Regards, Mr. Turritopsis Dohrnii Teo En Ming Republic of Singapore 27 Sep 2026 Sunday 1.00 am Singapore Time

