Enabling ModSecurity adds a web application firewall layer to Apache that inspects HTTP traffic and detects common exploit patterns before requests reach the application.

On Ubuntu and other Debian-style systems, ModSecurity is loaded as the security2_module and reads configuration and rule files via /etc/apache2/mods-available/security2.conf. The default module configuration typically pulls in any /etc/modsecurity/*.conf files, and the OWASP ModSecurity Core Rule Set (CRS) can be loaded from /usr/share/modsecurity-crs to provide a baseline ruleset.

Blocking mode can produce false positives and break logins, uploads, or APIs until exclusions are tuned. Starting in DetectionOnly mode keeps ModSecurity active while logging matches, which makes it possible to tune rules before enforcement; switching to On should be treated like a production change with monitoring and a quick rollback plan.

Steps to enable ModSecurity in Apache:

  1. Install the ModSecurity Apache module package.
    $ sudo apt update
    Hit:1 http://ports.ubuntu.com/ubuntu-ports noble InRelease
    Get:2 http://ports.ubuntu.com/ubuntu-ports noble-updates InRelease [126 kB]
    Hit:3 http://ports.ubuntu.com/ubuntu-ports noble-backports InRelease
    Hit:4 http://ports.ubuntu.com/ubuntu-ports noble-security InRelease
    Get:5 http://ports.ubuntu.com/ubuntu-ports noble-updates/main arm64 Packages [1,781 kB]
    Get:6 http://ports.ubuntu.com/ubuntu-ports noble-updates/universe arm64 Packages [1,467 kB]
    Fetched 3,375 kB in 4s (834 kB/s)
    Reading package lists...
    Building dependency tree...
    Reading state information...
    11 packages can be upgraded. Run 'apt list --upgradable' to see them.
     
    $ sudo apt install --assume-yes libapache2-mod-security2
    Reading package lists...
    Building dependency tree...
    Reading state information...
    The following additional packages will be installed:
      liblua5.1-0 libyajl2 modsecurity-crs
    ##### snipped #####
    Setting up libapache2-mod-security2 (2.9.7-1build3) ...
    apache2_invoke: Enable module security2
    ##### snipped #####
  2. Install the OWASP ModSecurity Core Rule Set package.
    $ sudo apt install --assume-yes modsecurity-crs
    Reading package lists...
    Building dependency tree...
    Reading state information...
    modsecurity-crs is already the newest version (3.3.5-2).
    modsecurity-crs set to manually installed.
    0 upgraded, 0 newly installed, 0 to remove and 0 not upgraded.
  3. Enable the security2 module.
    $ sudo a2enmod security2
    Considering dependency unique_id for security2:
    Module unique_id already enabled
    Module security2 already enabled

  4. Enable the headers module.
    $ sudo a2enmod headers
    Enabling module headers.
    To activate the new configuration, you need to run:
      systemctl restart apache2

    Many CRS deployments enable headers for rules that add or inspect HTTP headers.

  5. Create /etc/modsecurity/modsecurity.conf from the recommended template.
    $ sudo cp --update=none /etc/modsecurity/modsecurity.conf-recommended /etc/modsecurity/modsecurity.conf

    Files in /etc/modsecurity need a .conf suffix to be picked up by the default IncludeOptional /etc/modsecurity/*.conf pattern.

  6. Set SecRuleEngine to DetectionOnly for the initial reload.
    $ sudo sed --in-place 's/^SecRuleEngine .*/SecRuleEngine DetectionOnly/' /etc/modsecurity/modsecurity.conf

    DetectionOnly logs rule matches without blocking the request.

  7. Confirm the active SecRuleEngine value.
    $ sudo grep -n '^SecRuleEngine' /etc/modsecurity/modsecurity.conf
    7:SecRuleEngine DetectionOnly
  8. Confirm the CRS loader include is present.
    $ sudo grep -n 'modsecurity-crs' /etc/apache2/mods-enabled/security2.conf
    12:	IncludeOptional /usr/share/modsecurity-crs/*.load

    If the line is missing, add IncludeOptional /usr/share/modsecurity-crs/*.load inside the security2_module block in /etc/apache2/mods-available/security2.conf.

  9. Create the CRS configuration directory.
    $ sudo install --directory --mode=0755 /etc/modsecurity/crs
  10. Ensure the CRS setup file exists.
    $ sudo ls -l /etc/modsecurity/crs/crs-setup.conf
    -rw-r--r-- 1 root root 35016 Oct  2  2023 /etc/modsecurity/crs/crs-setup.conf

    Keep CRS defaults during tuning; lower anomaly thresholds gradually once false positives are under control.

  11. Test the Apache configuration for syntax errors.
    $ sudo apache2ctl -t
  12. Restart Apache to load ModSecurity.
    $ sudo systemctl restart apache2
  13. Confirm security2_module is loaded.
    $ sudo apache2ctl -M | grep security2
     security2_module (shared)
  14. Create a local test rule in /etc/modsecurity/local-test.conf.
    $ sudo tee /etc/modsecurity/local-test.conf >/dev/null <<'EOF'
    SecRule ARGS:modsectest "@streq 1" "id:1000000,phase:2,deny,status:403,log,msg:'ModSecurity local test rule hit'"
    EOF

    Rule IDs must be unique; duplicate IDs can prevent Apache from reloading.

  15. Restart Apache to load the new test rule.
    $ sudo systemctl restart apache2
  16. Record the audit log path from the SecAuditLog directive.
    $ sudo grep -n '^SecAuditLog ' /etc/modsecurity/modsecurity.conf
    205:SecAuditLog /var/log/apache2/modsec_audit.log
  17. Send a request that triggers the local test rule.
    $ curl --silent --show-error -i 'http://127.0.0.1/?modsectest=1'
    HTTP/1.1 200 OK
    Date: Sat, 10 Jan 2026 21:28:05 GMT
    Server: Apache/2.4.58 (Ubuntu)
    Last-Modified: Sat, 10 Jan 2026 21:15:28 GMT
    ETag: "29af-6480f24215cc8"
    Accept-Ranges: bytes
    Content-Length: 10671
    Vary: Accept-Encoding
    Content-Type: text/html
     
    ##### snipped #####

    DetectionOnly mode should log the match while allowing the request to complete.

  18. Confirm the test rule message appears in the audit log.
    $ sudo grep -n "ModSecurity local test rule hit" /var/log/apache2/modsec_audit.log | tail --lines=1
    386:Apache-Error: [file "apache2_util.c"] [line 275] [level 3] [client 127.0.0.1] ModSecurity: Warning. String match "1" at ARGS:modsectest. [file "/etc/modsecurity/local-test.conf"] [line "1"] [id "1000000"] [msg "ModSecurity local test rule hit"] [hostname "127.0.0.1"] [uri "/"] [unique_id "aWLEZfMECnPgWKJR8VR0DAAAAEQ"]

    Replace the path if SecAuditLog points to a different file.

  19. Switch SecRuleEngine to On to begin blocking.
    $ sudo sed --in-place 's/^SecRuleEngine .*/SecRuleEngine On/' /etc/modsecurity/modsecurity.conf

    Enforcement can block legitimate traffic until exclusions are tuned; keep a rollback path available.

  20. Test the Apache configuration for syntax errors.
    $ sudo apache2ctl -t
  21. Restart Apache to apply enforcement.
    $ sudo systemctl restart apache2
  22. Verify the local test request returns HTTP 403.
    $ curl --silent --show-error -i 'http://127.0.0.1/?modsectest=1'
    HTTP/1.1 403 Forbidden
    Date: Sat, 10 Jan 2026 21:28:05 GMT
    Server: Apache/2.4.58 (Ubuntu)
    Content-Length: 274
    Content-Type: text/html; charset=iso-8859-1
     
    ##### snipped #####

  23. Remove the local test rule file.
    $ sudo rm --force /etc/modsecurity/local-test.conf

    Restarting or reloading Apache removes the test rule from the active rule set.

  24. Reload Apache configuration.
    $ sudo systemctl reload apache2