cPanel CLI forensics: why whmapi1 fails for ModSecurity and how to use UAPI correctly per domain
Back to blog

cPanel CLI forensics: why whmapi1 fails for ModSecurity and how to use UAPI correctly per domain

6/7/2026 · 4 min · Infrastructure

ModSecurity blocking a legitimate application (HTTP 403)#

Environment: cPanel/WHM server on AlmaLinux/CloudLinux Symptom: Legitimate requests returning HTTP 403 Forbidden for a specific domain Objective: Diagnose the block and disable ModSecurity for the domain or configure a fine-grained rule exception via SSH (without the WHM graphical interface)

The 403 diagnosis in Apache logs at /usr/local/apache/logs/error_log:

[Wed May 01 21:40:00.123456 2025] [:error] [pid 12345] [client 203.0.113.45:52341]
ModSecurity: Access denied with code 403 (phase 2).
Pattern match "(?i)(?:\\b(?:(?:s(?:elect\\b(?:.{1,100}?\\b(?:(?:length|count|top)\\b.
{1,100}?\\bfrom|from\\b.{1,100}?\\bwhere)|.*?\\b(?:d(?:ump\\b.*\\bfrom|ata_type)|
(?:to_(?:numbe|cha)|inst)r))|p_(?:(?:addextendedpro|sqlexe)c|(?:oacreat|prepar)e|
execute(?:sql)?|makewebtask)|ql_(?:longvarchar|variant))..." at ARGS:description.
[file "/etc/apache2/conf.d/modsec_vendor_configs/OWASP3/rules/REQUEST-942-APPLICATION-ATTACK-SQLI.rlz"]
[line "45"] [id "942100"] [rev ""] [msg "SQL Injection Attack Detected via libinjection"]
[data "Matched Data: ..."] [severity "CRITICAL"] [ver "OWASP_CRS/3.3.4"]
[hostname "mydomain.com"] [uri "/admin/api/content"] [unique_id "YzX9..."]

The application uses free-text fields that trigger the OWASP rule 942100 (SQL Injection via libinjection). The false positive is a legitimate description field with SQL syntax in technical documentation.


The error: anatomy of the whmapi1 failure#

The first attempt was to use the WHM administrative API, the natural intuition for anyone managing via CLI:

whmapi1 toggle_modsec_for_domain domain=mydomain.com state=0

Complete API return:

---
metadata: 
  command: toggle_modsec_for_domain
  reason: Unknown app ("toggle_modsec_for_domain") requested for this version (1) of the API.
  result: 0
  version: 1

The reason field is explicit: Unknown app ("toggle_modsec_for_domain"). The endpoint does not exist in whmapi1.

Architectural analysis: WHM API vs. UAPI - scope separation#

The confusion is understandable because both WHM and cPanel manage ModSecurity, but at different layers of the hierarchy:

WHM (Web Host Manager) - Scope: SERVER
│
│  whmapi1 / whm_api2
│  ├── Create/remove cPanel accounts
│  ├── Restart services (Apache, MySQL, etc.)
│  ├── Configure IP addresses
│  ├── Manage ModSecurity GLOBALLY (enable/disable server-wide)
│  └── ❌ Does NOT manage per-domain/vhost configurations
│
└── cPanel - Scope: ACCOUNT (user/domain)
│
│  UAPI (uapi) - Current v3 API
│  ├── Manage addon and parked domains
│  ├── Create email accounts
│  ├── Manage databases
│  └── ✅ ModSecurity module: enable/disable per domain

Why this separation exists:

Per-domain ModSecurity is implemented through directives in the Apache VirtualHost specific to each cPanel account. The line in the httpd.conf generated by cPanel/EasyApache is based on include files. whmapi1 does not have access to operate in individual account context - that is the exclusive domain of the UAPI.


Security protocol and practical solution#

Step 1 - identify the domain owner#

/scripts/whoowns mydomain.com

Output: myuser

The /scripts/whoowns script queries the /etc/userdomains file - a flat domain→user map maintained by cPanel:

# Alternative manual verification
grep "mydomain.com" /etc/userdomains
# mydomain.com: myuser

Step 2 - false positive validation and initial triage#

Before making modifications, validate whether the endpoint actually triggers the block with legitimate traffic using curl:

curl -X POST https://mydomain.com/admin/api/content \
  -H "Content-Type: application/json" \
  -d '{"description": "test"}' \
  -v 2>&1 | grep -i "403\|modsecurity"

Verify if rule 942100 is the single or primary cause of blocks on the target domain:

grep "mydomain.com" /usr/local/apache/logs/error_log | grep "942100" | wc -l

Step 3 - pre-configuration backup protocol#

Never alter configuration files or run rebuild commands without ensuring restore points:

