A web application firewall becomes useful when an Apache site needs more than TLS and normal request logging. ModSecurity with Apache on Debian inspects HTTP requests before they reach applications such as WordPress, phpMyAdmin, Nextcloud, or custom PHP code, then uses the OWASP Core Rule Set (CRS) to detect common SQL injection, cross-site scripting, file traversal, and command-injection patterns.
Debian archive packages are the default path for most systems because they work across Debian’s supported architectures, including arm64. Digitalwave is an optional third-party source for amd64 and i386 systems that specifically need newer backports. CRS can stay package-managed or be replaced with a signed CRS 4 release from the official OWASP CRS GitHub releases. Start in monitoring mode on production sites, review the audit log, then enable blocking after you understand which requests your application normally sends.
Choose a ModSecurity Method on Debian
Debian provides ModSecurity and CRS packages in its default repositories for Debian 13 (Trixie), Debian 12 (Bookworm), and Debian 11 (Bullseye). Use those packages unless you have a clear reason to add a third-party source. The Digitalwave ModSecurity repository publishes newer Debian backports, but its Debian package indexes are limited to amd64 and i386.
| Path | What It Installs | Architecture Fit | Update Owner | Best For |
|---|---|---|---|---|
| Debian archive packages | Debian archive versions of libapache2-mod-security2 and modsecurity-crs | Default choice for Debian-supported architectures, including arm64 | APT | Most servers, ARM systems, and deployments that prefer distro-maintained packages |
| Optional Digitalwave packages | Digitalwave candidates for libapache2-mod-security2, its ModSecurity library, and packaged modsecurity-crs | amd64 and i386 from Digitalwave | APT | Servers that specifically need newer Digitalwave backports and accept a third-party repository |
| APT-managed module plus signed CRS 4 release | Apache module from the selected APT source with CRS files installed under /etc/apache2/modsec | Module follows the selected package source; CRS tarball is architecture-independent | APT for the module, helper script for CRS | Administrators who specifically want the latest CRS 4 release and are willing to maintain manual rule-set updates |
The package names matter. libapache2-mod-security2 is the Apache module package that loads ModSecurity 2 for Apache. modsecurity-crs is the packaged OWASP Core Rule Set. The package-managed CRS files are loaded through /usr/share/modsecurity-crs/owasp-crs.load on Debian after the package is installed.
Use one CRS source at a time. Do not leave both packaged CRS includes and a manual CRS 4 include active in
/etc/apache2/mods-available/security2.conf, because duplicate rules can break Apache startup with repeated rule IDs.
Install ModSecurity with Apache on Debian
The installation starts with Apache and small helper tools, then installs the Debian archive packages. Add Digitalwave only when newer backports are worth the extra repository, architecture limit, and source cleanup.
Update Debian and Install Prerequisites
Refresh APT metadata first so dependency resolution starts from the current enabled Debian sources.
sudo apt update
Install Apache and the small tools used by the optional Digitalwave and CRS 4 sections later.
sudo apt install apache2 ca-certificates curl gpg jq
If Apache is already installed, APT keeps the existing package and installs only missing prerequisites. These commands use sudo because package installation, repository files, and Apache configuration live in root-owned system paths. For a broader web-server baseline before adding a WAF, use the Apache installation guide for Debian.
Install Debian Archive Packages
Install the Apache module and packaged CRS from Debian’s own repositories. No third-party repository is required for this baseline path.
sudo apt install libapache2-mod-security2 modsecurity-crs
Check Digitalwave Architecture Support
Use this optional check only if you plan to add Digitalwave for newer backports. Digitalwave’s Debian package indexes are limited to amd64 and i386. If the command prints arm64, stay with the Debian archive package path.
dpkg --print-architecture
amd64
Add the Optional Digitalwave Repository
On amd64 or i386, add Digitalwave only when you want its newer ModSecurity backports. Download the repository key and store it as a dedicated keyring. The --yes option prevents gpg from prompting if you rerun the command during troubleshooting.
curl -fsSL https://modsecurity.digitalwave.hu/archive.key | sudo gpg --dearmor --yes -o /usr/share/keyrings/digitalwave-modsecurity.gpg
Create the APT source with an architecture guard. Debian 13, 12, and 11 use different codenames, so the source setup reads /etc/os-release instead of relying on lsb_release, which is often missing on minimal Debian systems.
. /etc/os-release
arch=$(dpkg --print-architecture)
case "$arch" in
amd64|i386)
printf '%s\n' \
'Types: deb' \
'URIs: http://modsecurity.digitalwave.hu/debian/' \
"Suites: ${VERSION_CODENAME}-backports" \
'Components: main' \
"Architectures: ${arch}" \
'Signed-By: /usr/share/keyrings/digitalwave-modsecurity.gpg' | sudo tee /etc/apt/sources.list.d/digitalwave-modsecurity.sources > /dev/null
;;
*)
printf 'Digitalwave ModSecurity packages are not published for %s. Use the Debian archive package path instead.\n' "$arch" >&2
false
;;
esac
Add an APT preference so the ModSecurity package family comes from Digitalwave when both Debian and Digitalwave provide a candidate. Debian 13 uses the libmodsecurity3t64 library name, while Debian 12 and 11 use libmodsecurity3, so the pin includes both names.
printf '%s\n' \
'Package: libapache2-mod-security2 libmodsecurity3 libmodsecurity3t64 modsecurity-crs' \
'Pin: origin modsecurity.digitalwave.hu' \
'Pin-Priority: 900' | sudo tee /etc/apt/preferences.d/99modsecurity > /dev/null
Refresh APT again so the new source is available.
sudo apt update
Verify the Optional Digitalwave Candidate
Before using the optional Digitalwave packages, check that APT sees Digitalwave as the selected source for both package names.
apt-cache policy libapache2-mod-security2 modsecurity-crs
On a correctly configured Debian 13 system, the important details are the Digitalwave URL, the trixie-backports suite, and priority 900. Debian 12 shows bookworm-backports, and Debian 11 shows bullseye-backports.
libapache2-mod-security2:
Candidate: 2.9.14-1~pre1+0~20260428~bpo13+02eed22
Version table:
2.9.14-1~pre1+0~20260428~bpo13+02eed22 900
500 http://modsecurity.digitalwave.hu/debian trixie-backports/main amd64 Packages
modsecurity-crs:
Candidate: 3.3.9-1~bpo13+1
Version table:
3.3.9-1~bpo13+1 900
500 http://modsecurity.digitalwave.hu/debian trixie-backports/main amd64 Packages
If APT still prefers Debian’s default package on amd64 or i386, recheck the source file name, the release codename, and the preference file before installing. On arm64, the expected path is the Debian archive package because Digitalwave does not publish a Debian binary-arm64 package index.
Install or Upgrade the Apache Module and Packaged CRS
If you stayed with Debian archive packages, this command is already complete. If you added Digitalwave, rerun it so APT installs the Digitalwave candidates. The module package enables the Apache security2 module, and the CRS package installs the rule-set loader under /usr/share/modsecurity-crs.
sudo apt install libapache2-mod-security2 modsecurity-crs
Confirm that Apache can see the module.
sudo apachectl -M 2>/dev/null | grep security2
security2_module (shared)
Check the packaged CRS loader path as well. This path is useful when troubleshooting include problems or answering the common Debian package-path question.
find /usr/share/modsecurity-crs -maxdepth 2 -type f \( -name '*.load' -o -name 'crs-setup.conf' \)
/usr/share/modsecurity-crs/owasp-crs.load
Configure ModSecurity on Apache
ModSecurity installs a recommended configuration template but does not use it until you copy it into place. The active file is /etc/modsecurity/modsecurity.conf, and Apache loads it through /etc/apache2/mods-available/security2.conf.
Activate the Recommended Configuration
Copy the recommended configuration to the active configuration path.
sudo cp /etc/modsecurity/modsecurity.conf-recommended /etc/modsecurity/modsecurity.conf
Set the rule engine. For a new live site, use DetectionOnly first so requests are logged but not blocked while you tune false positives. For a lab server or a site ready for active blocking, use On.
sudo sed -i 's/^SecRuleEngine .*/SecRuleEngine DetectionOnly/' /etc/modsecurity/modsecurity.conf
Switch to blocking mode when you are ready to reject matching requests.
sudo sed -i 's/^SecRuleEngine .*/SecRuleEngine On/' /etc/modsecurity/modsecurity.conf
Production sites should spend time in
DetectionOnlybefore active blocking. A WAF can block legitimate login forms, search boxes, API requests, or admin actions until the CRS exclusions match the application.
Tune Audit Log Detail
The audit log is the fastest way to understand why ModSecurity acted on a request. The following setting keeps useful request, response, and matched-rule sections in the log.
sudo sed -i 's/^SecAuditLogParts .*/SecAuditLogParts ABCEFHJKZ/' /etc/modsecurity/modsecurity.conf
The letters represent log parts. The most useful pieces for first tuning are request headers, request body, response status, matched rule data, and the audit trailer. If response-body inspection creates noise on your site, tune it deliberately instead of disabling the whole rule engine.
Test and Restart Apache
Run an Apache syntax test before restarting. A server-name warning such as AH00558 is common on fresh Apache installations; Syntax OK is the success signal for this step.
sudo apachectl configtest
Syntax OK
Restart Apache so the module, base configuration, and CRS includes load together.
sudo systemctl restart apache2
Verify ModSecurity Blocks a Test Request
Use a local request that should trigger CRS command-injection detection. This request targets 127.0.0.1, so it does not depend on DNS, TLS, a virtual host, or an open firewall port.
curl -sS -o /dev/null -w '%{http_code}\n' 'http://127.0.0.1/?exec=/bin/bash'
When SecRuleEngine is set to On and CRS is loaded, Apache should return 403.
403
If you intentionally left the engine in DetectionOnly, the same request may return 200 or another normal application response while still writing a ModSecurity audit entry. That is expected because monitoring mode logs matches without blocking them.
Inspect the audit log after the test. The tail command examples can help if you want more log-following patterns.
sudo tail -n 40 /var/log/apache2/modsec_audit.log
Look for the request URI, the returned status, and a matched rule ID such as [id "932100"] or another CRS rule number. The exact rule can change as CRS updates, so use the ID in your own log when building an exclusion.
Install the Latest Signed OWASP CRS 4 Release
The package-managed method is easier to maintain, but some administrators want the latest CRS 4 release from the official project. The OWASP CRS installation documentation recommends using supported releases and verifying release signatures with the CRS project key. This method downloads from the official CRS GitHub releases, keeps the Apache module under APT, and installs CRS files under /etc/apache2/modsec.
This method replaces the CRS include path used by Apache. Use it only if you are comfortable maintaining manual CRS updates and keeping backups of local exclusions.
Download and Verify the Latest CRS Release
Resolve the latest release tag from GitHub, download the signed minimal release tarball and matching signature, then verify the signature with the CRS project key in a temporary GPG home. The command resolves the release at runtime instead of hard-coding a CRS version into the install steps.
prepare_crs_download() {
workdir=$(mktemp -d)
crs_tag=$(curl -fsSL https://api.github.com/repos/coreruleset/coreruleset/releases/latest | jq -r '.tag_name')
case "$crs_tag" in
v*) ;;
*) echo "Could not resolve the latest CRS tag." >&2; rm -rf "$workdir"; return 1 ;;
esac
crs_version=${crs_tag#v}
archive="$workdir/coreruleset-${crs_version}-minimal.tar.gz"
signature="$workdir/coreruleset-${crs_version}-minimal.tar.gz.asc"
release_url="https://github.com/coreruleset/coreruleset/releases/download/${crs_tag}"
curl -fsSLo "$archive" "${release_url}/coreruleset-${crs_version}-minimal.tar.gz"
curl -fsSLo "$signature" "${release_url}/coreruleset-${crs_version}-minimal.tar.gz.asc"
GNUPGHOME="$workdir/gnupg"
export GNUPGHOME
mkdir -m 700 "$GNUPGHOME"
gpg --fetch-key https://coreruleset.org/security.asc
gpg --verify "$signature" "$archive"
}
prepare_crs_download
A successful verification includes a good signature from the OWASP Core Rule Set key and the project fingerprint.
gpg: Good signature from "OWASP Core Rule Set <security@coreruleset.org>" [unknown] Primary key fingerprint: 3600 6F0E 0BA1 6783 2158 8211 38EE ACA1 AB8A 6E72
Install CRS 4 Files Under Apache
Create the CRS directory, extract the verified archive, and activate the example CRS setup file. Keep these commands in the same terminal session so archive, crs_tag, and crs_version still exist.
Replacing the manual CRS tree deletes existing files under /etc/apache2/modsec. On systems with prior CRS 4 tuning, copy custom crs-setup.conf, exclusion files, or plugins before recreating the directory.
sudo rm -rf /etc/apache2/modsec
sudo mkdir -p /etc/apache2/modsec
sudo tar xzf "$archive" -C /etc/apache2/modsec --strip-components=1
sudo cp /etc/apache2/modsec/crs-setup.conf.example /etc/apache2/modsec/crs-setup.conf
sudo cp /etc/apache2/modsec/rules/REQUEST-900-EXCLUSION-RULES-BEFORE-CRS.conf.example /etc/apache2/modsec/rules/REQUEST-900-EXCLUSION-RULES-BEFORE-CRS.conf
sudo cp /etc/apache2/modsec/rules/RESPONSE-999-EXCLUSION-RULES-AFTER-CRS.conf.example /etc/apache2/modsec/rules/RESPONSE-999-EXCLUSION-RULES-AFTER-CRS.conf
Replace the ModSecurity Apache module include file so Apache loads the manual CRS 4 path. The plugin includes are optional, but keeping them in the file makes future CRS plugins easier to enable.
sudo tee /etc/apache2/mods-available/security2.conf > /dev/null <<'EOF'
<IfModule security2_module>
SecDataDir /var/cache/modsecurity
IncludeOptional /etc/modsecurity/*.conf
IncludeOptional /etc/apache2/modsec/crs-setup.conf
IncludeOptional /etc/apache2/modsec/plugins/*-config.conf
IncludeOptional /etc/apache2/modsec/plugins/*-before.conf
IncludeOptional /etc/apache2/modsec/rules/*.conf
IncludeOptional /etc/apache2/modsec/plugins/*-after.conf
</IfModule>
EOF
Test Apache and restart it.
sudo apachectl configtest
sudo systemctl restart apache2
Run the same local malicious-request check. A 403 response confirms that the manual CRS 4 include path is active in blocking mode.
curl -sS -o /dev/null -w '%{http_code}\n' 'http://127.0.0.1/?exec=/bin/bash'
403
Remove the temporary download directory after the signature check and extraction are complete.
rm -rf "$workdir"
unset GNUPGHOME
Get Started with ModSecurity Tuning
A fresh WAF install needs a tuning phase. CRS is intentionally generic, so it catches a broad range of attack patterns, but real applications can send unusual payloads that look suspicious. Tune from logs and specific rule IDs instead of disabling large rule groups too early.
Use DetectionOnly for First Observation
Use monitoring mode while you browse the application, submit forms, sign in to admin pages, upload files, and run API requests that normal users depend on.
sudo sed -i 's/^SecRuleEngine .*/SecRuleEngine DetectionOnly/' /etc/modsecurity/modsecurity.conf
sudo systemctl reload apache2
After reviewing the audit log and adding targeted exclusions, switch to blocking mode.
sudo sed -i 's/^SecRuleEngine .*/SecRuleEngine On/' /etc/modsecurity/modsecurity.conf
sudo systemctl reload apache2
Understand Paranoia Levels
CRS uses paranoia levels to decide how aggressively rules inspect requests. Level 1 is the default and the right starting point for most public sites. Higher levels can catch more suspicious patterns, but they also increase false positives and require more exclusions.
| Level | Behavior | Use It When |
|---|---|---|
| PL1 | Default CRS coverage with the lowest normal false-positive rate | You are protecting a typical public website or application |
| PL2 | More rules and stricter request inspection | You can review logs regularly and tune application-specific issues |
| PL3 | Strict detection that often needs careful exclusions | The application is high risk and you have a tuning process |
| PL4 | Maximum CRS strictness | Specialized environments where false positives are acceptable or heavily managed |
For CRS 4 installed under /etc/apache2/modsec, edit /etc/apache2/modsec/crs-setup.conf. For the packaged CRS path, inspect the packaged setup file and loader under /usr/share/modsecurity-crs. Keep local customizations documented because package updates and manual CRS upgrades can replace example files.
Enable Application-Specific Exclusions
CRS ships application exclusion packages for common software. If you run WordPress, phpMyAdmin, Drupal, Nextcloud, or another supported application, enable the relevant exclusions in crs-setup.conf instead of writing broad custom exclusions first. If the protected site is a WordPress stack, the WordPress with Apache on Debian guide is a useful companion for the application layer.
For CRS 4, check the current crs-setup.conf.example comments and plugin documentation before enabling older CRS 3 exclusion patterns. CRS 4 changed parts of the exclusion package workflow, so use the comments in the installed release rather than copying a stale rule from an older CRS 3 tutorial.
Create a Targeted Rule Exclusion
Use the before-CRS exclusion file for exceptions that must apply before normal CRS rules run. For the manual CRS 4 path, open this file:
sudo nano /etc/apache2/modsec/rules/REQUEST-900-EXCLUSION-RULES-BEFORE-CRS.conf
For the packaged CRS path, locate the active exclusion file from the package loader before editing:
grep -R "EXCLUSION-RULES" /usr/share/modsecurity-crs /etc/modsecurity 2>/dev/null
A narrow path-based exclusion is safer than disabling a rule globally. This example disables SQL injection and XSS rule ranges only for a known internal API path.
SecRule REQUEST_URI "@beginsWith /admin/api/" "id:1000,phase:1,pass,nolog,ctl:ruleRemoveById=941000-942999"
Reload Apache after exclusions and repeat the request that was previously blocked.
sudo apachectl configtest
sudo systemctl reload apache2
Connect ModSecurity with Broader Apache Security
ModSecurity inspects HTTP content, but it does not replace TLS, application updates, least-privilege permissions, or abuse controls. After the WAF is working, secure public sites with Let’s Encrypt for Apache on Debian and consider Fail2Ban on Debian for repeated authentication or abuse patterns that are better handled by IP-based jails.
Update ModSecurity and OWASP CRS
Update Package-Managed ModSecurity and CRS
When you use either package-managed path, normal APT updates handle the Apache module and packaged CRS. Digitalwave users receive the Digitalwave candidate on supported architectures; Debian archive users receive Debian’s candidate. To update only this package family, use --only-upgrade.
sudo apt update
sudo apt install --only-upgrade libapache2-mod-security2 modsecurity-crs
Restart Apache after a ModSecurity or CRS package update so the running workers use the refreshed module and rules.
sudo apachectl configtest
sudo systemctl restart apache2
Create a CRS 4 Update Helper
If you installed CRS 4 manually, create a helper that downloads the latest signed CRS release, backs up the current CRS directory, preserves your active crs-setup.conf and standard exclusion files, tests Apache, and rolls back if the syntax test fails.
sudo tee /usr/local/sbin/update-owasp-crs > /dev/null <<'EOF'
#!/usr/bin/env bash
set -euo pipefail
CRS_DIR="/etc/apache2/modsec"
workdir=$(mktemp -d)
cleanup() {
rm -rf "$workdir"
}
trap cleanup EXIT
latest_json=$(curl -fsSL https://api.github.com/repos/coreruleset/coreruleset/releases/latest)
crs_tag=$(printf '%s\n' "$latest_json" | jq -r '.tag_name')
case "$crs_tag" in
v*) ;;
*)
echo "Could not resolve the latest CRS tag." >&2
exit 1
;;
esac
crs_version=${crs_tag#v}
release_url="https://github.com/coreruleset/coreruleset/releases/download/${crs_tag}"
archive="$workdir/coreruleset-${crs_version}-minimal.tar.gz"
signature="$workdir/coreruleset-${crs_version}-minimal.tar.gz.asc"
curl -fsSLo "$archive" "${release_url}/coreruleset-${crs_version}-minimal.tar.gz"
curl -fsSLo "$signature" "${release_url}/coreruleset-${crs_version}-minimal.tar.gz.asc"
GNUPGHOME="$workdir/gnupg"
export GNUPGHOME
mkdir -m 700 "$GNUPGHOME"
gpg --fetch-key https://coreruleset.org/security.asc >/dev/null 2>&1
gpg --verify "$signature" "$archive"
backup="/etc/apache2/modsec-backup-${crs_version}-$(date +%Y%m%d%H%M%S)"
backup_created=0
if [ -d "$CRS_DIR" ]; then
sudo cp -a "$CRS_DIR" "$backup"
backup_created=1
fi
sudo mkdir -p "$CRS_DIR"
sudo rm -rf "${CRS_DIR:?}"/*
sudo tar xzf "$archive" -C "$CRS_DIR" --strip-components=1
if [ "$backup_created" -eq 1 ] && [ -f "$backup/crs-setup.conf" ]; then
sudo cp "$backup/crs-setup.conf" "$CRS_DIR/crs-setup.conf"
else
sudo cp "$CRS_DIR/crs-setup.conf.example" "$CRS_DIR/crs-setup.conf"
fi
for example in \
REQUEST-900-EXCLUSION-RULES-BEFORE-CRS.conf.example \
RESPONSE-999-EXCLUSION-RULES-AFTER-CRS.conf.example; do
target="${example%.example}"
if [ "$backup_created" -eq 1 ] && [ -f "$backup/rules/$target" ]; then
sudo cp "$backup/rules/$target" "$CRS_DIR/rules/$target"
else
sudo cp "$CRS_DIR/rules/$example" "$CRS_DIR/rules/$target"
fi
done
if sudo apachectl configtest; then
sudo systemctl reload apache2
echo "OWASP CRS $crs_tag installed and Apache reloaded."
else
echo "Apache syntax check failed." >&2
if [ "$backup_created" -eq 1 ]; then
echo "Restoring $backup." >&2
sudo rm -rf "${CRS_DIR:?}"/*
sudo cp -a "$backup"/. "$CRS_DIR"/
sudo apachectl configtest
else
echo "No previous CRS backup was available for automatic rollback." >&2
fi
exit 1
fi
EOF
sudo chmod +x /usr/local/sbin/update-owasp-crs
Run the helper manually after reviewing recent CRS release notes or when you are ready to test a rule update.
sudo /usr/local/sbin/update-owasp-crs
A successful run prints the signature check, Apache syntax result, and resolved CRS tag. The final line changes when GitHub publishes a newer CRS release.
gpg: Good signature from "OWASP Core Rule Set <security@coreruleset.org>" [unknown] Syntax OK OWASP CRS v4.26.0 installed and Apache reloaded.
The helper keeps backups under /etc/apache2/ with names that start with modsec-backup- and include the CRS version plus a timestamp. If you keep custom plugins or nonstandard rule files under the CRS directory, restore them from the backup after reviewing compatibility with the new release. Remove old backups only after the site has run normally with the new rules.
Troubleshoot ModSecurity on Debian
APT Reports a Digitalwave 404 Error
A 404 during sudo apt update usually means the source file points at the wrong repository path, suite, or architecture. Use the Digitalwave URI with /debian/ and the verified ${VERSION_CODENAME}-backports suite. If the error contains binary-arm64, remove the Digitalwave source and use the Debian archive package path.
dpkg --print-architecture
grep -R "modsecurity.digitalwave.hu" /etc/apt/sources.list /etc/apt/sources.list.d 2>/dev/null
If you are on arm64, remove the Digitalwave source and install the Debian archive packages. If you are on amd64 or i386 and find an older file that lacks /debian/ after the host or lists both trixie and trixie-backports, remove or replace it with the guarded DEB822 source, then refresh APT.
sudo rm -f /etc/apt/sources.list.d/digitalwave-modsecurity.list
sudo rm -f /etc/apt/sources.list.d/digitalwave-modsecurity.sources
sudo apt update
Apache Fails After Editing security2.conf
Run the syntax test first and read the failing include path. Missing files normally mean the configured CRS path does not match the method you installed.
sudo apachectl configtest
For the package-managed CRS path, the default include should reference the packaged CRS loader under /usr/share/modsecurity-crs. For the manual CRS 4 path, security2.conf should reference /etc/apache2/modsec/crs-setup.conf and /etc/apache2/modsec/rules/*.conf.
The Test Request Returns 200 Instead of 403
A 200 response means the request was not blocked. That can be correct in DetectionOnly mode, but in blocking mode check the rule engine, module, CRS includes, and Apache reload state.
grep '^SecRuleEngine' /etc/modsecurity/modsecurity.conf
sudo apachectl -M 2>/dev/null | grep security2
sudo apachectl -t -D DUMP_INCLUDES | grep -E 'modsecurity|modsec|crs|security2'
sudo systemctl restart apache2
If SecRuleEngine is On, the module is loaded, and no CRS files appear in the include dump, fix /etc/apache2/mods-available/security2.conf for the method you chose.
Legitimate Requests Are Blocked
Do not disable ModSecurity globally as the first response. Find the rule ID, confirm the request path or parameter, and add the narrowest exclusion that lets the legitimate request through.
sudo grep -E 'id "|Access denied|ModSecurity' /var/log/apache2/modsec_audit.log | tail -n 40
When the site is busy, it can be easier to reproduce the blocked action once and then inspect the newest audit entries immediately. Use the rule ID from your own log entry, not an example ID copied from another site.
The Audit Log Grows Too Quickly
Busy sites can create large ModSecurity audit logs. Add a logrotate file if your Debian system does not already rotate the audit log for your chosen package layout.
sudo tee /etc/logrotate.d/modsecurity > /dev/null <<'EOF'
/var/log/apache2/modsec_audit.log {
daily
rotate 14
missingok
compress
delaycompress
notifempty
create 640 root adm
}
EOF
Test the logrotate configuration without forcing an actual rotation.
sudo logrotate -d /etc/logrotate.d/modsecurity
Remove ModSecurity from Debian
Removal depends on whether you used only packaged CRS or also installed CRS 4 manually. Disable the Apache module first, then remove packages, manual CRS files, the update helper, and the Digitalwave APT source if it was used.
Disable the Apache Module
sudo a2dismod security2
sudo systemctl restart apache2
Remove Packages and Configuration Files
Purge the Apache module package and packaged CRS.
sudo apt remove --purge libapache2-mod-security2 modsecurity-crs
The next commands delete ModSecurity configuration, manual CRS files, CRS backups, the optional update helper, and any Digitalwave repository files. Back up custom exclusions before running them.
sudo rm -rf /etc/modsecurity
sudo rm -rf /etc/apache2/modsec
sudo rm -rf /etc/apache2/modsec-backup-*
sudo rm -f /etc/logrotate.d/modsecurity
sudo rm -f /usr/local/sbin/update-owasp-crs
sudo rm -f /etc/apt/sources.list.d/digitalwave-modsecurity.sources
sudo rm -f /etc/apt/sources.list.d/digitalwave-modsecurity.list
sudo rm -f /etc/apt/preferences.d/99modsecurity
sudo rm -f /usr/share/keyrings/digitalwave-modsecurity.gpg
Refresh APT after removing repository files so package policy no longer shows Digitalwave as a source.
sudo apt update
apt-cache policy libapache2-mod-security2 modsecurity-crs
Review unused dependencies before removing them. If the simulation lists only packages that were installed for this workflow, run the real autoremove command.
apt-get -s autoremove --purge
sudo apt autoremove --purge
Verify the Module Is Gone
if sudo apachectl -M 2>/dev/null | grep -q security2; then
echo "ModSecurity is still loaded."
else
echo "ModSecurity module is not loaded."
fi
ModSecurity module is not loaded.
Conclusion
Apache is running with ModSecurity and a verified OWASP CRS path on Debian, with package ownership, architecture-aware source choice, rule-engine mode, audit logs, updates, and removal covered. Keep the WAF in monitoring mode long enough to tune real application traffic, then pair active blocking with HTTPS, application updates, and focused log review.


I agree that this is a great article. One point that is missing is that this doesn’t work on an arm64 architecture.
Thanks for the feedback, Rusty. You are right about the current Digitalwave repository path is architecture-limited. Digitalwave publishes Debian backports indexes for
amd64andi386, so anarm64host can fail when the source file uses the local architecture. ModSecurity itself is not the issue, because Debian’s ownlibapache2-mod-security2package is available forarm64. Onarm64, use the Debian archive packages for now:I have updated the article so Debian archive packages are the default path. Digitalwave is now treated as an optional newer-backports method for
amd64/i386only unless its repository addsarm64packages.Not a bad article, everything is explained in detail, but some parts feel overly drawn out. I liked that it covers not only the installation of ModSecurity but also the setup of the OWASP Core Rule Set. This is useful for those just starting to explore web security. The section on paranoia levels and handling false positives stood out—it clearly highlights what to focus on to ensure stable system performance. One thing I wish it had is more practical examples for setting up exclusions for popular applications like WordPress or phpMyAdmin. Overall, it’s a solid resource, especially for beginners.
Thanks Alex, I could not cover too much more into OWASP core rule set as the needs a separate beginners guide and probably advanced guides, so many possibilities with the core rule set to customize, whitelist, create custom options and much more. The guide was just focused on installing it and doing a brief run through as you mentioned.
Appreciate the feedback.