Enabling ModSecurity in front of Apache adds a request-inspection layer that can catch common attack patterns before they reach the application. That gives the server a chance to reject suspicious traffic at the web tier instead of relying on the application to detect every payload on its own.
On current Ubuntu and Debian packaging, the libapache2-mod-security2 package loads security2_module for Apache and the module configuration in /etc/apache2/mods-available/security2.conf includes local files from /etc/modsecurity/*.conf plus the packaged OWASP ModSecurity Core Rule Set loader from /usr/share/modsecurity-crs/*.load. The recommended base policy file is /etc/modsecurity/modsecurity.conf-recommended, and the packaged CRS setup file lives at /etc/modsecurity/crs/crs-setup.conf.
CRS rules can block real login flows, uploads, JSON APIs, or admin forms until exclusions are tuned, so the safest first pass keeps SecRuleEngine in DetectionOnly mode and confirms matches in the audit log before enforcement is turned on. A local test rule makes it possible to prove that logging works, then prove that blocking works, and then remove the temporary rule before leaving the server in production service.
Related: How to enable or disable Apache modules
Related: How to test Apache configuration
Steps to enable ModSecurity in Apache:
- Open a terminal session with an account that can use sudo.
- Install the ModSecurity Apache module and the packaged CRS rules.
$ sudo apt update Hit:1 http://ports.ubuntu.com/ubuntu-ports noble InRelease Hit:2 http://ports.ubuntu.com/ubuntu-ports noble-updates InRelease Hit:3 http://ports.ubuntu.com/ubuntu-ports noble-backports InRelease Hit:4 http://ports.ubuntu.com/ubuntu-ports noble-security InRelease Reading package lists... $ sudo apt install --assume-yes libapache2-mod-security2 modsecurity-crs Reading package lists... Building dependency tree... Reading state information... The following additional packages will be installed: liblua5.1-0 libyajl2 ##### snipped ##### Setting up libapache2-mod-security2 (2.9.7-1build3) ... apache2_invoke: Enable module security2
Current Ubuntu releases already pull in modsecurity-crs when libapache2-mod-security2 is installed, but installing both package names keeps the intent explicit.
- Confirm the security2 module is enabled.
$ sudo a2enmod security2 Considering dependency unique_id for security2: Module unique_id already enabled Module security2 already enabled
The package usually enables security2 during installation. Re-running a2enmod confirms the state and restores the module if it had been disabled earlier. A separate headers step is not required for the base ModSecurity and CRS enablement path.
- Create the active ModSecurity policy file from the packaged template if it does not exist yet.
$ sudo cp --update=none /etc/modsecurity/modsecurity.conf-recommended /etc/modsecurity/modsecurity.conf
Using --update=none keeps an existing tuned file in place instead of overwriting it.
- Confirm the initial rule engine mode is DetectionOnly.
$ sudo grep -n '^SecRuleEngine' /etc/modsecurity/modsecurity.conf 7:SecRuleEngine DetectionOnly
If the copied file shows On or Off instead, change it before the first reload: sudo sed --in-place 's/^SecRuleEngine .*/SecRuleEngine DetectionOnly/' /etc/modsecurity/modsecurity.conf.
- Confirm the packaged CRS loader is still present in the Apache module config.
$ sudo sed -n '1,20p' /etc/apache2/mods-available/security2.conf <IfModule security2_module> # Default Debian dir for modsecurity's persistent data SecDataDir /var/cache/modsecurity # Include all the *.conf files in /etc/modsecurity. # Keeping your local configuration in that directory # will allow for an easy upgrade of THIS file and # make your life easier IncludeOptional /etc/modsecurity/*.conf # Include OWASP ModSecurity CRS rules if installed IncludeOptional /usr/share/modsecurity-crs/*.load </IfModule> - Confirm the packaged CRS setup file is available.
$ sudo ls -l /etc/modsecurity/crs/crs-setup.conf -rw-r--r-- 1 root root 35016 Oct 1 2023 /etc/modsecurity/crs/crs-setup.conf
Keep the packaged CRS defaults for the first pass. Application-specific exclusion packages and other baseline tuning live in /etc/modsecurity/crs/crs-setup.conf.
- Test the Apache configuration for syntax errors before loading the module and policy.
$ sudo apache2ctl -t Syntax OK
Related: How to test Apache configuration
- Restart Apache so the enabled module and active policy file are loaded.
$ sudo systemctl restart apache2
- Confirm security2_module is now loaded.
$ sudo apache2ctl -M | grep security2 security2_module (shared)
- Create a temporary local test rule that matches the query parameter modsectest=1.
$ sudo tee /etc/modsecurity/local-test.conf >/dev/null <<'EOF' SecRule ARGS:modsectest "@streq 1" "id:10001,phase:2,deny,status:403,log,msg:'ModSecurity local test rule hit'" EOF
Keep one-off local rule IDs unique. The local reservation range is 1 through 99999, so do not reuse IDs from packaged rule sets or third-party vendors.
- Test the configuration again after adding the local rule file.
$ sudo apache2ctl -t Syntax OK
Related: How to test Apache configuration
- Reload Apache to pick up the new local rule without dropping active connections.
$ sudo systemctl reload apache2
- Record the active audit log path from the policy file.
$ sudo grep -n '^SecAuditLog ' /etc/modsecurity/modsecurity.conf 205:SecAuditLog /var/log/apache2/modsec_audit.log
- Send a request that should match the local rule while the engine is still in DetectionOnly mode.
$ curl --silent --show-error -i 'http://127.0.0.1/?modsectest=1' HTTP/1.1 200 OK Date: Thu, 09 Apr 2026 04:25:11 GMT Server: Apache/2.4.58 (Ubuntu) Last-Modified: Thu, 09 Apr 2026 04:25:05 GMT ETag: "29af-64eff6697728d" Accept-Ranges: bytes Content-Length: 10671 Vary: Accept-Encoding Content-Type: text/html ##### snipped #####
DetectionOnly logs the match but still serves the response.
- Confirm the local rule hit was written to 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 "10001"] [msg "ModSecurity local test rule hit"] [hostname "127.0.0.1"] [uri "/"] [unique_id "adcqJ1IlXFSWFJk8REUQcAAAAEA"]
Application-specific exclusion packages can be enabled from /etc/modsecurity/crs/crs-setup.conf, while site-specific local overrides can stay in separate files under /etc/modsecurity.
- Switch the rule engine from DetectionOnly to enforcement mode.
$ sudo sed --in-place 's/^SecRuleEngine .*/SecRuleEngine On/' /etc/modsecurity/modsecurity.conf
Turning the engine on can block legitimate requests until exclusions are tuned. Keep a quick rollback path available.
- Verify that the policy now shows SecRuleEngine On.
$ sudo grep -n '^SecRuleEngine' /etc/modsecurity/modsecurity.conf 7:SecRuleEngine On
- Test the configuration before applying enforcement.
$ sudo apache2ctl -t Syntax OK
Related: How to test Apache configuration
- Reload Apache to apply enforcement.
$ sudo systemctl reload apache2
- Verify that the same request is now blocked with HTTP 403.
$ curl --silent --show-error -i 'http://127.0.0.1/?modsectest=1' HTTP/1.1 403 Forbidden Date: Thu, 09 Apr 2026 04:25:14 GMT Server: Apache/2.4.58 (Ubuntu) Content-Length: 274 Content-Type: text/html; charset=iso-8859-1 ##### snipped #####
- Remove the temporary local test rule once validation is complete.
$ sudo rm --force /etc/modsecurity/local-test.conf
- Reload Apache so the temporary rule is no longer active.
$ sudo systemctl reload apache2
- Confirm the temporary test request returns to a normal response after cleanup.
$ curl --silent --show-error -i 'http://127.0.0.1/?modsectest=1' HTTP/1.1 200 OK Date: Thu, 09 Apr 2026 04:25:16 GMT Server: Apache/2.4.58 (Ubuntu) Last-Modified: Thu, 09 Apr 2026 04:25:05 GMT ETag: "29af-64eff6697728d" Accept-Ranges: bytes Content-Length: 10671 Vary: Accept-Encoding Content-Type: text/html ##### snipped #####
After the temporary rule is removed, only the packaged CRS rules and any real local policy files remain active. That is the right time to keep tuning exclusions before relying on enforcement for production traffic.
Mohd Shakir Zakaria is a cloud architect with deep roots in software development and open-source advocacy. Certified in AWS, Red Hat, VMware, ITIL, and Linux, he specializes in designing and managing robust cloud and on-premises infrastructures.
