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




Reply via email to