spatie/ssl-certificate
Retrieve and validate SSL/TLS certificates for any host in PHP. This package fetches certificate details like issuer, validity dates, and expiration status, making it easy to monitor domains and detect upcoming certificate issues in Laravel apps.
Installation:
composer require spatie/ssl-certificate
Add the namespace to your composer.json autoload or use it directly in your code.
First Use Case: Fetch and validate a certificate for a domain:
use Spatie\SslCertificate\SslCertificate;
$certificate = SslCertificate::createForHostName('example.com');
$isValid = $certificate->isValid();
Key Methods to Explore:
expirationDate(): Get the expiration date as a Carbon instance.daysUntilExpirationDate(): Days remaining until expiration.getIssuer(): Issuer of the certificate (e.g., "Let's Encrypt Authority X3").getDomain(): Primary domain covered by the certificate.Spatie\SslCertificate\Exceptions for error handling (e.g., CouldNotDownloadCertificate).SslCertificate::download()->forHost('example.com') for custom configurations (ports, IPs, socket options).Certificate Validation in Laravel Controllers:
public function checkCertificate(Request $request, string $domain)
{
$certificate = SslCertificate::createForHostName($domain);
if (!$certificate->isValid()) {
return response()->json(['error' => 'Certificate expired'], 400);
}
return response()->json(['valid' => true, 'expires' => $certificate->expirationDate()]);
}
Batch Validation for Multiple Domains:
$domains = ['example.com', 'api.example.com'];
$results = collect($domains)->map(fn($domain) => [
'domain' => $domain,
'valid' => SslCertificate::createForHostName($domain)->isValid(),
]);
Scheduled Certificate Expiry Alerts:
// In a Laravel scheduled task (app/Console/Kernel.php)
protected function schedule(Schedule $schedule)
{
$schedule->command('check:certificates')->daily();
}
// Command to check critical certificates
public function handle()
{
$criticalDomains = config('certificates.critical');
foreach ($criticalDomains as $domain) {
$cert = SslCertificate::createForHostName($domain);
if ($cert->daysUntilExpirationDate() < 30) {
Log::warning("Certificate for {$domain} expires in {$cert->daysUntilExpirationDate()} days");
}
}
}
Custom Socket Context for Proxies:
$certificate = SslCertificate::download()
->withSocketContextOptions([
'ssl' => [
'verify_peer' => false, // Bypass for internal services
'verify_peer_name' => false,
],
])
->forHost('internal-service.local');
Fingerprint Comparison for Security Audits:
$expectedFingerprint = 'SHA256:...';
$actualFingerprint = SslCertificate::createForHostName('secure.example.com')->getFingerprintSha256();
if ($expectedFingerprint !== $actualFingerprint) {
throw new \RuntimeException('Certificate fingerprint mismatch!');
}
Laravel Service Providers: Bind the certificate checker to the container for dependency injection:
$this->app->bind(SslCertificate::class, function () {
return new SslCertificate();
});
Form Request Validation:
Validate certificate validity in a FormRequest:
public function rules()
{
return [
'domain' => [
'required',
function ($attribute, $value, $fail) {
$cert = SslCertificate::createForHostName($value);
if (!$cert->isValid()) {
$fail('The certificate for the domain is invalid.');
}
},
],
];
}
API Rate Limiting: Use certificate validation to rate-limit API calls from specific domains:
$cert = SslCertificate::createForHostName($request->getHost());
$rateLimiter->hit($cert->getFingerprintSha256());
Caching Certificates: Cache certificate data to avoid repeated network calls (e.g., in a queue worker):
$cacheKey = "certificate:{$domain}";
$certificate = Cache::remember($cacheKey, now()->addHours(1), function () use ($domain) {
return SslCertificate::createForHostName($domain);
});
Certificate Authority Trust:
stream_context_create() with verify_peer and cafile options for CA validation:
$context = stream_context_create([
'ssl' => [
'verify_peer' => true,
'cafile' => '/path/to/cacert.pem',
],
]);
$certificate = SslCertificate::download()
->withSocketContextOptions(['ssl' => ['verify_peer' => true]])
->forHost('example.com');
Wildcard Domains:
isValid('example.com') returns false for a wildcard certificate (*.example.com).getAdditionalDomains() to check coverage:
$cert = SslCertificate::createForHostName('*.example.com');
$isCovered = in_array('example.com', $cert->getAdditionalDomains());
IPv6 Addresses:
InvalidIpAddress if not formatted correctly.$certificate = SslCertificate::download()
->fromIpAddress('2a00:1450:4001:80e::200e')
->forHost('google.com');
Expired Certificates:
createForHostName() fails for expired certificates.$verifyCertificate = false to bypass:
$certificate = SslCertificate::createForHostName('expired.badssl.com', 5, false);
Port-Specific Certificates:
443 vs. 8443).$certificate = SslCertificate::createForHostName('example.com:8443');
Carbon Version Conflicts:
daysUntilExpirationDate() may return negative values in Carbon v3.absoluteDiffInDays() for clarity:
$daysRemaining = $certificate->expirationDate()->diffInDays(now(), false);
Public Key Algorithm Deprecations:
RSA-SHA1).$algorithm = $certificate->getPublicKeyAlgorithm();
if (str_contains($algorithm, 'SHA1')) {
Log::warning("Insecure algorithm detected: {$algorithm}");
}
Enable Stream Warnings: Add this to your script to debug connection issues:
error_reporting(E_ALL);
ini_set('display_errors', 1);
Inspect Raw Certificate Data: Convert the certificate to an array for debugging:
dd($certificate->toArray());
Timeout Handling: Increase the timeout for slow connections:
$certificate = SslCertificate::createForHostName('slow.example.com', 10); // 10-second timeout
Certificate Conversion: Convert between formats (e.g., PEM to DER) for testing:
$pemData = $certificate->toPem();
file_put_contents('certificate.pem', $pemData);
SslCertificate class to add domain-specific rules:
class CustomCertificate extends SslCertificate
{
public function isValidForOrganization(string $expectedOrg): bool
{
return $this->isValid() && $this->get
How can I help you explore Laravel packages today?