Skip to content

How to fix "Your site could not complete a loopback request" in WordPress

Site Health asked the server for the site's own wp-cron.php and did not get a 200 back. The line under the message says why, as a cURL error number or a status code. Find what stands between the server and its own address, because scheduled tasks and the file editors rely on the same route.

By
WP Ministry
Published
Tested on
WordPress 7.1.3, PHP 8.3.35

In short

  • The server asked for its own site and was not answered with a 200. Visitors' pages load as before.
  • The line under the message carries the reason, as a cURL error number or an HTTP status code.
  • cURL error 6, 7 or 28: the request never arrived. That is the host's network, or the server's own.
  • 401, 403 or 503: something answered in place of the site. Find the password, the rule or the firewall.
  • Since WordPress 5.7 the test ends before WordPress loads, so code running inside WordPress cannot fail it.
  • A pass does not prove that scheduled tasks run. "A scheduled event is late" and `wp cron test` answer that.

"Your site could not complete a loopback request" is a result on the Site Health screen: Tools, then Site Health, on the Status tab, with a "Performance" badge. A loopback request is the server asking for a page of its own site over HTTP, the way a visitor's browser would. Site Health made one, and the answer was not a 200.

Under the heading WordPress prints "Loopback requests are used to run scheduled events, and are also used by the built-in editors for themes and plugins to verify code stability." Then comes one of two things, and which one decides where the result is listed:

  • Among the critical issues: "The loopback request to your site failed, this means features relying on them are not currently working as expected." and a line that begins "Error:". The request got no answer at all.
  • Among the recommended improvements: "The loopback request returned an unexpected http status code, 403, it was not possible to determine if this will prevent features from working as expected." with the code that came back in place of 403. Something answered, and it was not the site.

Both have the same heading, so read the line under it. The table further down turns that line into a cause.

Visitors are not affected directly. Their requests come from outside, and their pages load as before. What stops is the work WordPress does by calling itself.

What Site Health asks for

The test sends one request from the server to wp-cron.php at the site's own address, the one under Settings, General, "WordPress Address (URL)". It waits up to 10 seconds. An error, or any status other than 200, gives the message.

Three details decide what can cause it.

  • WordPress is not loaded. The request is a form post, and wp-cron.php ends at once when it receives one, before it loads WordPress. So code running inside WordPress cannot fail the test: not a coming-soon or maintenance plugin, and not a security plugin's own checks. What can fail it sits in front of WordPress: the network, the web server's rules, a password, a firewall. This has been so since WordPress 5.7. Before that, the test asked for a page of the site.
  • It carries what your browser sent. The request goes out with your cookies and, where the site is behind an HTTP password and PHP was given the one you typed, with that password. The request WordPress makes to start its scheduled tasks carries neither.
  • The certificate is not checked. WordPress does not verify the certificate on requests to its own address, so an expired or self-signed certificate does not fail the test. WordPress 5.2 did check it, and 5.3 stopped.

A pass therefore says that the server can reach its own wp-cron.php. It does not say that scheduled tasks run. Site Health's "A scheduled event is late" and WP-CLI's wp cron test answer that.

What depends on these requests

  • Scheduled tasks. WP-Cron starts its tasks by requesting wp-cron.php in the same way. Scheduled posts, a plugin's scheduled backups and WordPress's own twice-daily checks for updates all wait on it. A post that stays unpublished shows "Missed schedule".
  • The Theme File Editor and the Plugin File Editor. When you save a PHP file of the active theme or an active plugin, WordPress requests the editor screen and then the home page to see whether the change broke the site. If it cannot, it puts the old file back and says "Unable to communicate back with site to check for fatal errors, so the PHP change was reverted. You will need to upload your PHP file change by some other means, such as by using SFTP."
  • Automatic plugin updates. Since WordPress 6.6, after an automatic update of an active plugin, WordPress requests its own home page to look for a fatal error. A request that fails is treated as one, and the plugin is put back to the version it had.
  • Other Site Health tests. The page cache test asks for the home page and, when it cannot, says "Unable to detect page cache due to possible loopback request problem." The REST API test asks the site's own address too, and has a result of its own: "The REST API encountered an error".

Read the line under the message

The number after "cURL error" is curl's own, and curl's documentation gives its meaning. A status code is the answer of whatever stood in the way.

