labrodev / laravel-ssl-keeper
TLS certificate expiry reader for Laravel — fetches and parses the peer certificate of any host.
Requires
- php: ^8.4
- ext-openssl: *
- guzzlehttp/guzzle: ^7.8
- illuminate/http: ^13.0
- illuminate/support: ^13.0
- spatie/laravel-package-tools: ^1.16
Requires (Dev)
- driftingly/rector-laravel: ^2.5
- larastan/larastan: ^3.0
- laravel/pint: ^1.0
- orchestra/testbench: ^11.0
- pestphp/pest: ^4.0
README
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.