Guide
Check SSL expiry with PowerShell
Use PowerShell to check SSL certificate expiry on Windows servers and workstations via TcpClient TLS—ideal for AD and IIS shops.
Updated Apr 10, 2026 818 words
PowerShell check ssl certificate expiry fits Windows admins who need scheduled Task Scheduler jobs without installing WSL—using .NET
SslStreamagainst live hostnames and ports.
Exchange, IIS, and RDS teams live in PowerShell already. OpenSSL exists on Windows but path and quoting friction push teams toward native TLS APIs. A short script returns NotAfter, days remaining, and colored console output for spot checks.
This guide covers a self-contained function, bulk iteration from a file, and Task Scheduler integration—alongside Bash and Python peers in the same automation cluster.
Active Directory shops standardize on PowerShell because Group Policy and Task Scheduler already run as service accounts with outbound internet access. A powershell check ssl certificate expiry report emailed as CSV attachments fits executives who will not log into Grafana but will open Excel every Monday morning.
Azure AD Application Proxy and RDS gateways present TLS on Windows listeners that Linux-centric monitors skip. Native probes from a management server close that visibility gap without waiting for another team to install WSL.
What to check
Target hostname, port (443 default), and TLS SNI (TargetHost in .NET). Measure certificate expiry on the leaf returned by the server handshake. Internal CA certs work the same if TCP reaches the listener.
CheckCertificateRevocationList behavior in .NET may add latency; for expiry-only monitoring, revocation can be disabled in the callback. PowerShell 7+ on Linux can run the same script for hybrid estates.
Distinguish certificate binding on IIS (netsh/http.sys) from what external clients see through a load balancer—probe the public name, not only localhost.
Step-by-step
Function: Get-TlsCertificateExpiry
function Get-TlsCertificateExpiry {
param(
[Parameter(Mandatory)][string]$HostName,
[int]$Port = 443,
[int]$WarningDays = 30
)
$tcp = New-Object System.Net.Sockets.TcpClient
try {
$tcp.Connect($HostName, $Port)
$ssl = New-Object System.Net.Security.SslStream(
$tcp.GetStream(), $false,
({ $true })
)
$ssl.AuthenticateAsClient($HostName)
$cert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2 $ssl.RemoteCertificate
$days = ($cert.NotAfter - [datetime]::UtcNow).TotalDays
[pscustomobject]@{
Host = $HostName
Port = $Port
NotAfter = $cert.NotAfter.ToUniversalTime()
DaysLeft = [math]::Floor($days)
Subject = $cert.Subject
Issuer = $cert.Issuer
Status = if ($days -lt 0) { 'EXPIRED' }
elseif ($days -lt $WarningDays) { 'WARN' }
else { 'OK' }
}
$ssl.Close()
}
finally {
$tcp.Close()
}
}
Single host
Get-TlsCertificateExpiry -HostName www.example.com
Bulk from text file
Get-Content .\hosts.txt | ForEach-Object {
Get-TlsCertificateExpiry -HostName $_ -WarningDays 14
} | Format-Table -AutoSize
Export CSV for renewal meetings
Get-Content .\hosts.txt | ForEach-Object {
Get-TlsCertificateExpiry -HostName $_
} | Export-Csv -NoTypeInformation .\ssl-expiry-report.csv
Pair with check SSL expiry for multiple domains from CSV conventions by adding columns in Excel afterward.
Scheduled Task (daily)
$action = New-ScheduledTaskAction -Execute 'pwsh.exe' -Argument `
'-File C:\Monitoring\Run-ssl-expiry.ps1'
$trigger = New-ScheduledTaskTrigger -Daily -At 6am
Register-ScheduledTask -TaskName 'SslExpiryCheck' -Action $action -Trigger $trigger
Run-ssl-expiry.ps1 should log WARNING/EXPIRED rows to the Event Log or your SIEM.
IIS local binding check (optional)
Get-WebBinding | ForEach-Object {
$bn = $_.bindingInformation
# Parse host:port:hostname from bindingInformation; probe public DNS name separately
$bn
}
Binding metadata does not replace an external TLS handshake on the customer-facing DNS name.
How to read the output
Objects show NotAfter in UTC, DaysLeft floored to whole days, Subject/Issuer DN strings, and Status OK|WARN|EXPIRED. Negative DaysLeft means the cert is already invalid for date checks.
.NET returns the leaf the server sent; if you receive a wrong host cert on shared IPs, Subject will not match—fix SNI/DNS before trusting dates.
Export objects to CSV with explicit UTC column headers so European colleagues do not misinterpret local Excel timezone conversions. Include Status as the first column for conditional formatting rules that highlight EXPIRED rows in red without macros.
When piping to Format-Table, wide issuer strings truncate—use Format-List in transcripts saved for audits. For bulk runs exceeding five hundred hosts, stream results to disk with Export-Csv -Append per host instead of holding everything in memory.
Compare DaysLeft against business calendars: whole-day flooring hides hosts that expire later tonight UTC but still during US business hours. Some teams add one safety day to WarningDays on executive-facing hosts for that reason.
Common mistakes
Connecting to localhost while users hit a CDN—dates reflect the origin loopback binding, not the edge cert. Always use public hostnames.
Using [datetime]::Now instead of UTC when comparing to NotAfter.ToUniversalTime() skews warnings near midnight.
Bulk scripts without try/catch stop on the first firewall drop. Wrap Connect failures:
try { Get-TlsCertificateExpiry $_ } catch { Write-Warning "Failed $_" }
Assuming Install-Module PSPKI is required—it helps AD CS shops but public TLS checks only need SslStream.
Relying on certificate store expiry for bindings without probing listeners after renewal—reload IIS and re-run external checks per OpenSSL check certificate expiry date runbooks on Linux peers.
When to use automation
Task Scheduler on each management server works for regional probes. Centralize results with Azure Monitor, SCOM, or log shipping from the script’s CSV output.
Set WarningDays to match change windows—14 for manual OV, 30 for automated ACME. Alert separately on Connect failures versus EXPIRED.
Combine with ssldays or similar external vantage checks so split-horizon DNS does not false-negative. For Linux-centric pipelines, mirror logic in Bash script SSL certificate expiry. Keep a printed one-page cheat sheet beside NOC desks listing the Task Scheduler name, script path, on-call alias, and last successful run timestamp for quick verification during incidents.
Store scheduled task passwords in Windows Credential Manager rather than plain XML task exports. Rotation procedures should include updating task principals when service accounts roll.
Enable transcript logging during bulk runs so support can see which hostname failed Connect without reproducing the entire sweep.
Publish the function to an internal PowerShell gallery with semantic versioning. Teams pin versions in deployment scripts to avoid surprise behavior changes during gallery publishes.
Document .NET TLS version defaults used by SslStream; older Windows builds may negotiate protocols you must align with corporate hardening baselines separately from expiry monitoring.
Windows patching cycles sometimes upgrade Schannel behavior under the same script version, producing different DaysLeft without code changes. Track OS patch KB numbers beside weekly CSV exports so mysterious deltas have explainable causes during audits.
Delegated administration in large AD forests benefits from publishing the function through constrained language mode approved module paths. Helpdesk tiers can run checks without full local administrator rights on management servers if execution policy and JEA endpoints are designed intentionally.
Hybrid cloud estates should run PowerShell probes from both on-prem management jumps and Azure automation accounts with outbound internet. Compare outputs when split-brain DNS causes different certificates per vantage; mismatches warrant routing tickets to networking before certificate renewal.
Executive reporting occasionally asks for certificate issuers aggregated by business unit. Group-Object on Issuer DN after CSV export satisfies those asks without building new dashboards, provided host lists include department metadata maintained by service owners.
Training materials should contrast this script with browser padlock UIs: executives understand Excel traffic-light formatting on Status long before they interpret X.509 fields. Meet stakeholders where they are, then deepen technical detail in appendix openssl sessions for engineers.
Add Pester tests mocking TcpClient failures so CI validates your function before Task Scheduler picks up broken releases. Infrastructure as code repositories should gate merges on those tests alongside PSScriptAnalyzer style rules and mandatory code owner review from the Windows platform team responsible for Task Scheduler infrastructure.
Record baseline DaysLeft distributions quarterly; gradual downward trends across the fleet indicate shorter CA lifetimes or systematic deployment delays worth addressing in platform engineering roadmaps.
Field support technicians carrying laptops without openssl benefit from a signed PowerShell module they can import during customer site visits. A thirty second check ends debates about whether the lobby Wi-Fi or the corporate load balancer expired first.
Identity governance teams syncing service account ownership should tag scheduled tasks to individuals departing the company. Orphaned tasks running expired-host checks nobody reads waste electricity and hide stale hostnames in forgotten share paths.
Retail point of sale environments locked down to PowerShell constrained language still permit read-only TLS handshakes when modules are allowlisted. Build those allowlists during compliance reviews instead of after auditors discover unsigned internet download scripts.
European subsidiaries running GDPR programs appreciate CSV exports with minimized columns. Strip issuer DN detail if unnecessary for renewal conversations, retaining hostname, NotAfter, and Status only for privacy officers who review monitoring artifacts quarterly.
Collaboration with network teams clarifies when proxy authentication interferes with TcpClient.Connect. Document PAC files and explicit proxy exceptions for management hosts running scheduled tasks so UNREACHABLE does not mean expired.
Windows Server Core deployments without browsers still benefit from scheduled Task Scheduler exports sent to centralized logging, proving powershell check ssl certificate expiry coverage on headless roles that never install Chrome solely for padlock screenshots during audits or quarterly compliance walkthroughs with external assessors.
Document escalation paths when Status shows EXPIRED on a hostname owned by another department; monitoring without ownership creates noise without remediation.
Frequently asked questions
Can I check client certificates?
This function validates server certs presented to your client. Mutual TLS requires client cert configuration elsewhere.
What about STARTTLS on SMTP?
SslStream expects immediate TLS. Use OpenSSL or .NET mail libraries for SMTP STARTTLS—see check mail server TLS expiry.
Why do DaysLeft differ from openssl by one day?
Timezone and floor rounding differ. Standardize on UTC in tickets and show the raw NotAfter timestamp.
Should I use Invoke-WebRequest?
Invoke-WebRequest hides cert details behind redirects and proxy settings. SslStream gives a direct handshake on the target port.
See what the server presents right now
Free live certificate check — no signup.