The line saysWhat happenedCause
cURL error 6"Could not resolve host." The server could not turn the site's name into an address.The server cannot reach the site's own address
cURL error 7"Failed to connect() to host or proxy." The address was found and no connection could be made to it, for example because it was refused.The server cannot reach the site's own address
cURL error 28, "Connection timed out"No connection was made in 10 seconds. Something drops the server's own requests without a word.The server cannot reach the site's own address
cURL error 28, "Operation timed out", "0 bytes received"The connection was made and no answer came in 10 seconds.No PHP process is free to answer, or something in front of the site held the request
cURL error 35"A problem occurred somewhere in the SSL/TLS handshake."The secure connection fails
cURL error 60The certificate was checked and refused. WordPress does not check it unless code has switched that on.The secure connection fails
401The request "lacks valid authentication credentials".The site is behind an HTTP password
403The server "understood the request but refused to process it".A firewall or a server rule refuses the request
503The server "is not ready to handle the request".A maintenance rule on the server, or a server that is overloaded

Other codes have pages of their own. 429 is a limit on how often one address may ask. 502 and 504 come from a server in front of PHP. A 503 with no maintenance rule behind it is covered in 503 Service Unavailable.

What does not cause it

  • WP_HTTP_BLOCK_EXTERNAL. The setting blocks WordPress's requests to other sites. WordPress's documentation says it "will only allow localhost and your blog to make requests", so a request to the site's own address goes out without the site being listed in WP_ACCESSIBLE_HOSTS.
  • An untrusted certificate on its own. Browsers refuse one, with "Your connection is not private". The loopback request does not check.
  • A plugin working inside WordPress. It never runs for this request. A security plugin can still cause the message through anything it sets up in front of WordPress, such as rules it writes into .htaccess.

Keep scheduled tasks running in the meantime

Scheduled tasks do not have to wait for the route to be repaired. A cron job on the server that runs WP-CLI makes no web request at all, and WordPress has a second way of starting tasks that goes through the visitor's browser. Both are on the page for "Missed schedule", and the guide to automatic backups sets the cron job up step by step under "Put WP-Cron on a real clock".

The file editors and the check after an automatic plugin update still need the request to work.

Where it goes wrong

A page request passes through each of these in turn. This one comes from the web server.

  1. Browser
  2. DNS
  3. HTTPS
  4. CDN or firewall
  5. Web server (this error comes from here)
  6. PHP
  7. WordPress
  8. Database and files

What causes it

How to fix it

Make the same request from the server

  • Takes care
  • No risk
  • About 10 minutes
  • Steps tested on WordPress 7.1.3

Site Health shows one line. Making the request yourself, from the server, shows the whole answer, with the headers that say who sent it. This needs SSH. Without it, work from the line under the message and the table above. How to use WP-CLI covers connecting.

  1. Step 1: Read the address WordPress asks for

    Without WP-CLI, it is "WordPress Address (URL)" under Settings, General.

    bash
    wp option get siteurl
  2. Step 2: Send the request Site Health sends

    Put that address in place of https://example.com.

    -d posts the form field the test posts, -D - prints the headers of the answer, and -o /dev/null throws the body away.

    bash
    curl -sS -o /dev/null -D - -d "site-health=loopback-test" https://example.com/wp-cron.php
    echo "curl exit code: $?"
  3. Step 3: Read the answer

    • A first line with 200 and exit code 0. The server reached its own address. The body is empty, as it should be. If Site Health still shows the message, send the host both results.
    • No headers, and an exit code other than 0. The exit code is the number Site Health prints after "cURL error". Go to the next fix.
    • A first line with 401, 403 or 503. The headers under it say what answered. WWW-Authenticate means a password. Server names the software that sent the answer. If that is not your web server, the request was stopped on the way to it.

This request ends before WordPress loads. wp cron test makes the one that starts scheduled tasks, which does load it. The page on "Missed schedule" reads its answers under "Test whether the site can reach itself".

Take the line to the host

  • Easy
  • No risk
  • About 15 minutes

A cURL error means the request never got an answer, and no setting inside WordPress changes that. Send the host the line under the message, letter for letter, the time you saw it, and the output of the first fix if you have it. Then ask the question that fits the number.

  • 6: "Does the server resolve the site's own domain name?"
  • 7, or 28 with "Connection timed out": "May the server connect to its own public address on ports 80 and 443, or does a firewall stop it?"
  • 28 with "Operation timed out": "How many PHP processes may the site run at once, and were they all busy at that time?" PHP's manual says of FPM's pm.max_children that it "sets the limit on the number of simultaneous requests that will be served".
  • 35: "What answers on port 443 when the server connects to the site's own name?"
  • 403, with no rule of your own behind it: "Does a firewall on the server refuse requests the server makes to its own sites?"