# Create a complete backup of the userdata directory
tar czf /root/modsec-backup-$(date +%Y%m%d).tar.gz /etc/apache2/conf.d/userdata/

# Back up the domain-specific modsec file if it already exists
cp /etc/apache2/conf.d/userdata/ssl/2/myuser/mydomain.com/modsec.conf \
   /root/modsec.conf.bak.$(date +%Y%m%d) 2>/dev/null || echo "No existing modsec.conf file found for this domain."

Instead of completely disabling ModSecurity (which would expose the application to multiple attacks), edit or create the userdata file to apply an exception only for rule 942100.

Directory for HTTPS: /etc/apache2/conf.d/userdata/ssl/2/myuser/mydomain.com/modsec.conf

Directory for HTTP: /etc/apache2/conf.d/userdata/2/myuser/mydomain.com/modsec.conf

Example of Exception for the Entire VHost:

<IfModule mod_security2.c>
    # Disable only rule 942100 (SQL Injection via libinjection)
    SecRuleRemoveById 942100
</IfModule>

Example of Exception Restricted to the Problematic Endpoint (Even More Secure):

<IfModule mod_security2.c>
    # Disable the rule only within the corresponding URI
    <LocationMatch "/admin/api/content">
        SecRuleRemoveById 942100
    </LocationMatch>
</IfModule>

After editing the files above, rebuild Apache's configurations and reload the daemon:

# Validate and rebuild Apache configuration
/scripts/rebuildhttpdconf

# Apply changes without dropping active connections
/usr/sbin/apachectl graceful

Alternative solution: UAPI for complete disabling (last resort)#

If you must completely disable ModSecurity for this domain, execute the UAPI in the context of the identified user:

uapi --user=myuser ModSecurity disable_domains domains=mydomain.com

Complete success return:

---
apiversion: 3
func: disable_domains
module: ModSecurity
result: 
  data: 
- 
  dependencies: []
  domain: mydomain.com
  enabled: 0
  exception: ~
  searchhint: ''
  type: main
  errors: ~
  messages: ~
  metadata: 
transformed: 1
  status: 1
  warnings: ~

What happens internally after the UAPI command#

cPanel performs the following automated sequence of operations:

1. Validates that myuser exists and owns mydomain.com
2. Creates (or overwrites) the file:
   /etc/apache2/conf.d/userdata/ssl/2/myuser/mydomain.com/modsec.conf
3. Writes the SecRuleEngine Off directive:
   <IfModule mod_security2.c>
   SecRuleEngine Off
   </IfModule>
4. Replicates the directive for the HTTP version (non-SSL):
   /etc/apache2/conf.d/userdata/2/myuser/mydomain.com/modsec.conf
5. Automatically executes: /scripts/rebuildhttpdconf
6. Executes: /usr/sbin/apachectl graceful  ← reload without connection drops

Post-configuration verification and monitoring#

After making the change (either via fine-grained exception or disabling), follow these technical validation steps:

  1. Test the endpoint that was blocked:
   curl -I https://mydomain.com/admin/api/content
   # Must return HTTP 200 OK (or the expected response code, no longer 403)
  1. Verify that other security rules remain active (Shielding Test):

If you used the Fine-Grained Exception Approach, send an alternative malicious payload (such as an XSS script tag) to ensure the WAF still blocks invalid attempts:

   curl -X POST https://mydomain.com/admin/api/content \
     -H "Content-Type: application/x-www-form-urlencoded" \
     -d "payload=<script>alert(1)</script>" \
     -w "%{http_code}\n" -o /dev/null
   # Must return 403 (ModSecurity blocking XSS correctly)
  1. Check logs for new errors:
   tail -50 /usr/local/apache/logs/error_log | grep "mydomain.com"

Advanced systemic triage and complementary modules#

1. Auditing other domains with disabled ModSecurity#

Check for other WAF security holes on the same user account:

uapi --user=myuser ModSecurity list_domains | grep -B1 -A2 "enabled: 0"

Check statistics for the most frequently triggered rules on the server to understand the global impact of CRS rules:

grep "id \"" /usr/local/apache/logs/error_log | awk -F'id "' '{print $2}' | awk -F'"' '{print $1}' | sort | uniq -c | sort -rn | head -15

2. Checking additional Apache modules#

Sometimes, the client continues to receive HTTP 403 errors or timeouts due to other active security modules in Apache that police connections. Audit which modules are enabled:

apachectl -M | grep -E "evasive|limitipconn|reqtimeout"

3. ModSecurity audit logging configuration#

If Apache error logs do not show enough detail to diagnose complex false positives, ensure detailed ModSecurity auditing is active for the request:

# In /etc/apache2/conf.d/modsec2.conf or WHM ModSecurity Configure page
SecAuditEngine RelevantOnly
SecAuditLogRelevantStatus "^(?:5|4(?!04))"
SecAuditLogParts ABIJDEFHX
SecAuditLog /var/log/apache2/modsec_audit.log
SecAuditLogType Serial

