# DirectAdmin ModSecurity: Secure OWASP CRS Setup

Source: https://srvscripts.com/guides/directadmin-modsecurity-owasp-crs/
Updated: 2026-10-03
Publisher: srvScripts (https://srvscripts.com/)

In short: Enable the firewall with ./build set modsecurity yes, pick owasp or comodo for modsecurity_ruleset, then build modsecurity, modsecurity_rules and rewrite_confs.

ModSecurity is the web application firewall layer for Apache, nginx and both LiteSpeed servers on DirectAdmin, and CustomBuild currently ships ModSecurity 3.0.16 with a choice of the OWASP Core Rule Set 4.29 or the Comodo ruleset. It blocks the bulk of automated exploitation attempts against WordPress and other PHP applications before they reach PHP. The price is false positives, and the skill in running it on a shared server is handling those per domain rather than switching rules off globally. Since 1.689 there is a **Web application firewall** page at Admin Level that shows the audit log and lets you toggle rules, replacing the old `CMD_MODSECURITY?action=log` endpoint that was removed in 1.688.

**Short answer:** Enable the firewall with `./build set modsecurity yes`, pick `owasp` or `comodo` for `modsecurity_ruleset`, then build `modsecurity`, `modsecurity_rules` and `rewrite_confs`. Handle false positives by finding the contributing rule ID in the audit log and adding `SecRuleRemoveById` or `SecRuleUpdateTargetById` to the domain’s `.cust_httpd` (or `.cust_nginx`) file, never by removing the anomaly-scoring rule or disabling the engine globally.

## Enabling ModSecurity and choosing a ruleset

```
cd /usr/local/directadmin/custombuild
./build set modsecurity yes
./build set modsecurity_ruleset owasp
./build modsecurity
./build modsecurity_rules
./build rewrite_confs
```

Substitute `comodo` for `owasp` to use the Comodo rules. The Comodo set was split into separate downloads and the old `cwaf_rules` option was removed in 1.687, so on a server that was configured years ago check `./build options | grep modsecurity` and correct any stale value before building.

Which ruleset? OWASP CRS is community-maintained, well documented and tunable through paranoia levels; it is the better choice when you have engineers who will read the log. Comodo’s rules are written with shared hosting in mind and produce fewer false positives on common CMS traffic out of the box, at the cost of being less transparent. Both stop the same class of attack. On a busy shared node we usually start with Comodo for the low tuning burden, and move to CRS on servers where the customers are known applications.

The build writes the engine configuration to `/etc/modsecurity.conf` and the rules under `/etc/modsecurity.d/`. The engine starts in `On` mode; if you want a monitoring-only period first, set `SecRuleEngine DetectionOnly` in the custom copy of the configuration rather than the generated file:

```
mkdir -p custom/modsecurity/conf
cp /etc/modsecurity.conf custom/modsecurity/conf/modsecurity.conf
```

Files under `custom/modsecurity/` are re-applied by `./build modsecurity` on every rebuild; edits made directly in `/etc` are lost.

## Reading the audit log

Blocked requests are recorded in `/var/log/httpd/modsec_audit.log` (Apache and nginx_apache), `/var/log/nginx/modsec_audit.log`, or `/usr/local/lsws/logs/auditmodsec.log` for LiteSpeed products. Each entry includes the rule ID that fired, the matched variable and the URI. The Admin Level page presents the same data filtered by domain, which is quicker for support staff.

To find the rule that is blocking a customer’s request from the shell:

```
grep -B2 -A10 'example.com' /var/log/httpd/modsec_audit.log | grep -E 'id "[0-9]+"|uri|msg' | tail -n 20
```

For CRS, IDs in the 942xxx range are SQL injection rules, 941xxx are XSS, 930xxx and 931xxx are file inclusion, and 949110 is the anomaly-score blocking rule that fires when the total exceeds the threshold. The rule you need to exclude is the one that contributed to the score, not 949110 itself.

## Per-domain exclusions

The right place for an exclusion on Apache is the domain’s custom configuration file, which DirectAdmin includes inside the virtual host and preserves across `rewrite_confs`:

```
/usr/local/directadmin/data/users//domains/.cust_httpd
```

Remove a rule for the whole domain, or only for a path:

```
SecRuleRemoveById 942100

    SecRuleRemoveById 941100 941160

```

A tighter alternative that keeps the rule active but ignores one parameter, which is what you want for a page-builder field that legitimately contains HTML:

```
SecRuleUpdateTargetById 941100 "!ARGS:content"
```

After editing, queue a rewrite for the user and reload:

```
echo "action=rewrite&value=httpd&user=" >> /usr/local/directadmin/data/task.queue
```

On nginx the file is `.cust_nginx` and uses `modsecurity_rules 'SecRuleRemoveById 942100';` syntax. On OpenLiteSpeed and LiteSpeed Enterprise use `.cust_openlitespeed`, or in the LiteSpeed case the per-vhost context in the Apache-style file, since LiteSpeed reads the same directives. Users can also add exclusions themselves in `.htaccess` on Apache and LiteSpeed if you enable `SecRuleRemoveById` at the `.htaccess` level, but we advise against it on shared servers because it lets a customer disable protection for a compromised site without you knowing.

Global exclusions, for a rule that fires on every WordPress site, belong in a file under `/etc/modsecurity.d/` that loads after the ruleset. Keep it in `custom/modsecurity/conf/` too so it survives rebuilds, and record why each ID was removed in a comment; six months later nobody remembers.

## Common pitfall: excluding the anomaly rule

Removing 949110 or 980130 in CRS silences all blocking for that scope while every other rule still evaluates, which looks in the log as if the firewall works. Always exclude the contributing rule. Similarly, raising the paranoia level globally after a compromise multiplies false positives across every customer; if you need more coverage for one domain, set `SecAction "id:900000,phase:1,pass,t:none,nolog,setvar:tx.paranoia_level=2"` in that domain’s custom file instead.

## Verify

Send a request that should be blocked and one that should not:

```
curl -s -o /dev/null -w '%{http_code}\n' "https://example.com/?id=1%20UNION%20SELECT%201,2,3"
curl -s -o /dev/null -w '%{http_code}\n' "https://example.com/"
```

Expect 403 for the first and 200 for the second, and a new audit-log entry for the first. Then log in to the customer’s WordPress admin, save a post with HTML in it and update a plugin; those three actions exercise the parts of CRS most likely to need exclusions. If you run CSF, `LF_MODSEC` turns repeated ModSecurity blocks from one IP into a firewall block, which is configured in [Installing and tuning CSF/LFD on DirectAdmin](/guides/csf-directadmin-install-tune/).

## DirectAdmin ModSecurity at a glance

**Official documentation:** [OWASP CRS documentation](https://coreruleset.org/docs/), [DirectAdmin documentation](https://docs.directadmin.com/), [Linux man pages](https://man7.org/linux/man-pages/).

**Related guides:** [Using a paid CA via External Account Binding (EAB) in DirectAdmin 1.711+](https://srvscripts.com/guides/directadmin-external-account-binding-eab/) · [Installing Imunify360 or ImunifyAV on DirectAdmin](https://srvscripts.com/guides/install-imunify360-directadmin/) · [Mitigating Copy Fail, Dirty Frag and the DirectAdmin 1.711 TLS privilege escalation](https://srvscripts.com/guides/directadmin-1-711-tls-privilege-escalation/).

## Frequently asked questions

### Does a ModSecurity exclusion in .cust_httpd also apply to the domain’s subdomains?

No. Each subdomain has its own virtual host and its own `.cust_httpd` file under the user’s `domains/` directory, so an exclusion must be added to each subdomain that needs it or placed in a global file under `/etc/modsecurity.d/`.

### How long does enabling ModSecurity through CustomBuild take?

The build itself takes a few minutes on a typical server, and the `rewrite_confs` reload is brief; plan a week of watching the audit log afterwards, because most false positives only appear once customers use their admin panels.

### Can I undo this?

Yes. Run `./build set modsecurity no` followed by `./build modsecurity` and `./build rewrite_confs` to remove the engine from the web-server configuration; per-domain exclusion files can stay in place harmlessly or be deleted.
