Notes on How to Install Coraza Web Application Firewall (WAF) + OWASP CRS on Debian 13.7.0 Linux Server
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
REFERENCES
=============
[1] https://lists.debian.org/debian-user/2026/09/msg00395.html
[2] https://mail-archive.com/debian-user@lists.debian.org/msg824615.html
[3] https://marc.info/?l=debian-user&m=179044276368797&w=2
[5] mail-archive.com - NIL
[6] https://marc.info/?l=linux-netdev&m=179047826082629&w=2
[7] https://lists.freebsd.org/archives/freebsd-chat/2026-September/000088.html
[8] mail-archive.com - NIL
Comments
Post a Comment