spatie/ssl-certificate-chain-resolver
Resolves incomplete SSL certificate chains by discovering and returning the missing intermediate certificates between a site’s cert and trusted roots. Helps fix “Extra download” issues flagged by SSL Labs, improving compatibility for mobile and strict clients.
Installation
composer require spatie/ssl-certificate-chain-resolver
No additional configuration is required—just autoload the package.
First Use Case: Resolving Certificate Chains
use Spatie\SslCertificateChainResolver\Facades\SslCertificateChainResolver;
$resolver = new \Spatie\SslCertificateChainResolver\SslCertificateChainResolver();
$chain = $resolver->resolve('https://example.com');
Where to Look First
Spatie\SslCertificateChainResolver\Facades\SslCertificateChainResolver for quick usage.\Spatie\SslCertificateChainResolver\SslCertificateChainResolver for custom logic.$resolver = new \Spatie\SslCertificateChainResolver\SslCertificateChainResolver();
$chain = $resolver->resolve('https://example.com');
// Save to a file (e.g., for Nginx/Apache)
file_put_contents('/etc/ssl/certs/example.com-chain.pem', implode(PHP_EOL, $chain));
$resolver = new \Spatie\SslCertificateChainResolver\SslCertificateChainResolver();
$chain = $resolver->resolve('https://example.com');
if (count($chain) === 0) {
throw new \RuntimeException('No valid certificate chain found!');
}
use Illuminate\Support\Facades\Http;
use Spatie\SslCertificateChainResolver\Facades\SslCertificateChainResolver;
$url = 'https://example.com';
$chain = SslCertificateChainResolver::resolve($url);
// Use the chain with Guzzle (if needed)
$client = Http::withOptions([
'curl' => [
'SSLCERT' => tempnam(sys_get_temp_dir(), 'chain_') . '.pem',
],
])->post($url, ['data' => 'test']);
// Cleanup
unlink($tempCertPath);
$cacheKey = 'ssl_chain_example_com';
$chain = cache()->remember($cacheKey, now()->addHours(1), function () {
return SslCertificateChainResolver::resolve('https://example.com');
});
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Spatie\SslCertificateChainResolver\Facades\SslCertificateChainResolver;
class ResolveCertificateChain implements ShouldQueue
{
use Queueable;
public function handle()
{
$chain = SslCertificateChainResolver::resolve('https://example.com');
// Save to storage or database
}
}
use Illuminate\Console\Command;
use Spatie\SslCertificateChainResolver\Facades\SslCertificateChainResolver;
class UpdateCertificates extends Command
{
protected $signature = 'certificates:update {url}';
protected $description = 'Update SSL certificate chain for a domain';
public function handle()
{
$chain = SslCertificateChainResolver::resolve($this->argument('url'));
file_put_contents(storage_path("app/{$this->argument('url')}-chain.pem"), implode(PHP_EOL, $chain));
$this->info('Certificate chain updated!');
}
}
schedule:run).use Illuminate\Support\ServiceProvider;
use Spatie\SslCertificateChainResolver\SslCertificateChainResolver;
class AppServiceProvider extends ServiceProvider
{
public function register()
{
$this->app->singleton('ssl.resolver', function () {
return new SslCertificateChainResolver();
});
}
}
Network Timeouts
$resolver = new \Spatie\SslCertificateChainResolver\SslCertificateChainResolver();
$resolver->setTimeout(5); // 5 seconds
Self-Signed Certificates
resolveWithOptions() to bypass validation:
$chain = $resolver->resolveWithOptions('https://self-signed.example', [
'verify_peer' => false,
]);
Warning: Only use this for trusted internal systems.Rate Limiting
PEM Format Assumptions
$derCert = openssl_csr_export($pemCert, 'DER');
Enable Verbose Output
$resolver = new \Spatie\SslCertificateChainResolver\SslCertificateChainResolver();
$resolver->setVerbose(true); // Logs HTTP requests/responses
$chain = $resolver->resolve('https://example.com');
Inspect Raw Responses
$resolver = new \Spatie\SslCertificateChainResolver\SslCertificateChainResolver();
$response = $resolver->getResponse('https://example.com');
// $response contains the raw HTTP response object
Check for OCSP Stapling Issues
$chain = $resolver->resolveWithOptions('https://example.com', [
'ssl' => [
'disable_ocsp' => true,
],
]);
Custom Certificate Stores
$resolver = new \Spatie\SslCertificateChainResolver\SslCertificateChainResolver();
$resolver->setCaStore('/path/to/custom/cacert.pem');
Extending the Resolver Class
class CustomResolver extends \Spatie\SslCertificateChainResolver\SslCertificateChainResolver
{
public function resolve($url)
{
$chain = parent::resolve($url);
// Add custom logic (e.g., filter specific CAs)
return array_filter($chain, fn($cert) => str_contains($cert, 'Let\'s Encrypt'));
}
}
Hooking into HTTP Requests
beforeResolve and afterResolve events (if the package supports them) or wrap the resolver:
$resolver = new \Spatie\SslCertificateChainResolver\SslCertificateChainResolver();
$resolver->beforeResolve(function ($url) {
Log::info("Resolving chain for: {$url}");
});
Environment-Specific Settings
.env for timeout and CA store paths:
SSL_CERTIFICATE_CHAIN_RESOLVER_TIMEOUT=10
SSL_CERTIFICATE_CHAIN_RESOLVER_CA_STORE=/etc/ssl/certs/ca-certificates.crt
Proxy Support
$resolver = new \Spatie\SslCertificateChain
How can I help you explore Laravel packages today?