If you are running Nginx with ModSecurity and OWASP Core Rule Set (CRS) as a Web Application Firewall (WAF), you may encounter unexpected 403 Forbidden errors when editing content in WordPress.
This issue is especially common when updating posts or pages containing:
- HTML markup (
<h1>,<p>, etc.) - Visual builder shortcodes (e.g.,
) - Inline CSS
- Rich formatted text
- Line breaks and special characters
In this article, we’ll explain:
- Why ModSecurity blocks WordPress content
- What the OWASP CRS rules are doing
- How to safely fix the issue
- The correct way to whitelist WordPress admin content
- Security considerations you must not ignore
The Problem: 403 Access Denied (Phase 2)
When updating a WordPress page, you may see errors like:
932370 – Remote Command Execution: Windows Command Injection932230 – Unix Command Injection932380 – Windows Command Injection932250 – Direct Unix Command Execution941100 – XSS Attack Detected via libinjection
Example log entry:
ModSecurity: Access denied with code 403 (phase 2)
[file ".../REQUEST-932-APPLICATION-ATTACK-RCE.conf"]
[id "932370"]
[msg "Remote Command Execution: Windows Command Injection"]
[uri "/wp-admin/post.php"]
The request is blocked because ModSecurity detects patterns in:
ARGS:content
This is the WordPress post content field.
Why This Happens
OWASP CRS is designed to detect:
- Remote Command Execution (RCE)
- XSS (Cross-Site Scripting)
- SQL Injection
- Shell injection patterns
The problem?
WordPress post content contains HTML and formatting, which can resemble attack patterns.
For example:
<h1>Example Title</h1>
Or:
These can match generic regex patterns intended to detect shell injection.
This is called a false positive.
Important: Do NOT Disable ModSecurity Globally
Many administrators solve this by:
- Disabling ModSecurity entirely
- Lowering paranoia level globally
- Turning CRS into DetectionOnly mode
This reduces protection for the entire site.
Instead, the correct approach is:
Whitelist only the affected parameter (
ARGS:content) and only for WordPress admin endpoints.
Proper Solution: Targeted Whitelisting
The correct fix is to remove specific rule targets for:
- URI:
/wp-admin/post.php - Parameter:
ARGS:content
And leave everything else protected.
Step 1: Create or Use a Whitelist File
If you are using OWASP CRS with Nginx, you likely have:
/etc/nginx/owasp-crs/rules/
If you already have a whitelist file there, use it.
If not, create one:
/etc/nginx/owasp-crs/rules/wordpress-whitelist.conf
Make sure it is included in your ModSecurity configuration.
Step 2: Add a Phase 1 Whitelist Rule
Use phase:1, so the control action executes before blocking rules in phase 2.
# WordPress Admin Content Whitelist
SecRule REQUEST_URI "@streq /wp-admin/post.php" \
"id:100100,phase:1,pass,nolog,\
ctl:ruleRemoveTargetById=932370;ARGS:content,\
ctl:ruleRemoveTargetById=932230;ARGS:content,\
ctl:ruleRemoveTargetById=932380;ARGS:content,\
ctl:ruleRemoveTargetById=932250;ARGS:content,\
ctl:ruleRemoveTargetById=941100;ARGS:content"
Why phase:1?
Because CRS blocking rules execute in phase 2.
Your control rule must run earlier to modify rule targets before they evaluate.
Step 3: Reload Nginx
sudo systemctl reload nginx
Now test updating WordPress content again.
What This Configuration Actually Does
It does NOT:
- Disable the rules globally
- Disable XSS protection site-wide
- Remove RCE protection entirely
It only tells ModSecurity:
“When the request is
/wp-admin/post.php, do not inspect thecontentparameter for these specific rules.”
Everything else remains protected.
Why WordPress Admin Often Triggers CRS
WordPress admin content includes:
- HTML tags
- Inline CSS
- Script-like structures
- Visual builder shortcodes
- JSON fragments
- Special characters
- Line breaks
CRS regex patterns are intentionally aggressive. They are optimized for APIs and raw input forms — not CMS HTML editors.
This makes CMS platforms frequent victims of false positives.
Alternative Approaches (When Needed)
Option 1: Lower Paranoia Level for Admin Area
Instead of global paranoia reduction, you can scope it:
SecRule REQUEST_URI "@beginsWith /wp-admin/" \
"id:100200,phase:1,pass,nolog,setvar:tx.paranoia_level=0"
Option 2: Disable ModSecurity Only for Admin
location /wp-admin/ {
ModSecurityOff;
}
⚠️ Not recommended unless your admin area is IP-restricted.
Security Best Practices
If you whitelist WordPress admin content:
- Restrict
/wp-adminvia IP allowlist if possible - Use strong authentication
- Enable 2FA
- Keep WordPress updated
- Monitor ModSecurity logs regularly
Remember:
Whitelisting admin content is acceptable because authenticated users are allowed to submit HTML.
When You See New Rule IDs
If another rule blocks ARGS:content, simply extend your control rule:
ctl:ruleRemoveTargetById=<new_rule_id>;ARGS:content
Keep everything consolidated in one whitelist rule.
Final Recommendation
The safest, production-ready approach is:
- Use phase:1 control rule
- Remove target only for
ARGS:content - Scope only to
/wp-admin/post.php - Do not disable ModSecurity globally
- Do not turn CRS to DetectionOnly permanently
This keeps:
✔ Public site fully protected
✔ Admin editing functional
✔ Security posture intact
Conclusion
If you run WordPress behind Nginx + ModSecurity + OWASP CRS, 403 errors during content editing are almost always false positives from RCE or XSS detection rules.
The correct solution is targeted whitelisting, not disabling your WAF.
By carefully removing inspection of the content parameter only in WordPress admin endpoints, you maintain both usability and strong security.