4. Version check#

Always document the version of the environment and installed CRS rules for syntax compatibility:

# cPanel/WHM version
cat /usr/local/cpanel/version

# Active ModSecurity version in daemon
httpd -M | grep -i security
# Or consult the Apache startup log
grep -i "ModSecurity" /usr/local/apache/logs/error_log | head -n 5

Visual decision flow#

flowchart TD A[HTTP 403 on domain] --> B{Check ModSecurity logs} B -->|Specific rule found| C{Is it a false positive?} C -->|Yes| D[Create specific rule exception] C -->|No| E[Investigate application / Sanitize input] B -->|No ModSecurity logs| F{Check other security modules} D --> D1[SecRuleRemoveById 942100] D1 --> D2[Or disable for specific URL using LocationMatch] D2 --> G[Backup, Apply, and rebuildhttpdconf] E --> H[Fix payload or behavior] F -->|evasive/limitipconn/reqtimeout| I[Adjust rules for the respective module] F -->|No active modules| J[Check external firewall/WAF] K[Disable ModSecurity for domain] --> L[Last resort - SecRuleEngine Off] style K fill:#7f1d1d,stroke:#ef4444,color:#fff style L fill:#854d0e,stroke:#f59e0b,color:#fff

ModSecurity troubleshooting checklist#

## Checklist: ModSecurity 403 - diagnosis and fix

### 1. Diagnosis and triage
- [ ] Check Apache error logs: `tail -100 /usr/local/apache/logs/error_log | grep 403`
- [ ] Identify the blocking CRS rule (ID and corresponding file)
- [ ] Validate the false positive by reproducing the request via `curl`
- [ ] Check if other domains on the account show the same behavior

### 2. Solution and rule modeling
- [ ] Create an exception for the specific rule via `SecRuleRemoveById` (preferred)
- [ ] Restrict the exception to the correct endpoint using `<LocationMatch>`
- [ ] Completely disable ModSecurity for the domain (only as a last resort)

### 3. Secure implementation
- [ ] Generate a complete backup of userdata directories: `tar czf /root/modsec-backup.tar.gz /etc/apache2/conf.d/userdata/`
- [ ] Create or modify the `modsec.conf` file in the appropriate userdata path (SSL and non-SSL)
- [ ] Rebuild vhosts configuration: `/scripts/rebuildhttpdconf`
- [ ] Gracefully reload Apache: `/usr/sbin/apachectl graceful`

### 4. Technical validation
- [ ] Test the problematic URI and verify the expected HTTP response code
- [ ] Audit error logs to ensure no new blocks occur on the request
- [ ] Ensure blocking remains active against other vectors (such as XSS with `<script>`)
- [ ] Monitor domain stability for 24 hours

### 5. Documentation and post-configuration audit
- [ ] Record the exception created, reason for the false positive, and review date
- [ ] Confirm the domain status via UAPI
- [ ] Test and document the rollback procedure

Rollback plan#

In case of instability, syntax errors detected during Apache restart, or new security requirements, follow this rollback guide:

Manual rollback (restore from userdata backup)#

Restore files from the backup copy created before making any changes:

# Restore the individual file
cp /root/modsec.conf.bak.YYYYMMDD /etc/apache2/conf.d/userdata/ssl/2/myuser/mydomain.com/modsec.conf

# If the file did not originally exist, delete the created file to remove the exception:
rm -f /etc/apache2/conf.d/userdata/ssl/2/myuser/mydomain.com/modsec.conf
rm -f /etc/apache2/conf.d/userdata/2/myuser/mydomain.com/modsec.conf

# Rebuild and reload Apache
/scripts/rebuildhttpdconf
/usr/sbin/apachectl graceful

Rollback via cPanel API (re-enable ModSecurity on domain)#

If you used the UAPI to disable ModSecurity, you can re-enable it with:

uapi --user=myuser ModSecurity enable_domains domains=mydomain.com

Reference table: whmapi1 vs. UAPI for ModSecurity#

ActionCorrect toolCommand
Disable for 1 domainuapiuapi --user=X ModSecurity disable_domains domains=Y
Re-enable for 1 domainuapiuapi --user=X ModSecurity enable_domains domains=Y
List status per accountuapiuapi --user=X ModSecurity list_domains
Disable globallywhmapi1whmapi1 modsec_disable_all_domains
View global configurationwhmapi1whmapi1 modsec_get_settings
View ModSec audit logwhmapi1whmapi1 modsec_get_log

Was this article helpful?

Leave a quick reaction to help prioritize future technical guides:

CC BY-NC

This post is licensed under CC BY-NC.

Comments

Join the discussion below.

0 comments