How to Install ModSecurity and OWASP CRS with Apache on Ubuntu 26.04, 24.04 and 22.04

Install ModSecurity with OWASP CRS on Ubuntu 26.04, 24.04, and 22.04. Block SQL injection, XSS, and common web attacks on Apache.

Last updatedAuthorJoshua JamesRead time8 minGuide typeUbuntu

A web application firewall becomes useful when it blocks bad requests before they reach WordPress, phpMyAdmin, APIs, or other code running behind Apache. You can install ModSecurity with Apache on Ubuntu through the default repositories, then add the OWASP Core Rule Set (CRS) for SQL injection, cross-site scripting, command injection, and other common attack patterns.

This Apache-focused workflow covers Ubuntu 26.04 (resolute), Ubuntu 24.04 (noble), and Ubuntu 22.04 (jammy). It uses Ubuntu’s packaged ModSecurity and CRS path first, then shows the optional Digitalwave repository and GitHub CRS 4.x path for readers who need newer rule files or a newer ModSecurity 2 module.

These commands do not configure ModSecurity for Nginx. Ubuntu’s Nginx connector uses a different package and configuration path, commonly involving libnginx-mod-http-modsecurity, so treat Nginx searches and the owasp/modsecurity-crs:apache container image as separate workflows from this Apache package guide.

Install ModSecurity with Apache on Ubuntu

Start by refreshing APT metadata and applying available package updates:

sudo apt update
sudo apt upgrade

These commands use sudo for tasks that need root privileges. If your account is not configured for sudo yet, add an administrator account first with the guide on how to add a new user to sudoers on Ubuntu.

Install Apache, the ModSecurity Apache module, and the packaged OWASP CRS files in one step:

sudo apt install apache2 libapache2-mod-security2 modsecurity-crs

The install enables the Apache security2 module automatically. If you need a fuller Apache baseline before adding a WAF, use the separate Apache installation guide for Ubuntu first.

Older Apache references may call the module mod_security or mod_security2. On Ubuntu, install the libapache2-mod-security2 package and verify Apache reports the loaded module as security2_module.

Check the installed package revisions:

dpkg-query -W apache2 libapache2-mod-security2 modsecurity-crs

Relevant output on Ubuntu 26.04 includes:

apache2                    2.4.66-2ubuntu2
libapache2-mod-security2  2.9.12-2build1
modsecurity-crs           3.3.8-1

Confirm that Apache loaded the ModSecurity module:

sudo apachectl -M | grep security

Expected output:

 security2_module (shared)

If the module is missing, enable it and restart Apache:

sudo a2enmod security2
sudo systemctl restart apache2

Configure ModSecurity Blocking Mode

Ubuntu ships the recommended ModSecurity configuration as a sample file. Copy it into the active configuration path:

sudo cp /etc/modsecurity/modsecurity.conf-recommended /etc/modsecurity/modsecurity.conf

The sample file starts in DetectionOnly mode, which logs matched rules without blocking traffic. To turn on active blocking immediately, replace the rule engine line:

sudo sed -i 's/^SecRuleEngine DetectionOnly/SecRuleEngine On/' /etc/modsecurity/modsecurity.conf

For production sites with existing traffic, consider leaving SecRuleEngine DetectionOnly in place for a short observation period, then switch to On after you review false positives in /var/log/apache2/modsec_audit.log.

Check the final setting:

grep '^SecRuleEngine' /etc/modsecurity/modsecurity.conf
SecRuleEngine On

The default audit log parts are enough for basic testing, but adding matched rule data makes troubleshooting easier. Edit the configuration file:

sudo nano /etc/modsecurity/modsecurity.conf

Find SecAuditLogParts and use this value:

SecAuditLogParts ABCEFHJKZ

Test Apache syntax and restart the service:

sudo apachectl configtest
sudo systemctl restart apache2
Syntax OK

Verify ModSecurity and OWASP CRS Blocking

Send a local request that should trigger a CRS command-injection rule:

curl -I "http://localhost/?exec=/bin/bash"

Relevant headers should show a 403 Forbidden response:

HTTP/1.1 403 Forbidden
Content-Type: text/html; charset=iso-8859-1

