Template saves in Etch kept failing with 500 errors. The logs showed PHP-FPM warnings. The obvious conclusion? Wrong pool configuration.
Except it wasn’t.
The Symptom
Editing templates in Etch (or any modern page builder – Bricks, Breakdance, whatever) worked fine until you clicked save. Then: 500 error. No helpful message, no explanation, just broken.
The Red Herring
The server logs showed this beauty repeating constantly:
WARNING: [pool u28_development] seems busy (you may need to increase
pm.start_servers, or pm.min/max_spare_servers), spawning 8 children,
there are 0 idle, and 6 total children
Classic PHP-FPM saturation, right? All workers busy, no idle processes, requests queuing up. Every guide on the internet will tell you to bump those pool settings.
So we did. Changed from the absurdly low defaults:
pm = dynamic
pm.max_children = 40
pm.start_servers = 10
pm.min_spare_servers = 5
pm.max_spare_servers = 15
pm.max_requests = 500
For a 16GB server running 18 sites at 30% memory usage, this was still conservative. The warning disappeared.
The 500 error didn’t.
The Real Problem
Page builders send massive PUT requests to WordPress REST API endpoints. Etch’s template saves go through /wp-json/ like this:
PUT /wp-json/etch/v1/templates/123
Here’s what most nginx configs do with FastCGI cache:
# Don't cache these URIs
if ($request_uri ~* "/wp-admin/|/wp-json/|...") {
set $skip_cache 1;
}
# Don't cache POST requests
if ($request_method = POST) {
set $skip_cache 1;
}
See the problem?
PUT requests aren’t POST requests. Neither are PATCH or DELETE.
So nginx was happily caching REST API mutations. When Etch tried to save a template, it got a stale cached response from a previous request. That corrupted response looked like a 500 error to the browser.
The Fix
Add method-based cache bypass:
# Don't skip by default
set $skip_cache 0;
# Don't cache REST API mutations
if ($request_method ~* ^(PUT|PATCH|DELETE)$) {
set $skip_cache 1;
}
# POST requests and urls with a query string should always go to PHP
if ($request_method = POST) {
set $skip_cache 1;
}
Reload nginx:
sudo systemctl reload nginx
Done. Template saves work again.
Why This Matters
Modern WordPress development uses REST API for everything. WooCommerce, membership plugins, page builders – they all send PUT/PATCH/DELETE requests. If your cache config only thinks about POST, you’re caching mutations.
The symptoms are weird:
- Intermittent 500 errors that disappear after cache clears
- Working fine in incognito (no cache)
- Error logs that show nothing useful
- Users complaining about things “not saving”
What About Those Pool Settings?
Still needed. The PHP-FPM warning was real – 6 total children for 18 WordPress sites is ridiculous. But it wasn’t causing the 500 error.
For reference, here’s how to calculate pool settings properly:
pm.max_children = (RAM available for PHP) / (Average process size)
Check your average process RAM:
ps aux | grep php-fpm | awk '{sum+=$6} END {print sum/NR/1024 " MB"}'
If each process uses ~60MB and you allocate 8GB to PHP-FPM:
8192MB / 60MB = ~136 processes maximum
Set max_children conservatively at 50-75% of maximum. For the rest:
- pm.start_servers: 25-50% of max_children
- pm.min_spare_servers: ~25% of start_servers
- pm.max_spare_servers: ~75% of start_servers
This keeps idle workers ready for traffic spikes without spawning delays.
Debugging Process That Actually Worked
The standard advice – “check your error logs” – was useless here. The error logs showed deprecation warnings from Etch’s preview system, not the actual save failure.
What revealed the real issue:
- Noticed the 500 only happened on saves, not previews
- Realized saves use PUT, not POST
- Checked if cache bypass handled PUT methods
- It didn’t
Clearing WordPress cache (both page and object cache) temporarily fixed it, which confirmed cache corruption rather than PHP errors.
The Broader Point
Most nginx + WordPress guides copy-paste the same cache rules from 2015. They handle POST because that’s what contact forms use. Nobody thought about REST API methods becoming standard for admin operations.
Your infrastructure config should match how your applications actually work, not how they worked a decade ago.
Optional: Add Cache Debugging Headers
Want to see what’s being cached? Add this to your nginx PHP location block:
add_header X-Cache $upstream_cache_status always;
add_header X-Cache-Skip $skip_cache always;
Now browser DevTools shows:
X-Cache: HIT– served from cacheX-Cache: BYPASS– cache was skippedX-Cache-Skip: 1– cache bypass was triggered
Makes debugging cache issues obvious.
Summary
Symptom: Page builder template saves fail with 500 errors
Red herring: PHP-FPM pool warnings
Actual cause: nginx caching PUT requests to REST API
Fix: Add method-based cache bypass for PUT/PATCH/DELETE
The PHP-FPM pool settings still needed adjustment, but they weren’t causing the error. Always verify your assumptions by testing the fix in isolation.
Environment: nginx 1.28+, PHP 8.3-FPM, WordPress 6.9+, Etch builder
Time to debug: 45 minutes (would’ve been 5 if I’d checked HTTP methods first)
Time to fix: 2 minutes
Filed under: Things that should’ve been obvious but weren’t.