Skip to content
ThemesIonic — home
Troubleshooting

Download Failed in WordPress: Read the Words After the Full Stop

"Download failed." is a wrapper. WordPress appends the real reason after it — a status message, a cURL error, a temp-folder problem — and each one points somewhere different: the vendor, the network, or your own server.

5 min read intermediate

"Download failed." is never the whole message — the words after it are the actual error. When a plugin, theme, core or translation update cannot fetch its ZIP, WP_Upgrader::download_package() wraps whatever download_url() returned in a download_failed error, and the upgrader skin prints the wrapper followed by the original message. So you see Download failed. Not Found, Download failed. cURL error 28: …, or Download failed. A valid URL was not provided. Each suffix belongs to a different layer.

Map the suffix to the layer

After "Download failed." Produced by Points to
Not Found, Forbidden, Unauthorized, Service Unavailable Any non-200 status from the package host The vendor or package URL
cURL error 28: Operation timed out… No complete response within the timeout Network or remote server
cURL error 6: Could not resolve host / cURL error 7: … DNS or blocked outbound connection Your server's network
cURL error 60: SSL certificate problem… Certificate verification TLS — see cURL error 60
A valid URL was not provided. WordPress's own URL safety check Package host, port or DNS
Could not create temporary file. / Destination directory for file streaming does not exist or is not writable. Temp file creation Your server's temp folder
The checksum of the file … does not match … Content-MD5 header mismatch A corrupted or altered download

Status messages: the package URL

WordPress downloads with wp_safe_remote_get() and treats any response other than 200 as failure. It returns an error coded http_404 regardless of the actual status, with the server's reason phrase as the message — which is why a 403 still reads Forbidden, not "404".

From WordPress.org, this is rare and nearly always transient or stale: the update record points at a version that was pulled. Clear the cached update data and check again:

wp transient delete update_plugins --network
wp plugin update plugin-slug

Without WP-CLI, Dashboard › Updates › Check again does the same refresh.

From a premium vendor, Unauthorized, Forbidden or Not Found is almost always the licence: expired, activated on a different domain, or tied to a site URL that changed after a migration. Re-enter or re-activate the licence in the plugin's settings, then retry. If the vendor's server blocks your host's IP range, only the vendor can fix that.

cURL errors: the network

The download runs with a 300-second timeout, so cURL error 28 here is a genuinely stalled connection, not an impatient default. If it happens to every update, outbound HTTPS from the server is restricted or throttled; the same diagnosis as in cURL error 28 applies. cURL error 6 means the server cannot resolve the host at all. Test from the server, not your laptop:

curl -sI https://downloads.wordpress.org/plugin/akismet.zip | head -1

A 200 there and a failure in WordPress points to a PHP-level setting such as a proxy defined in wp-config.php (WP_PROXY_HOST) or a security plugin that filters HTTP requests.

"A valid URL was not provided."

This one is not a network error. wp_safe_remote_get() runs every package URL through wp_http_validate_url(), which rejects:

  • hosts that resolve to private, loopback or link-local addresses (10.x, 172.16–31.x, 192.168.x, 127.x, 169.254.x);
  • ports other than 80, 443 and 8080, unless the URL is on the site's own host and port;
  • hostnames the server cannot resolve at all — the lookup failure surfaces as this message, not as a DNS error.

So a self-hosted update server on an internal network, or a package URL on port 8443, fails here by design. Allow it explicitly rather than weakening the check globally; for a port, the http_allowed_safe_ports filter does the same job:

add_filter( 'http_request_host_is_external', function ( $external, $host ) {
	return $external || 'updates.internal.example' === $host;
}, 10, 2 );

If the host is public, check DNS from the server with getent hosts downloads.wordpress.org; a resolver that fails intermittently produces this message intermittently.

Temp folder: your server

download_url() first creates an empty temporary file with wp_tempnam(), then streams the download into it. The folder comes from get_temp_dir(): WP_TEMP_DIR if defined, otherwise PHP's temp directory, otherwise wp-content. If none is writable — common on hardened hosting with open_basedir, or after the system temp partition fills up — the download fails before a single byte arrives.

Point WordPress at a folder you control:

// wp-config.php, above "That's all, stop editing!"
define( 'WP_TEMP_DIR', __DIR__ . '/wp-content/temp' );

Create the folder, give it to the PHP user, and deny web access to it. This constant governs WordPress's own temp files only; uploads through a browser form use PHP's upload_tmp_dir, a separate setting with separate errors.

Download succeeded, install failed

If the download finished and the next line is "Unpacking the update…" (or "Unpacking the package…" on a fresh install) followed by an error, you are past this problem. A truncated or non-ZIP file fails at unpack with PCLZIP_ERR_BAD_FORMAT; permission problems fail at Could not create directory.

Common mistakes

  • Reading only "Download failed." and reinstalling WordPress. The suffix was the diagnosis.
  • Deleting the plugin to "reinstall it clean" when the cause is a licence: the reinstall fails the same way and the settings may go with the plugin.
  • Disabling SSL verification to make a cURL error disappear.
  • Retrying ten times on a Forbidden from a vendor that is rate-limiting your IP.

Verify

  • The update completes from Dashboard › Updates and the plugin list shows the new version.
  • A second, unrelated plugin also updates — proving the server side, not one package, was the problem.
  • Automatic background updates resume; check the next morning's update email or auto-update settings.
  • Following safe update practice, the site still works after the update you were blocked on.

Frequently asked

Because the package URL WordPress was given no longer works, not because the plugin is gone. WordPress turns any non-200 response into an error carrying the server's status text, so a stale update record, a removed release or a premium vendor refusing the download all produce a short status message after Download failed.
The update itself, yes: a ZIP uploaded from Plugins › Add New skips the download step entirely. The cause stays. If the suffix was a cURL error or a temp-folder problem, the next update fails the same way, so fix the server side as well.
Premium plugins download from the vendor's own server, usually through a URL tied to your licence and domain. An expired licence, a site URL the licence does not cover, or a vendor server blocking your host returns a 401, 403 or 404, which shows up as Download failed followed by that status.

Related guides