If you've worked with PHP's built-in SoapClient, you've almost certainly encountered this error:
SOAP-ERROR: Parsing WSDL: Couldn't load from 'https://example.com/service?wsdl'
This error means PHP tried to fetch the WSDL document from the given URL and failed. The frustrating part is that the error message gives you zero detail about why it failed. The WSDL URL might work perfectly in your browser, in SoapUI, or from a curl command on the same server — but PHP's SoapClient still refuses to load it.
This guide covers every realistic cause and gives you copy-paste fixes for each one.
Cause 1: SSL Certificate Verification Failure
This is the most common cause, especially in development environments or when connecting to services with self-signed certificates.
PHP's SoapClient uses PHP's stream wrapper to fetch the WSDL, and by default it verifies SSL certificates. If the remote server uses a self-signed certificate, an expired certificate, or a certificate signed by a CA that PHP doesn't trust, the fetch silently fails and you get the generic "Couldn't load" error.
How to diagnose:
# Check the certificate from the command line
openssl s_client -connect example.com:443 -servername example.com </dev/null 2>/dev/null | openssl x509 -noout -dates -issuer
# Try fetching the WSDL with curl (verbose SSL output)
curl -v https://example.com/service?wsdl 2>&1 | grep -E "(SSL|certificate|expire)"
Fix — Create a stream context that configures SSL:
$context = stream_context_create([
'ssl' => [
'verify_peer' => true,
'verify_peer_name' => true,
'cafile' => '/etc/ssl/certs/ca-certificates.crt',
],
]);
$client = new SoapClient('https://example.com/service?wsdl', [
'stream_context' => $context,
]);
If you're in a development environment with a self-signed certificate (never do this in production):
$context = stream_context_create([
'ssl' => [
'verify_peer' => false,
'verify_peer_name' => false,
'allow_self_signed' => true,
],
]);
$client = new SoapClient('https://example.com/service?wsdl', [
'stream_context' => $context,
]);
Important: On many Linux distributions, the CA bundle path varies. Common locations:
- Debian/Ubuntu:
/etc/ssl/certs/ca-certificates.crt - CentOS/RHEL:
/etc/pki/tls/certs/ca-bundle.crt - Alpine:
/etc/ssl/cert.pem
You can also check your PHP configuration:
php -r "echo openssl_get_cert_locations()['default_cert_file'];"
Cause 2: WSDL Cache Corruption
PHP caches WSDL files by default (controlled by soap.wsdl_cache_enabled in php.ini). If a previous fetch partially failed or the WSDL was updated on the server side, you might be stuck loading a corrupt or stale cached copy.
How to diagnose:
# Check your WSDL cache directory
php -r "echo ini_get('soap.wsdl_cache_dir');"
# List cached WSDL files (usually in /tmp)
ls -la /tmp/wsdl-*
Fix — Clear the cache and optionally disable it during debugging:
// Option 1: Disable cache for this specific client
$client = new SoapClient('https://example.com/service?wsdl', [
'cache_wsdl' => WSDL_CACHE_NONE,
]);
// Option 2: Clear all cached WSDL files
$cacheDir = ini_get('soap.wsdl_cache_dir') ?: '/tmp';
array_map('unlink', glob($cacheDir . '/wsdl-*'));
Or in php.ini:
; Disable WSDL caching globally (development only)
soap.wsdl_cache_enabled = 0
; Or set a shorter TTL (in seconds)
soap.wsdl_cache_ttl = 60
Once you confirm the error is resolved, re-enable caching. WSDL caching exists for good reason — parsing a WSDL on every request adds significant overhead.
Cause 3: open_basedir Restriction
If your PHP configuration has open_basedir set, PHP's stream wrapper might be blocked from writing to the WSDL cache directory or from opening network streams to certain hosts.
How to diagnose:
php -r "echo ini_get('open_basedir');"
If this returns a restricted path list, the SoapClient may not be able to write cached WSDL files to /tmp or access the network.
Fix:
; In php.ini, add the cache directory to open_basedir
open_basedir = /var/www/html:/tmp:/etc/ssl/certs
; Or set a custom WSDL cache directory within the allowed path
soap.wsdl_cache_dir = /var/www/html/tmp/wsdl-cache
Make sure the cache directory exists and is writable:
mkdir -p /var/www/html/tmp/wsdl-cache
chmod 755 /var/www/html/tmp/wsdl-cache
chown www-data:www-data /var/www/html/tmp/wsdl-cache
Cause 4: DNS Resolution Failure
If the WSDL URL contains a hostname that the PHP server can't resolve, the fetch fails silently. This is common in Docker containers, Kubernetes pods, or servers with custom /etc/resolv.conf configurations.
How to diagnose:
# Check DNS resolution from the server
nslookup example.com
dig example.com
# Check from inside a Docker container
docker exec -it your-container nslookup example.com
Fix:
If DNS is the issue, you have a few options:
// Option 1: Use the IP address directly with a Host header
$context = stream_context_create([
'http' => [
'header' => 'Host: example.com',
],
'ssl' => [
'peer_name' => 'example.com',
],
]);
$client = new SoapClient('https://10.0.1.50/service?wsdl', [
'stream_context' => $context,
'location' => 'https://10.0.1.50/service',
]);
// Option 2: Download the WSDL locally and use a file path
$wsdlContent = file_get_contents('https://example.com/service?wsdl', false, $sslContext);
$localPath = '/tmp/service.wsdl';
file_put_contents($localPath, $wsdlContent);
$client = new SoapClient($localPath, [
'location' => 'https://example.com/service',
]);
Cause 5: Connection Timeout
PHP's default socket timeout (default_socket_timeout in php.ini) is typically 60 seconds, but some WSDL endpoints hosted on slow or distant servers may not respond within that window. Enterprise SOAP services behind VPNs or load balancers are particularly prone to this.
How to diagnose:
# Time the WSDL fetch manually
time curl -o /dev/null -s -w "%{time_total}\n" https://example.com/service?wsdl
Fix:
$context = stream_context_create([
'http' => [
'timeout' => 120, // seconds
],
'ssl' => [
'verify_peer' => true,
],
]);
$client = new SoapClient('https://example.com/service?wsdl', [
'stream_context' => $context,
'connection_timeout' => 120,
]);
Or globally in php.ini:
default_socket_timeout = 120
Cause 6: The WSDL Response Isn't Valid XML
Some servers require specific HTTP headers before they'll return WSDL content. Without the right Accept header or User-Agent, the server might return an HTML error page, a redirect, or an authentication prompt — none of which are valid WSDL.
How to diagnose:
# Check what the server actually returns
curl -s -D - https://example.com/service?wsdl | head -30
# Look at the Content-Type header
curl -s -I https://example.com/service?wsdl | grep -i content-type
If you see text/html instead of text/xml or application/wsdl+xml, the server isn't returning the WSDL.
Fix:
$context = stream_context_create([
'http' => [
'header' => "Accept: text/xml, application/xml\r\n" .
"User-Agent: PHP-SoapClient\r\n",
],
]);
$client = new SoapClient('https://example.com/service?wsdl', [
'stream_context' => $context,
]);
If the URL requires authentication to access the WSDL:
$context = stream_context_create([
'http' => [
'header' => "Authorization: Basic " . base64_encode("user:pass") . "\r\n",
],
]);
$client = new SoapClient('https://example.com/service?wsdl', [
'stream_context' => $context,
'login' => 'user',
'password' => 'pass',
]);
Cause 7: PHP Extensions Not Loaded
The SoapClient class requires the soap extension, and HTTPS connections require the openssl extension. If either is missing, you'll get this error (or a fatal error about the class not existing).
How to diagnose:
php -m | grep -E "(soap|openssl)"
# Or check from PHP
php -r "var_dump(extension_loaded('soap'), extension_loaded('openssl'));"
Fix:
# Debian/Ubuntu
sudo apt-get install php-soap php-xml
sudo phpenmod soap
sudo systemctl restart apache2 # or php-fpm
# CentOS/RHEL
sudo yum install php-soap
# Docker (common Dockerfile pattern)
# RUN docker-php-ext-install soap
Debugging Checklist
When you encounter this error, work through this checklist in order:
1. Can you curl the WSDL URL from the same server?
$ curl -s https://example.com/service?wsdl | head -5
2. Does it return valid XML (starts with <?xml)?
Look for <definitions> or <wsdl:definitions> root element
3. Is the SSL certificate valid from PHP's perspective?
$ php -r "var_dump(file_get_contents('https://example.com/service?wsdl'));"
4. Is WSDL caching causing stale data?
$ ls -la /tmp/wsdl-* && rm /tmp/wsdl-*
5. Are the soap and openssl extensions loaded?
$ php -m | grep -E 'soap|openssl'
6. Is open_basedir restricting access?
$ php -r "echo ini_get('open_basedir');"
7. Can the server resolve the hostname?
$ php -r "var_dump(gethostbyname('example.com'));"
Complete Working Example
Here's a robust SoapClient initialization that handles most of the common failure modes:
function createSoapClient(string $wsdlUrl, array $options = []): SoapClient
{
$defaults = [
'cache_wsdl' => WSDL_CACHE_DISK,
'connection_timeout' => 30,
'trace' => true,
'exceptions' => true,
];
$sslContext = stream_context_create([
'ssl' => [
'verify_peer' => true,
'verify_peer_name' => true,
'cafile' => '/etc/ssl/certs/ca-certificates.crt',
'crypto_method' => STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT |
STREAM_CRYPTO_METHOD_TLSv1_3_CLIENT,
],
'http' => [
'timeout' => 30,
'header' => "Accept: text/xml\r\nUser-Agent: PHP-SoapClient\r\n",
],
]);
$defaults['stream_context'] = $sslContext;
try {
return new SoapClient($wsdlUrl, array_merge($defaults, $options));
} catch (SoapFault $e) {
throw new RuntimeException(
"Failed to load WSDL from {$wsdlUrl}: {$e->getMessage()}",
0,
$e
);
}
}
// Usage
$client = createSoapClient('https://example.com/service?wsdl', [
'login' => 'api_user',
'password' => 'api_secret',
]);
How SOAPless Helps
Dealing with PHP's SoapClient means wrestling with stream contexts, SSL configurations, WSDL caching quirks, and php.ini settings — all before you even make your first API call. Every deployment environment has its own combination of issues.
SOAPless eliminates this entire class of problems. You paste a WSDL URL into the SOAPless dashboard, and it generates a standard REST JSON API. Your PHP application makes simple HTTP calls — no SoapClient, no stream contexts, no WSDL cache headaches:
// Before: fighting with SoapClient
$context = stream_context_create([/* SSL options, headers, timeouts... */]);
$client = new SoapClient($wsdl, ['stream_context' => $context, /* more options */]);
$result = $client->__soapCall('GetUser', [$params]);
// After: a standard HTTP call through SOAPless
$response = file_get_contents('https://api.soapless.com/v1/your-service/GetUser', false,
stream_context_create([
'http' => [
'method' => 'POST',
'header' => "Content-Type: application/json\r\nX-API-Key: your-key\r\n",
'content' => json_encode(['userId' => '12345']),
],
])
);
$data = json_decode($response, true);
SOAPless handles WSDL parsing, SSL connections, SOAP envelope construction, and XML-to-JSON conversion on its infrastructure. Your application never touches SOAP directly.