WordPress Site Not Loading? Check TTFB First
When your WordPress site is not loading - tab spinning, blank white page, no error message at all - the natural assumption is that it's down. Often it isn't. The server is alive, it's accepting the request, it just hasn't sent back the first byte yet. That wait has a name: TTFB, time to first byte. A site with a 15-second TTFB looks identical to a dead site until you put a number on it.
The distinction matters because the fixes are different. A 500 error means something crashed. A hanging page means something is still running - slowly, or stuck on something it will never get. Most advice you'll find for this mixes the spinner in with the white screen of death and the critical error page. Those are crash states. This post is about the other failure: the request that goes in and never comes back out.
Step 1: Rule out the actual outage (60 seconds)
Before measuring anything, make sure "not loading" isn't literal downtime:
- Open the site on your phone over mobile data, not wifi. That rules out your connection, your DNS resolver, and your ISP's cache.
- Run it through a down detector. If it fails from everywhere, skip this post.
- Check your host's status page. A container that OOMed at 3am looks exactly like this from the outside.
If it genuinely fails everywhere - connection refused, DNS dead, host down - that's a real outage and a different checklist. My 5-minute triage for a down WordPress site covers that path.
If the site responds but crawls, keep reading.
Step 2: Put a number on the wait
One command:
curl -o /dev/null -s -w 'TTFB: %{time_starttransfer}s\n' https://yoursite.com
time_starttransfer is the seconds from request start to the first byte of the response. Run it three times so you don't chase a fluke.
No terminal? Same number in Chrome: F12 > Network > reload > click the first document request > Timing tab. The row labeled "Waiting for server response" is TTFB.
What the number means, per web.dev's TTFB guide: under 0.8 seconds is good, over 1.8 is poor, and it's a rough guide since TTFB gates everything after it. Nobody reading this post has a 1.9-second problem. You're here because curl sits there for 10, 20, 30 seconds - or the request never finishes and some timeout upstream eventually turns it into a 504.
One caveat before blaming WordPress: TTFB also includes DNS, the TLS handshake, and any redirects. DevTools shows those as separate rows. If DNS or connect time is the big chunk, this post won't fix it. If nearly all of it is "Waiting", the delay is work happening on your server. That's the case I'm covering.
What's happening during the wait
On every uncached request, a PHP worker boots WordPress, loads your whole autoloaded options set, runs every active plugin, queries MySQL, and builds the HTML. The first byte leaves only after there's something to send. Anything blocking inside that - a fat options read, an HTTP call to a dead API, a queue waiting for a free worker - lands in TTFB.
Four causes cover most of the sites I've untangled. In order:
Cause 1: Autoloaded options have grown into a wall
Before any plugin code runs, WordPress reads every wp_options row marked autoload into memory. Core needs about 100KB of this. Plugins add theirs, and deleted plugins rarely clean up.
Measure it. In phpMyAdmin, Adminer, or wp db query:
SELECT ROUND(SUM(LENGTH(option_value)) / 1024, 1) AS autoload_kb,
COUNT(*) AS autoload_count
FROM wp_options
WHERE autoload IN ('yes', 'on', 'auto', 'auto-on');
The IN list matters: WordPress 6.6+ writes auto and auto-on alongside yes and on, so the older autoload = 'yes' query undercounts. Then find what's carrying the weight:
SELECT option_name, ROUND(LENGTH(option_value) / 1024, 1) AS size_kb, autoload
FROM wp_options
WHERE autoload IN ('yes', 'on', 'auto', 'auto-on')
ORDER BY LENGTH(option_value) DESC
LIMIT 25;
Reading the total:
| Autoload size | Verdict |
|---|---|
| Under 300KB | Fine |
| 300-800KB | Worth investigating |
| Over 800KB | Action required |
| Over 1MB | Urgent - the whole set caches as one alloptions key, and many Redis/Memcached setups cap a single value around 1MB, so it can silently stop caching |
The fix is flipping autoload off on the big stale rows - wp option set-autoload OPTION_NAME off - not deleting them. Some large rows are supposed to be large, so check each against the do-not-touch list in my autoload bloat guide before changing anything. It's usually three or four rows doing the damage.
Cause 2: The object cache points at something dead
This one is sneaky. Someone installed Redis or Memcached for speed months ago, dropped object-cache.php into wp-content/, and everyone forgot. Then Redis dies - OOM, a bad restart, a socket path that changed. Now every request blocks on a cache connection that never answers, and the site hangs until timeout.
It happens in the wild. In this support thread the site went unresponsive twice, a VPS reboot changed nothing, and the only fix was deleting object-cache.php by hand. The plugin author's reply was blunt: "if Redis Server is not reachable then your site won't be either."
The check takes seconds: does wp-content/object-cache.php exist? If yes and the site is hanging, rename it to object-cache.php.bak. WordPress falls back to MySQL and loads. That's also the official uninstall step in the Redis Object Cache FAQ - delete the drop-in file, or set WP_REDIS_DISABLED to true if you want it reversible from wp-config.
Site comes back? Fix or restart Redis first, then put the drop-in back. Don't just leave it off and forget why it was there.
Cause 3: A plugin is blocked on an outbound HTTP call
License verification, update checks, a payment gateway ping, an API call to a service having a bad day. wp_remote_get defaults to a five-second timeout, plugins can raise it, and a few of those chained together inside the page build will hang a request without ever erroring.
The tell: the site was fine, then a plugin updated or some third-party service degraded, and now everything spins. A recent r/wordpress thread had a textbook version - one wp-admin settings page loading forever, no errors anywhere, but it loaded instantly with &noheader=1 appended. That parameter is core behavior that skips the admin header hooks, which narrowed it to something in admin_head or an admin notice - exactly where a blocking license check likes to live.
How to confirm it on your site:
- Query Monitor has an "HTTP API Calls" panel showing each outbound request and how long it took. Install it, load the hanging page, look at the panel.
- On a server you control, the PHP-FPM slow log does the same job without a plugin.
- A
WP_DEBUG_LOGin wp-config will at least record the timeout errors - the four debug lines from the official doc are a two-minute add.
Once you find the call, you find the plugin. Deactivate it, or fix its endpoint, or decide the license check wasn't worth your homepage.
Cause 4: All the PHP workers are busy
Your host gives you a fixed pool of PHP workers. If every worker is stuck on a slow request - usually because of causes 1-3, sometimes just a traffic spike - the next request queues. That queue time counts toward its TTFB even though the request itself did nothing wrong.
The signature is load-dependent pain: fine at 9am, hanging at noon. There's a clean test for it:
curl -o /dev/null -s -w 'static file: %{time_starttransfer}s\n' https://yoursite.com/wp-content/uploads/any-image.jpg
If a JPG answers in 40ms while the homepage hangs for 20 seconds, the web server is fine and PHP is the bottleneck. Your options are fixing what's blocking the workers (back to causes 1-3), asking the host for a bigger pool, or putting a real page cache in front so most requests never reach PHP.
Where a caching plugin fits - and where it doesn't
Full-page caching genuinely helps: a cached response skips PHP and MySQL entirely, so anonymous traffic stops paying for any of this. But it only covers what it can cache. wp-admin, cart, checkout, logged-in users, and every cache miss still run the full stack. And a cache can hide a rotting backend for months - the homepage looks instant while the uncached path gets slower until something tips it into the hanging state you came here about. I wrote the long version in why caching plugins don't fix slow WordPress.
This is also the honest scope line for my own tool. WP Multitool is built for the measurement half of this post: the autoload audit above is what its Autoload Optimizer runs, and Site Doctor flags an object-cache.php drop-in pointing at a dead backend - the exact failure from Cause 2. What it won't do is help if the host is offline or DNS is dead. No plugin fixes a server that isn't there.
When the spinner isn't a TTFB problem
If curl can't connect at all, DNS doesn't resolve, or the host is having an incident, none of this applies. That's an actual outage, and the calculus changes: downtime costs more than an hour of your evening.
That's the job fix-wp.com exists for - $100 flat, I SSH in and fix it, and the payment is held until the site is actually back or refunded automatically. But if your curl answers after 15 seconds, save the hundred dollars and work the four causes first. Most of them resolve in an afternoon.
Either way, install Query Monitor or get your SSH credentials sorted today. A hanging site is a warning that something in the stack is one bad afternoon away from a real outage.
Marcin Dudek - I fix broken WordPress sites at fix-wp.com and build WP Multitool.
Still stuck? We fix it for you.
fix-wp.com resolves WordPress emergencies in under 1 hour. $100 flat fee. Only pay if we fix it.
Fix My Site Now