How I Diagnose WordPress cURL Error 28 — The Timeout That Breaks Everything

· 11 min read

A client's WordPress Site Health page was showing two critical errors: "The REST API encountered an unexpected result" and "Your site could not complete a loopback request." Both pointed to the same underlying cause — cURL error 28: connection timed out.

The site was a WooCommerce store on a self-managed VPS running Nginx, PHP 8.2-FPM, and MariaDB. Plugin updates were failing silently. WP-Cron jobs weren't firing. The WooCommerce background order processing had stalled. Three separate symptoms, one root cause — the server couldn't talk to itself.

What cURL Error 28 Actually Means

WordPress uses PHP's cURL library constantly. Every time the site checks for plugin updates, fires a cron job, processes a REST API request, validates a licence key, or generates critical CSS — it makes an HTTP request using cURL. Error 28 means that request was sent but no response came back before the timeout expired.

The default timeout for WordPress internal requests is just 10 seconds. For loopback requests (where the site calls its own REST API), it's even shorter. If anything in the chain — DNS resolution, firewall rules, web server config, or PHP-FPM capacity — takes longer than that, you get the error.

What makes this frustrating is that the site can look perfectly fine to visitors. Pages load. Orders process. But behind the scenes, all the internal machinery that keeps WordPress healthy is quietly failing.

Step 1: Confirm the Problem from the Command Line

The WordPress admin Site Health page is useful, but it doesn't tell you much about why the request is timing out. SSH into the server and test the loopback manually:

curl -sS -o /dev/null -w "HTTP %{http_code} in %{time_total}s\n" \
  https://example.com/wp-json/wp/v2/types

If this returns something like HTTP 200 in 0.342s, your loopback is working from the server's perspective. If it hangs for 10+ seconds and returns nothing, you've confirmed the problem.

Test DNS resolution separately to rule that out:

dig +short example.com

Then test whether the server can reach itself via that resolved IP:

curl -sS -o /dev/null -w "HTTP %{http_code} in %{time_total}s\n" \
  --resolve example.com:443:$(dig +short example.com | head -1) \
  https://example.com/wp-json/wp/v2/types

If DNS resolves fine but the curl request times out, the problem is between your server and itself — usually a firewall or proxy issue.

Step 2: Check if the Server's Firewall Is Blocking Itself

This is the most common cause I see on self-managed VPS setups. The server sends an HTTP request to its own public IP, and the firewall treats it as an inbound connection from an external source — then blocks or rate-limits it.

Check if your server's public IP can reach itself:

SERVER_IP=$(curl -s ifconfig.me)
curl -sS -o /dev/null -w "HTTP %{http_code}\n" \
  --resolve example.com:443:$SERVER_IP \
  https://example.com/

If this times out, your firewall is the culprit. The fix depends on your firewall setup.

UFW (common on Ubuntu):

sudo ufw allow from $(curl -s ifconfig.me) to any

iptables:

SERVER_IP=$(curl -s ifconfig.me)
sudo iptables -I INPUT -s $SERVER_IP -j ACCEPT

CSF (ConfigServer Security & Firewall — common on cPanel/WHM):

Add your server's public IP to /etc/csf/csf.allow:

echo "$(curl -s ifconfig.me) # Server self-access for WordPress loopback" | sudo tee -a /etc/csf/csf.allow
sudo csf -r

Fail2ban: If you're running Fail2ban (which you should be), check whether it's jailing your own server's IP. I've seen this happen when WordPress fires a burst of cron requests and Fail2ban interprets it as a brute force attack:

sudo fail2ban-client status | grep "Jail list"
sudo fail2ban-client status wordpress-login

If your server's IP is in the banned list, unban it and add it to the Fail2ban ignoreip list in /etc/fail2ban/jail.local:

[DEFAULT]
ignoreip = 127.0.0.1/8 ::1 YOUR_SERVER_IP

Step 3: Check Cloudflare or Your WAF

If the site is behind Cloudflare, the server's loopback request goes out to the public IP, hits Cloudflare's edge, and comes back. Cloudflare's bot protection, rate limiting, or firewall rules can challenge or block these requests.

I see this constantly. The site works fine in a browser because browsers can solve JavaScript challenges. But PHP's cURL can't — it just times out.

Test whether Cloudflare is the issue by bypassing it entirely. Find your origin server's IP and test directly:

curl -sS -o /dev/null -w "HTTP %{http_code} in %{time_total}s\n" \
  -H "Host: example.com" \
  https://YOUR_ORIGIN_IP/wp-json/wp/v2/types --insecure

If this succeeds instantly but the normal domain request times out, Cloudflare is blocking the loopback.

Fixes:

  1. Add your server's public IP to Cloudflare's IP Access Rules (Security > WAF > Tools) with action "Allow"
  2. If you use Cloudflare's "Under Attack Mode" or "Bot Fight Mode", these will challenge cURL requests. Create a WAF exception rule for requests from your server's IP
  3. Alternatively, add your origin IP to wp-config.php so WordPress calls itself directly, bypassing Cloudflare entirely:
define('WP_SITEURL', 'https://example.com');
define('WP_HOME', 'https://example.com');

And add a hosts file override on the server so your domain resolves locally:

echo "127.0.0.1 example.com" | sudo tee -a /etc/hosts

This makes loopback requests stay on the server without ever touching Cloudflare. It's the most reliable fix I've found for sites behind any CDN or WAF.

Step 4: Check the Timeout Chain

If the firewall isn't the problem, the issue might be that a legitimate request is taking longer than the allowed timeout. WordPress, PHP, and Nginx each have their own timeout values, and they all need to be coordinated.

PHP's socket timeout (/etc/php/8.2/fpm/php.ini):

default_socket_timeout = 60

This controls how long PHP waits for any socket operation, including cURL requests. The default is 60 seconds, which is usually fine. But some hosting panels set this to 10 or 15 seconds.

Nginx fastcgi timeouts (your site's Nginx config):

fastcgi_connect_timeout 60;
fastcgi_send_timeout 60;
fastcgi_read_timeout 60;

PHP-FPM worker availability: This is the sneaky one. If all your PHP-FPM workers are busy handling visitor requests, the loopback request sits in the queue waiting for a free worker — and times out. Check your current worker status:

sudo systemctl status php8.2-fpm | grep "active"

And check the pool configuration (/etc/php/8.2/fpm/pool.d/www.conf):

grep -E "^(pm\.|pm =)" /etc/php/8.2/fpm/pool.d/www.conf

If you're running pm = ondemand with a low pm.max_children value on a busy WooCommerce site, you might be running out of workers during traffic spikes. I've written about this in detail in my post on PHP-FPM worker exhaustion. The loopback request needs a worker just like any other request — if none are free, it queues and eventually times out.

Step 5: Increase WordPress's Internal Timeout

Sometimes the underlying request is legitimate but slow — a plugin licence check hitting a slow API, or a WooCommerce update check when the .org servers are under load. You can increase WordPress's default HTTP timeout by adding this to your theme's functions.php or a custom plugin:

add_filter('http_request_timeout', function ($timeout) {
    return 30;
});

This bumps the default from 10 seconds to 30. It won't fix firewall or DNS issues — those need to be resolved at the source — but it gives legitimate slow requests more breathing room.

For WP-Cron specifically, if loopback issues are preventing scheduled tasks from running, you can bypass the loopback entirely by switching to a system cron:

crontab -e

Add:

*/5 * * * * cd /var/www/example.com && wp cron event run --due-now --path=/var/www/example.com > /dev/null 2>&1

Then disable the WordPress loopback cron in wp-config.php:

define('DISABLE_WP_CRON', true);

I do this on every site I manage. It's more reliable than the loopback method and eliminates one of the most common triggers for cURL error 28 entirely. I covered the full setup in my post on switching WP-Cron to system cron.

Quick Diagnostic Checklist

When I get a cURL error 28 report, I run through this in order. It usually takes under 10 minutes to identify the cause:

  1. Test from the server: curl -sS -w "%{http_code} %{time_total}s" https://example.com/wp-json/wp/v2/types — confirms whether the problem exists at server level
  2. Test DNS: dig +short example.com — rules out DNS resolution failures
  3. Check firewall: Test self-referencing connection using the server's public IP
  4. Check Cloudflare/WAF: Bypass the CDN and test directly against the origin
  5. Check PHP-FPM status: Ensure workers aren't exhausted
  6. Check timeout values: Verify default_socket_timeout, fastcgi_read_timeout, and PHP-FPM pool settings
  7. Check PHP error logs: tail -50 /var/log/php8.2-fpm.log for timeout or connection errors
  8. Test with plugins disabled: wp plugin deactivate --all then re-run the loopback test to rule out plugin-level issues

The Fix for This Client

In this case, the client's server was behind Cloudflare with Bot Fight Mode enabled. The server's loopback requests were being challenged by Cloudflare's bot detection, and since PHP's cURL can't solve JavaScript challenges, every internal request timed out.

I added the server's origin IP to Cloudflare's WAF allow list and added a /etc/hosts entry so the domain resolved locally. The loopback test went from timing out after 10 seconds to completing in 0.2 seconds. Site Health immediately cleared both errors. Plugin updates started working again. WP-Cron fired on schedule. WooCommerce background jobs resumed processing.

Total fix time: about 8 minutes once I'd identified the cause. The hard part is always the diagnosis, not the fix.

If your WordPress site is showing cURL error 28 in Site Health, or you're seeing plugin updates fail and scheduled tasks stall, the problem is almost always one of the five causes above. Work through the checklist and you'll find it.

And if you'd rather not troubleshoot server networking yourself, this is exactly the kind of issue I handle as part of my WordPress maintenance plans. I also cover this in my server management service for clients on self-managed VPS or dedicated servers.

Stop Firefighting. Start Maintaining.

I manage 70+ WordPress sites for UK agencies and businesses. Whether you need ongoing maintenance, emergency support, or a one-off performance fix — I can help.

View Maintenance Plans Get in Touch

Get in Touch to Discuss Your Needs