If the same test returns 200 OK, check that SecRuleEngine is set to On, Apache has restarted, and the CRS include file is loaded through /usr/share/modsecurity-crs/owasp-crs.load.

Compare ModSecurity and CRS Package Versions

Ubuntu’s default repositories provide a working Apache WAF path on all three supported LTS releases, but the exact package versions differ:

Ubuntu ReleaseDefault Apache ModuleDefault CRS PackageCRS 4.x From GitHub
Ubuntu 26.04 (resolute)libapache2-mod-security2 2.9.12-2build1modsecurity-crs 3.3.8-1Compatible with default ModSecurity
Ubuntu 24.04 (noble)libapache2-mod-security2 2.9.7-1build3modsecurity-crs 3.3.5-2Compatible with default ModSecurity
Ubuntu 22.04 (jammy)libapache2-mod-security2 2.9.5-1modsecurity-crs 3.3.2-1Requires a newer ModSecurity 2 package first

Use the packaged CRS path when you want APT-managed updates and the least maintenance. Use the GitHub CRS 4.x path only when you specifically need the newest CRS branch and are comfortable maintaining the rule files outside APT.

Use Digitalwave ModSecurity Packages on Ubuntu 22.04 or 24.04

The Digitalwave repository is an optional third-party source for newer ModSecurity packages. It is most useful on Ubuntu 22.04 when you want CRS 4.x, because Ubuntu 22.04’s default ModSecurity 2.9.5 package lacks the @within operator used by CRS 4.x.

Digitalwave currently publishes Apache module packages for Ubuntu 24.04 (noble) and 22.04 (jammy) backports. Its Ubuntu 26.04 (resolute) metadata does not currently provide libapache2-mod-security2, and Ubuntu 26.04 already has a CRS 4.x-compatible default Apache module.

Install the tools needed for the repository key and source file:

sudo apt install ca-certificates curl gpg

Import the repository signing key. The --yes flag makes reruns overwrite the local keyring without an interactive prompt:

curl -fsSL https://modsecurity.digitalwave.hu/archive.key | sudo gpg --dearmor --yes -o /usr/share/keyrings/digitalwave-modsecurity.gpg

Create the DEB822 source file. The command limits the source to the verified jammy-backports or noble-backports suite and writes the file with tee because normal shell redirection would not inherit sudo:

. /etc/os-release

case "$VERSION_CODENAME" in
    jammy|noble) ;;
    *)
        echo "Digitalwave Apache packages are documented here only for Ubuntu 24.04 (noble) and 22.04 (jammy)."
        exit 1
        ;;
esac

if [ "$(dpkg --print-architecture)" != "amd64" ]; then
    echo "This Digitalwave repository example is limited to amd64 packages."
    exit 1
fi

printf '%s\n' \
'Types: deb' \
'URIs: https://modsecurity.digitalwave.hu/ubuntu/' \
"Suites: ${VERSION_CODENAME}-backports" \
'Components: main' \
'Architectures: amd64' \
'Signed-By: /usr/share/keyrings/digitalwave-modsecurity.gpg' \
| sudo tee /etc/apt/sources.list.d/digitalwave-modsecurity.sources > /dev/null

Pin the Digitalwave ModSecurity packages above the Ubuntu archive packages:

printf '%s\n' \
'Package: libapache2-mod-security2 libapache2-modsecurity modsecurity-crs libmodsecurity3 libmodsecurity-dev' \
'Pin: origin modsecurity.digitalwave.hu' \
'Pin-Priority: 900' \
| sudo tee /etc/apt/preferences.d/99modsecurity > /dev/null

Refresh APT metadata and install the newer packages:

sudo apt update
sudo apt install libapache2-mod-security2 modsecurity-crs

Verify that the Digitalwave candidate wins:

apt-cache policy libapache2-mod-security2 modsecurity-crs

Relevant output on Ubuntu 22.04 includes:

libapache2-mod-security2:
  Installed: 2.9.14~pre-20260428-ubuntu0.22.04.5
  Candidate: 2.9.14~pre-20260428-ubuntu0.22.04.5
     2.9.14~pre-20260428-ubuntu0.22.04.5 900
        500 https://modsecurity.digitalwave.hu/ubuntu jammy-backports/main amd64 Packages