On a server you run yourself, one cure for 6, 7 and a connection that times out is a line in /etc/hosts that gives the site's name the server's own address, so the request never leaves the machine. The file "associates IP addresses with hostnames, one line per IP address". Remember the line if the site ever moves. ERR_CONNECTION_TIMED_OUT has the checks for a server that drops connections, and hosting problems has what to put in a message to a host.

Allow the server's own address where it is refused

  • Takes care
  • Low risk
  • About 20 minutes

A 403 means the request arrived and something refused it. The work is finding which thing.

  1. Step 1: Look for a rule that names the file

    A rule in .htaccess that denies wp-cron.php refuses the server along with everyone else. The page on "Missed schedule" shows how to find and remove one.

  2. Step 2: Rule the security plugin in or out

    If a security plugin on the site has a firewall and a log of what it blocked, look in that log for the server's own address, and add the address to the plugin's allow list. The 403 Forbidden page shows how to switch such a plugin off without the dashboard, to see whether the message goes with it.

  3. Step 3: Look in front of the server

    If the site's name points at a CDN or a web application firewall, the server's request to its own name goes out to that service like any stranger's and is judged by its rules. Add the server's address to the service's allow list, or ask the host to make the site's name resolve to the server itself on that server.

To undo it: Remove the address from the allow list, or put the rule back.

Let the server past your own password or maintenance rule

  • Takes care
  • Back up first
  • About 15 minutes
  • Steps tested on WordPress 7.1.3

This is for a site on Apache whose password or maintenance rule is written in .htaccess. If it was set in the hosting panel, look there for a way to exempt an address, or switch it off when the work is done.

  1. Step 1: Find the password lines

    Open .htaccess in the folder WordPress is installed in. HTTP Basic authentication is a group of lines that starts with AuthType Basic and ends with Require valid-user.

  2. Step 2: Add one line under "Require valid-user"

    Apache lets a request through Require local when it comes from the server itself: from 127.0.0.1, from ::1, or when "both the client and the server address of the connection are the same". Two Require lines side by side are alternatives. In Apache's words, "the first one to authorize a user authorizes the entire request".

    .htaccess
    Require local
  3. Step 3: Check both sides

    Reload Tools, Site Health. Then open the site in a private window: it must still ask for the password. If it no longer does, take the line out. That happens when a proxy on the same machine passes visitors' requests to Apache: every visitor then looks local.

If the line under the message says 503 and .htaccess holds a rewrite rule that answers 503 to every address, give that rule one more condition. Put it directly above the RewriteRule line, with the server's own address in place of 203.0.113.10:

.htaccess
RewriteCond %{REMOTE_ADDR} !=203.0.113.10

The rule then leaves requests from that address alone. The address is the one the server's requests arrive from. The server's access log shows it beside each request for wp-cron.php, and the host can tell you.

On nginx, the password is auth_basic in the server's configuration. satisfy any; with an allow line for the server's address beside it does the same job, and on most hosting plans that file is the host's to change.

On a staging copy, a 401 is the password doing its work. How to set up a staging site explains why the copy has one.

To undo it: Remove the line you added to .htaccess.

When to get help

If the line under the message is a cURL error and the host says nothing is blocked, or the line changes from one reload to the next, what is left is the server's own logs, DNS and firewall. That takes access to the server. More changes inside WordPress are guesses.

Common questions

Is it safe to leave it?

The site goes on serving pages. What stops is quieter: posts that do not publish on time, backups and update checks that do not run, and a file editor that refuses to save PHP. If a cron job on the server already runs your scheduled tasks, the editors and the check after automatic plugin updates are what remain.

Site Health says loopback requests work, but scheduled posts are still missed. Why?

The test proves less than its name suggests. It ends before WordPress loads, and it carries your cookies and any HTTP password your browser sent, which the real request does not. A password on the site, or anything inside WordPress that turns away a visitor who is not logged in, can stop scheduled tasks while the test passes. wp cron test makes the real request.

Why is it a critical issue on one site and a recommended improvement on another?

It depends on what came back. No answer at all, which is a cURL error, is listed as critical. An answer with a status other than 200 is listed as recommended, because WordPress cannot tell whether that answer stops anything.

More on this subject

Would you rather we fixed it?

Quick Fix is $49. One issue, one site, up to about an hour. No fix, no fee. 30-day warranty. It starts with a free diagnosis.