tls-check
Inspect TLS/SSL certificates in Node.js using the built-in tls module -- connect, extract cert metadata, calculate days until expiry, and handle errors.
Best use case
tls-check is best used when you need a repeatable AI agent workflow instead of a one-off prompt.
Inspect TLS/SSL certificates in Node.js using the built-in tls module -- connect, extract cert metadata, calculate days until expiry, and handle errors.
Teams using tls-check should expect a more consistent output, faster repeated execution, less prompt rewriting.
When to use this skill
- You want a reusable workflow that can be run more than once with consistent structure.
When not to use this skill
- You only need a quick one-off answer and do not need a reusable workflow.
- You cannot install or maintain the underlying files, dependencies, or repository context.
Installation
Claude Code / Cursor / Codex
Manual Installation
- Download SKILL.md from GitHub
- Place it in
.claude/skills/tls-check/SKILL.mdinside your project - Restart your AI agent — it will auto-discover the skill
How tls-check Compares
| Feature / Agent | tls-check | Standard Approach |
|---|---|---|
| Platform Support | Not specified | Limited / Varies |
| Context Awareness | High | Baseline |
| Installation Complexity | Unknown | N/A |
Frequently Asked Questions
What does this skill do?
Inspect TLS/SSL certificates in Node.js using the built-in tls module -- connect, extract cert metadata, calculate days until expiry, and handle errors.
Where can I find the source code?
You can find the source code on GitHub using the link provided at the top of the page.
SKILL.md Source
## When to use this skill
Use this skill when implementing certificate inspection in Node.js without external HTTP clients: connecting via TLS, extracting expiry dates and issuer information, handling timeouts and errors, and calculating days remaining.
## Install
No additional packages needed. Uses Node.js built-in `tls` module.
## Basic TLS Connect and Cert Extraction
```typescript
import tls from 'node:tls';
interface CertInfo {
expiresAt: Date;
issuedAt: Date;
issuer: string;
subject: string;
sans: string[];
serialNumber: string;
daysUntilExpiry: number;
}
function checkCert(hostname: string, port = 443): Promise<CertInfo> {
return new Promise((resolve, reject) => {
const socket = tls.connect(
{
host: hostname,
port,
servername: hostname, // SNI: required for virtual hosting
rejectUnauthorized: false, // inspect expired/self-signed certs too
},
() => {
const cert = socket.getPeerCertificate(false);
socket.end();
if (!cert || !cert.valid_to) {
reject(new Error('No certificate in TLS handshake'));
return;
}
const expiresAt = new Date(cert.valid_to);
const issuedAt = new Date(cert.valid_from);
const now = new Date();
const daysUntilExpiry = Math.floor(
(expiresAt.getTime() - now.getTime()) / (1000 * 60 * 60 * 24),
);
resolve({
expiresAt,
issuedAt,
issuer: cert.issuer?.O ?? cert.issuer?.CN ?? '',
subject: cert.subject?.CN ?? '',
sans: parseSANs(cert.subjectaltname ?? ''),
serialNumber: cert.serialNumber ?? '',
daysUntilExpiry,
});
},
);
socket.setTimeout(10_000, () => {
socket.destroy();
reject(new Error('Connection timeout after 10000ms'));
});
socket.on('error', (err) => {
reject(err);
});
});
}
function parseSANs(subjectaltname: string): string[] {
if (!subjectaltname) return [];
return subjectaltname
.split(', ')
.filter((s) => s.startsWith('DNS:'))
.map((s) => s.replace(/^DNS:/, ''));
}
```
## Certificate Object Fields
`getPeerCertificate(false)` returns a `tls.PeerCertificate` with these useful fields:
| Field | Type | Example |
|-------|------|---------|
| `subject.CN` | string | `api.example.com` |
| `issuer.O` | string | `Let's Encrypt` |
| `issuer.CN` | string | `R3` |
| `valid_from` | string | `Jan 27 00:00:00 2024 GMT` |
| `valid_to` | string | `Apr 27 00:00:00 2024 GMT` |
| `subjectaltname` | string | `DNS:api.example.com, DNS:www.api.example.com` |
| `serialNumber` | string | `04A13B9C...` (hex, uppercase) |
| `fingerprint` | string | `AA:BB:CC:...` |
`valid_from` and `valid_to` are parseable directly with `new Date()`.
## Non-Default Ports
```typescript
// SMTP with STARTTLS (not supported via tls.connect -- use separate approach)
// HTTPS on non-standard port
const info = await checkCert('api.example.com', 8443);
// LDAPS
const info = await checkCert('ldap.corp.net', 636);
```
## Handling Expired Certificates
`rejectUnauthorized: false` allows the handshake to complete even if:
- The certificate is already expired
- The certificate is self-signed
- The chain is incomplete
This means `daysUntilExpiry` will be negative for expired certs. Store as-is; the UI renders as "Expired".
## Concurrency Limiter
```typescript
import { checkCert } from './checker.js';
async function checkAll(hosts: Array<{ hostname: string; port: number }>, concurrency = 5) {
const results: Map<string, CertInfo | Error> = new Map();
const queue = [...hosts];
async function worker() {
while (queue.length > 0) {
const host = queue.shift();
if (!host) break;
const key = `${host.hostname}:${host.port}`;
try {
results.set(key, await checkCert(host.hostname, host.port));
} catch (err) {
results.set(key, err instanceof Error ? err : new Error(String(err)));
}
}
}
await Promise.all(Array.from({ length: concurrency }, () => worker()));
return results;
}
```
## Checking Multiple Certificates on the Same IP
When a server hosts multiple domains, pass `servername` to use SNI:
```typescript
// Both hit the same IP but get different certificates via SNI
const cert1 = await checkCert('app.example.com', 443);
const cert2 = await checkCert('api.example.com', 443);
```
`servername` in the options object sets the SNI extension. Always set it equal to the logical hostname, not the IP.
## Error Handling
| Error Code | Cause | Action |
|------------|-------|--------|
| `ECONNREFUSED` | Nothing listening on the port | Mark as error, show in UI |
| `ECONNRESET` | Connection dropped by server | Retry once, then mark error |
| `ETIMEDOUT` | No response within timeout | Check network path; increase timeout |
| `ENOTFOUND` | DNS resolution failure | Verify hostname spelling |
| `CERT_HAS_EXPIRED` | Not thrown (rejectUnauthorized false) | `daysUntilExpiry` will be negative |
| `SELF_SIGNED_CERT` | Not thrown (rejectUnauthorized false) | Certificate is still inspectable |
## Days Until Expiry Calculation
```typescript
function daysUntilExpiry(expiresAt: Date): number {
const now = Date.now();
const expiry = expiresAt.getTime();
return Math.floor((expiry - now) / (1000 * 60 * 60 * 24));
}
// Returns negative values for expired certs
// Returns 0 for certs expiring today
```
## Checking Self-Signed Certificates
```typescript
// Self-signed or private CA -- still readable with rejectUnauthorized: false
const cert = await checkCert('internal.corp.net', 443);
// cert.issuer will equal cert.subject for self-signed
const isSelfSigned = cert.issuer === cert.subject;
```
## One-Shot vs. Streaming
`tls.connect` with the callback approach is one-shot: connect, read cert, close. This is appropriate for periodic monitoring. Do not keep the socket open between checks.Related Skills
Skill: Uptime Monitoring
## Overview
Skill: Status Page
## Overview
Skill: unit-conversion
## Overview
Skill: recipe-scaler
## Overview
reading-list
Operate the reading-list API to save, manage, tag, search, and export articles.
email-digest
Configure, test, and troubleshoot the reading-list daily email digest delivered via nodemailer.
websocket-realtime
Use the WebSocket connection in poll-builder to receive live vote updates. Use when you need to stream real-time poll results, monitor a poll for new votes, or build a live dashboard. Triggers include "live results", "real-time updates", "stream votes", "watch poll", or "WebSocket".
poll-builder
Self-hosted poll creation tool with real-time results. Use when you need to create a poll, check vote counts, close a poll, export results, or get the shareable link for a poll. Triggers include "create poll", "vote", "poll results", "survey", "collect votes", "share poll", or any task involving polling or voting.
Skill: personal-finance
## Overview
Skill: csv-import
## Overview
Skill: Syntax Highlighting
## Purpose
Skill: Pastebin Core
## Purpose