modsecurity-crs:
  Installed: 3.3.9-20260329-ubuntu0.22.04.5
  Candidate: 3.3.9-20260329-ubuntu0.22.04.5

Run the Apache syntax check again after the package change:

sudo apachectl configtest
sudo systemctl restart apache2

Install OWASP CRS 4.x from GitHub

The GitHub path replaces the packaged CRS include with a manually managed CRS 4.x tree under /etc/apache2/modsec. Use it on Ubuntu 26.04 or 24.04 with the default ModSecurity package, or on Ubuntu 22.04 only after installing a newer ModSecurity package such as the Digitalwave package above.

Install curl and jq. The version lookup uses jq so the command parses GitHub’s JSON response instead of scraping it as plain text; the curl command guide covers the download flags if you want more detail.

sudo apt install curl jq

Resolve the latest stable CRS release:

CRS_VERSION=$(curl -fsSL https://api.github.com/repos/coreruleset/coreruleset/releases/latest | jq -r '.tag_name')

if [ -z "$CRS_VERSION" ] || [ "$CRS_VERSION" = "null" ]; then
    echo "Could not determine the latest CRS release."
    exit 1
fi

echo "$CRS_VERSION"
v4.x.x

Download and extract that release into /etc/apache2/modsec:

TMP_DIR=$(mktemp -d)
curl -fL "https://github.com/coreruleset/coreruleset/archive/refs/tags/${CRS_VERSION}.tar.gz" -o "$TMP_DIR/crs.tar.gz"

sudo rm -rf /etc/apache2/modsec
sudo install -d -m 0755 /etc/apache2/modsec
sudo tar xzf "$TMP_DIR/crs.tar.gz" -C /etc/apache2/modsec --strip-components=1
rm -rf "$TMP_DIR"

Create the active CRS setup file and exclusion files from the upstream examples:

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

Back up the packaged Apache module include file, then replace it with a GitHub CRS include path:

sudo cp /etc/apache2/mods-available/security2.conf /etc/apache2/mods-available/security2.conf.bak

sudo tee /etc/apache2/mods-available/security2.conf > /dev/null <<'EOF'
<IfModule security2_module>
    SecDataDir /var/cache/modsecurity
    IncludeOptional /etc/modsecurity/*.conf
    Include /etc/apache2/modsec/crs-setup.conf
    Include /etc/apache2/modsec/rules/*.conf
</IfModule>
EOF

Test and reload Apache:

sudo apachectl configtest
sudo systemctl restart apache2
Syntax OK

Run the same blocking probe again:

curl -I "http://localhost/?exec=/bin/bash"
HTTP/1.1 403 Forbidden
Content-Type: text/html; charset=iso-8859-1

Tune OWASP CRS Configuration

The CRS setup file location depends on the install method. Packaged CRS uses /etc/modsecurity/crs/crs-setup.conf, while the GitHub CRS path above uses /etc/apache2/modsec/crs-setup.conf.

For packaged CRS, edit this file:

sudo nano /etc/modsecurity/crs/crs-setup.conf

For GitHub CRS, edit this file instead:

sudo nano /etc/apache2/modsec/crs-setup.conf

Understand Anomaly Scoring Mode

CRS uses anomaly scoring mode by default. A single rule match adds points to the request score, and the blocking evaluation denies the request when the score reaches the configured threshold. This is less brittle than blocking every single rule match immediately.

  • Inbound anomaly threshold: the default value is 5, which blocks suspicious requests at 5 or more points.
  • Outbound anomaly threshold: the default value is 4, which blocks suspicious responses at 4 or more points.

Higher thresholds reduce false positives but allow more suspicious traffic through. Lower thresholds block more aggressively and usually require more tuning.

Choose a CRS Paranoia Level

Paranoia levels control how aggressive the CRS rule set becomes:

  1. Paranoia Level 1: default coverage with the lowest false-positive risk for most sites.
  2. Paranoia Level 2: stronger inspection that may require tuning on complex applications.
  3. Paranoia Level 3: strict detection for high-security deployments where false positives are expected.
  4. Paranoia Level 4: maximum rule coverage for specialized use cases, not a normal first setting.

Start at level 1. Raise the level only after you have reviewed audit logs and added exclusions for legitimate traffic that your application needs.

Enable Application-Specific CRS Exclusions

CRS includes optional exclusions for applications such as WordPress, phpMyAdmin, Drupal, Nextcloud, phpBB, DokuWiki, cPanel, and XenForo. In crs-setup.conf, enable only the exclusions that match software actually running behind Apache.

SecAction \
 "id:900130,\
  phase:1,\
  nolog,\
  pass,\
  t:none,\
  setvar:tx.crs_exclusions_wordpress=1,\
  setvar:tx.crs_exclusions_phpmyadmin=1"

Create Custom CRS Rule Exclusions

Use the before-CRS exclusion file for most local allow rules. Packaged CRS and GitHub CRS use different paths:

For packaged CRS, edit this file:

sudo nano /etc/modsecurity/crs/REQUEST-900-EXCLUSION-RULES-BEFORE-CRS.conf

For GitHub CRS, edit this file instead:

sudo nano /etc/apache2/modsec/rules/REQUEST-900-EXCLUSION-RULES-BEFORE-CRS.conf

To disable a rule range for a specific URL prefix, add an exclusion like this:

SecRule REQUEST_URI "@beginsWith /admin/api/" "id:1000,phase:1,pass,nolog,ctl:ruleRemoveById=941000-942999"

To disable inspection for a trusted management IP address, use @ipMatch:

SecRule REMOTE_ADDR "@ipMatch 192.168.1.100" "id:1001,phase:1,allow,nolog,ctl:ruleEngine=off"

For a whole trusted subnet:

SecRule REMOTE_ADDR "@ipMatch 10.0.0.0/8" "id:1002,phase:1,allow,nolog,ctl:ruleEngine=off"

To disable a specific rule globally, use its rule ID:

SecRuleRemoveById 942100

Use the audit log to find the rule ID that caused a false positive. The grep command guide is useful when you need to filter larger ModSecurity logs repeatedly:

sudo grep "$(date +%d/%b/%Y)" /var/log/apache2/modsec_audit.log | grep -A 10 "403"

Look for the [id "942100"] pattern or a similar rule ID in the matching audit entry, then add the narrowest exclusion that fixes the legitimate request.

Configure ModSecurity Audit Log Rotation

ModSecurity audit logs can grow quickly after active blocking is enabled. Create a dedicated logrotate file:

sudo nano /etc/logrotate.d/modsecurity

Add this configuration:

/var/log/apache2/modsec_audit.log {
    rotate 14
    daily
    missingok
    compress
    delaycompress
    notifempty
    create 640 root adm
}

The file keeps 14 daily logs, compresses old entries, skips empty rotations, and recreates the audit log with permissions readable by root and the adm group.

Update OWASP CRS from GitHub

If you installed CRS from Ubuntu or Digitalwave packages, update it with APT. Use this helper only for the manual GitHub CRS 4.x tree under /etc/apache2/modsec.

sudo tee /usr/local/bin/update-owasp-crs > /dev/null <<'SCRIPT'
#!/usr/bin/env bash
set -euo pipefail

if [ "$(id -u)" -ne 0 ]; then
    echo "Run this command with sudo: sudo update-owasp-crs"
    exit 1
fi

CRS_DIR="/etc/apache2/modsec"
BACKUP_ROOT="/etc/apache2"

if ! command -v curl >/dev/null || ! command -v jq >/dev/null; then
    echo "Missing curl or jq. Install them with: sudo apt install curl jq"
    exit 1
fi

LATEST_VERSION=$(curl -fsSL https://api.github.com/repos/coreruleset/coreruleset/releases/latest | jq -r '.tag_name')
if [ -z "$LATEST_VERSION" ] || [ "$LATEST_VERSION" = "null" ]; then
    echo "Could not determine the latest CRS release from GitHub."
    exit 1
fi
LATEST_CLEAN=${LATEST_VERSION#v}

CURRENT_VERSION=""
if [ -f "$CRS_DIR/CHANGES.md" ]; then
    CURRENT_VERSION=$(grep -m1 '^## Version ' "$CRS_DIR/CHANGES.md" | awk '{print $3}' || true)
fi

echo "Current CRS version: ${CURRENT_VERSION:-not installed}"
echo "Latest CRS version: ${LATEST_VERSION}"

if [ "$CURRENT_VERSION" = "$LATEST_CLEAN" ]; then
    echo "CRS is already up to date."
    exit 0
fi

TMP_DIR=$(mktemp -d)
PRESERVE_DIR=$(mktemp -d)
trap 'rm -rf "$TMP_DIR" "$PRESERVE_DIR"' EXIT

if [ -f "$CRS_DIR/crs-setup.conf" ]; then
    install -D -m 0644 "$CRS_DIR/crs-setup.conf" "$PRESERVE_DIR/crs-setup.conf"
fi

for relpath in \
    rules/REQUEST-900-EXCLUSION-RULES-BEFORE-CRS.conf \
    rules/RESPONSE-999-EXCLUSION-RULES-AFTER-CRS.conf; do
    if [ -f "$CRS_DIR/$relpath" ]; then
        install -D -m 0644 "$CRS_DIR/$relpath" "$PRESERVE_DIR/$relpath"
    fi
done

echo "Downloading CRS ${LATEST_VERSION}..."
curl -fL "https://github.com/coreruleset/coreruleset/archive/refs/tags/${LATEST_VERSION}.tar.gz" -o "$TMP_DIR/crs.tar.gz"

echo "Installing CRS ${LATEST_VERSION}..."
if [ -d "$CRS_DIR" ]; then
    BACKUP_DIR="${BACKUP_ROOT}/modsec-backup-$(date +%Y%m%d-%H%M%S)"
    cp -a "$CRS_DIR" "$BACKUP_DIR"
    echo "Backup saved to $BACKUP_DIR"
fi

rm -rf "$CRS_DIR"
install -d -m 0755 "$CRS_DIR"
tar xzf "$TMP_DIR/crs.tar.gz" -C "$CRS_DIR" --strip-components=1

if [ -f "$PRESERVE_DIR/crs-setup.conf" ]; then
    cp "$PRESERVE_DIR/crs-setup.conf" "$CRS_DIR/crs-setup.conf"
else
    cp "$CRS_DIR/crs-setup.conf.example" "$CRS_DIR/crs-setup.conf"
fi

for relpath in \
    rules/REQUEST-900-EXCLUSION-RULES-BEFORE-CRS.conf \
    rules/RESPONSE-999-EXCLUSION-RULES-AFTER-CRS.conf; do
    if [ -f "$PRESERVE_DIR/$relpath" ]; then
        cp "$PRESERVE_DIR/$relpath" "$CRS_DIR/$relpath"
    else
        cp "$CRS_DIR/${relpath}.example" "$CRS_DIR/$relpath"
    fi
done

apachectl configtest
systemctl reload apache2

echo "CRS updated successfully to ${LATEST_VERSION}."
SCRIPT
sudo chmod +x /usr/local/bin/update-owasp-crs

Run the helper:

sudo update-owasp-crs

When the installed release already matches GitHub’s latest release, the output looks like this:

Current CRS version: 4.x.x
Latest CRS version: v4.x.x
CRS is already up to date.

When an update is available, the helper backs up the existing CRS directory, preserves your main setup and exclusion files, downloads the new release, tests Apache, and reloads the service only after the syntax check passes.

Troubleshoot ModSecurity with Apache

Apache Fails After a ModSecurity Change

Start with the Apache syntax check:

sudo apachectl configtest

Common causes include duplicate CRS includes, missing files in security2.conf, or an exclusion rule that reuses an existing rule ID. Check which ModSecurity and CRS files Apache includes:

sudo apachectl -t -D DUMP_INCLUDES | grep -E "modsecurity|modsec|crs"

Then inspect the latest Apache errors. The tail command examples are useful when you need to watch log files during repeated config tests:

sudo tail -n 50 /var/log/apache2/error.log

ModSecurity Does Not Block the Test Request

Verify the rule engine first:

grep '^SecRuleEngine' /etc/modsecurity/modsecurity.conf
SecRuleEngine On

If the file still says DetectionOnly, switch it to On, run sudo apachectl configtest, and restart Apache. If the value is already correct, confirm that the CRS include path is present in the include dump command from the previous section.

Legitimate Requests Are Blocked

Find the matching rule ID in the audit log:

sudo grep "$(date +%d/%b/%Y)" /var/log/apache2/modsec_audit.log | grep -A 10 "403"

Prefer a narrow exclusion for the affected URI, parameter, or trusted management IP instead of disabling a broad rule group globally. Keep broad exclusions temporary until you understand the exact rule and request pattern.

CRS 4.x Shows Unknown Operator on Ubuntu 22.04

Ubuntu 22.04’s default libapache2-mod-security2 package is ModSecurity 2.9.5. CRS 4.x can fail with this error because it requires a newer operator set:

Unknown operator: @within

Use the packaged CRS 3.x path on Ubuntu 22.04, or install a newer ModSecurity 2 package first before switching to GitHub CRS 4.x.

Remove ModSecurity from Ubuntu

Disable the Apache module and restart Apache first:

sudo a2dismod security2
sudo systemctl restart apache2

Remove the Ubuntu packages:

sudo apt remove --purge libapache2-mod-security2 modsecurity-crs

The next command permanently deletes local ModSecurity configuration, manual GitHub CRS files, log rotation settings, and the optional updater. Back up custom exclusions first if you need to keep them, and do not remove /etc/modsecurity if another ModSecurity connector still uses it.

sudo rm -rf /etc/modsecurity
sudo rm -rf /etc/apache2/modsec
sudo rm -f /etc/logrotate.d/modsecurity
sudo rm -f /usr/local/bin/update-owasp-crs

If you added the Digitalwave repository, remove its source, pin, and keyring, then refresh APT metadata:

sudo rm -f /etc/apt/sources.list.d/digitalwave-modsecurity.sources
sudo rm -f /etc/apt/preferences.d/99modsecurity
sudo rm -f /usr/share/keyrings/digitalwave-modsecurity.gpg
sudo apt update

Preview dependency cleanup before removing anything else:

sudo apt autoremove --dry-run

If the preview only lists packages you no longer need, run:

sudo apt autoremove

Verify the Apache module is gone. This command should return no output:

sudo apachectl -M | grep security

Additional Security Recommendations

ModSecurity adds application-layer inspection, but it is not a replacement for basic server hardening. Restrict exposed ports with the UFW firewall guide for Ubuntu, serve production sites over HTTPS, and use the Fail2Ban installation guide to slow repeated authentication or probing attempts.

Conclusion

ModSecurity is active in Apache with OWASP CRS rules loaded and a local 403 test confirming that malicious request patterns are blocked. Keep packaged CRS installs current through APT, maintain GitHub CRS installs with the updater, and review audit logs before tightening paranoia levels or adding broad exclusions.

Share this guide

Help another Linux user troubleshoot faster

Share this guide with someone troubleshooting Linux systems or saving it for later.

Follow LinuxCapable

Want more LinuxCapable guides in Google?

Add LinuxCapable as a preferred source so Google can show our tutorials more often in Top Stories and mark them as preferred in AI Mode and AI Overviews when relevant.

Add LinuxCapable as a preferred source on Google
Search LinuxCapable

Need another guide?

Search LinuxCapable for package installs, commands, troubleshooting, and follow-up guides related to what you just read.

Found this guide useful?

Support LinuxCapable to keep tutorials free and up to date.

Buy me a coffeeBuy me a coffee
Before commenting, please review our Comments Policy.
Formatting tips for your comment

You can use basic HTML to format your comment. Useful tags currently allowed in published comments:

You type Result
<code>command</code> command
<strong>bold</strong> bold
<em>italic</em> italic
<a href="https://example.com">link</a> link
<blockquote>quote</blockquote> quote block

Got a Question or Feedback?

We read and reply to every comment - let us know how we can help or improve this guide.

Verify before posting: