laravel-ssl-keeper maintained by labrodev
Laravel SSL Keeper
TLS certificate expiry reader for Laravel — fetches and parses the peer certificate of any host. Connects over TLS, captures the presented leaf certificate, and returns its "not after" expiry date as a Carbon instance. The package stores nothing and keeps no state: one contract, one call, one date.
Installation
composer require labrodev/laravel-ssl-keeper
Publish the config file:
php artisan vendor:publish --tag=ssl-keeper-config
Optionally override the defaults via environment variables:
SSL_KEEPER_TIMEOUT=10
SSL_KEEPER_PORT=443
Reading a certificate's expiry date
The service provider binds the SslExpiryReader contract to the concrete reader, so type-hint the contract and let the container inject it:
use Labrodev\SslKeeper\Contracts\SslExpiryReader;
use Labrodev\SslKeeper\Exceptions\SslCertificateUnparsable;
use Labrodev\SslKeeper\Exceptions\SslConnectionFailed;
use Labrodev\SslKeeper\Exceptions\SslNoCertificate;
class DomainCertificateCheck
{
public function __construct(
private readonly SslExpiryReader $sslExpiryReader,
) {}
public function __invoke(string $host): void
{
try {
$expiryDate = $this->sslExpiryReader->expiryDate($host);
} catch (SslConnectionFailed|SslNoCertificate|SslCertificateUnparsable $exception) {
// The message is user-presentable: "Unable to connect to example.com:443 (...)."
report($exception);
return;
}
$daysLeft = now()->diffInDays($expiryDate, false);
}
}
Peer verification is deliberately disabled
The TLS handshake runs with verify_peer and verify_peer_name turned off. That is the point of the package: it reads the expiry date even from an expired certificate, a self-signed certificate, or an otherwise broken chain — exactly the certificates a monitoring tool most needs to look at.
Consequently, this reader must never be used as a trust decision. It tells you when a certificate expires, not whether the host should be trusted.
Exceptions
Labrodev\SslKeeper\Exceptions\SslConnectionFailed— the TLS connection could not be established; the message carries host, port, and the socket error.Labrodev\SslKeeper\Exceptions\SslNoCertificate— the handshake succeeded but no peer certificate was presented.Labrodev\SslKeeper\Exceptions\SslCertificateUnparsable— the certificate could not be parsed, or parsed without a usable expiry timestamp.
All three are built through ::make() and carry the hostname in a user-presentable message.
Testing
The raw socket work is isolated in Labrodev\SslKeeper\Services\TlsCertificateFetcher, so consuming apps can fake the network entirely — bind a fake fetcher in the container and every code path above it becomes testable without touching a real host.
composer test # pest
composer phpstan # larastan, level 7
composer pint # laravel preset
composer check # all of the above
License
MIT. See LICENSE.md.