woocommerce Case study
A2 Egress Firewall
wp-admin was regularly taking ten seconds or more to render a page, and nothing in the query log explained it. The time was going to WordPress dutifully waiting on outbound HTTP calls: plugin licence servers, font CDNs and update endpoints that are slow or simply unreachable from where the store is hosted, plus the calls WordPress makes to its own hostname for cron and the REST API. This is the diagnosis that found where the time went, and the firewall that came out of it.
The business problem
The admin was slow, the database was not the bottleneck, and the slow query log was unremarkable. That combination usually means the request is blocked on something that is not the database. A WordPress install makes a surprising number of blocking outbound calls during an admin request: update checks, licence validation, template libraries, Gravatar, font APIs. It also calls its own URL through the HTTP API for WP-Cron, the REST API and health checks, and a firewall rule, a DNS quirk or a proxy can turn one of those internal calls into a full timeout. From an Iranian host, several third-party destinations time out rather than fail fast, and each one adds its full timeout to the page. The dashboard felt broken while every individual component was working as designed.
What I delivered
- A tracing pass over every outbound call WordPress makes during an admin request, separating true third-party calls from the site calling itself, so the fix targeted the layer that was actually slow.
- a2-egress-firewall-adminspeed.php, a blocklist rather than an allowlist, so nothing that currently works breaks when the plugin is installed.
- A curated list of destinations known to hang from this host: plugin licence and update endpoints, template libraries, font and avatar APIs, and a CDN or two.
- A fail-fast path that drops the connect timeout to 1 second and the total timeout to 2 seconds for anything not blocked outright, which converts an invisible stall into a fast, visible failure.
- A companion front-end module, a2-egress-assets-firewall.php, that catches external script and style URLs at script_loader_src and style_loader_src, with a monitor mode that logs without blocking.
- A bypass key and an admin-only switch, so the whole thing can be disabled for a single request when something genuinely needs to reach out.
Technical approach
- I asked what the request was waiting on before asking how to make it faster, which meant instrumenting the HTTP layer rather than the query layer. The findings fed directly into the firewall instead of staying in a report, so the diagnosis and the fix live in the same code path.
- The block happens at pre_http_request, which returns a synthetic response before a socket is ever opened. Nothing waits.
- Host matching is suffix-based against a parsed host, not a substring search against the URL, so a blocked domain cannot be smuggled through in a query string.
- I explicitly exempt the site's own hostnames and anything resolving locally, because loopback requests are how WP-Cron and the REST API talk to themselves. The local check covers the www and non-www forms of the configured home and site URLs, plus localhost and the loopback address, because a loopback request to the wrong variant is exactly the case that breaks.
- Defaults are deliberately conservative: admin-only enforcement, logging off, and the asset firewall shipped in monitor mode so I could read the log before deciding what to block. Blocking is opt-in per destination once the logs justify it.
- I documented one destination in the source as deliberately not blocked. It is an Iranian host that responds normally, and blocking it would break a price integration.
Result and evidence
Tracing the slowness to outbound calls rather than to WordPress or the database turned the whole effort away from query tuning that would not have helped. Admin page loads stopped stalling on outbound calls. I have not measured a clean before-and-after average, because the delay varied with which endpoints were unreachable that day. What I can defend is that the failure mode is gone: blocked destinations now return instantly instead of consuming a full timeout, and the loopback-safe host check runs in production inside the firewall.
Commercial value
This is the difference between a dashboard staff avoid and one they use. It also cut the store's exposure to third-party availability: an outage at a plugin vendor no longer becomes an outage in our admin. The saved effort matters too. Without the diagnostic pass, the obvious next step would have been database work on a system whose database was fine.
Readable implementation brief
implementation_brief {
project: "A2 Egress Firewall"
question: "what is the admin request blocked on?"
method: "instrument the HTTP layer, not the query layer"
finding: "outbound calls, not DB; loopback must stay exempt"
files: "a2-egress-firewall-adminspeed.php (224 lines)
a2-egress-assets-firewall.php (84 lines)"
strategy: "blocklist, not allowlist"
hook: "pre_http_request -> synthetic response, no socket"
failfast: "connect 1s / total 2s for everything else"
scope: "wp-admin only by default; bypass key available"
frontend: "script_loader_src + style_loader_src, monitor mode"
exempt: "home_url + site_url hosts, www variants,
localhost and loopback, always"
}What this project shows
Diagnosis is the part of performance work that gets skipped, and skipping it is how teams spend a month optimizing the wrong layer. I would rather spend a day proving where the time goes than a week making something fast that was never slow.
The tempting version of the fix is a strict allowlist. I chose a blocklist because an allowlist on a live store with dozens of plugins is a guaranteed incident, and the blocklist reaches most of the benefit with a fraction of the risk. Shipping the front-end half in monitor mode first came from the same instinct: I would rather read a week of logs than guess, especially when the failure mode is an invisible missing stylesheet.