diff --git a/constructs/ssl-monitor.mdx b/constructs/ssl-monitor.mdx
new file mode 100644
index 00000000..325cd397
--- /dev/null
+++ b/constructs/ssl-monitor.mdx
@@ -0,0 +1,509 @@
+---
+title: 'SslMonitor Construct'
+description: 'Learn how to configure SSL monitors with the Checkly CLI.'
+sidebarTitle: 'SSL Monitor'
+canonical: 'https://www.checklyhq.com/docs/constructs/ssl-monitor/'
+---
+
+import GeneralMonitorOptionsTable from '/snippets/general-monitor-options-table.mdx';
+
+
+Learn more about SSL Monitors in [the SSL monitor overview](/detect/uptime-monitoring/ssl-monitors/overview).
+
+
+Use SSL Monitors to verify the health and security posture of your TLS certificates. The examples below show how to configure monitoring for common scenarios.
+
+
+Before creating SSL Monitors, ensure you have:
+
+- An initialized Checkly CLI project
+- Network access to the HTTPS endpoint you want to monitor
+- The hostname (and optionally port) of the target server
+
+For additional setup information, see [CLI overview](/cli/overview).
+
+
+
+
+```ts Basic Example
+import { Frequency, SslMonitor } from "checkly/constructs"
+
+new SslMonitor("homepage-ssl", {
+ name: "Homepage Certificate",
+ description: "Monitors the TLS certificate for `example.com`.",
+ frequency: Frequency.EVERY_1H,
+ request: {
+ hostname: "example.com",
+ sslConfig: {
+ alertDaysBeforeExpiry: 30,
+ },
+ },
+})
+```
+
+```ts Advanced Example
+import {
+ Frequency,
+ SslAssertionBuilder,
+ SslMonitor,
+ TlsVersion,
+} from "checkly/constructs"
+
+new SslMonitor("api-ssl-advanced", {
+ name: "API Certificate — strict baseline",
+ description: "Enforces TLS 1.3, checks chain trust, and alerts 45 days before expiry.",
+ activated: true,
+ frequency: Frequency.EVERY_10M,
+ locations: ["us-east-1", "eu-west-1"],
+ degradedResponseTime: 1000,
+ maxResponseTime: 5000,
+ request: {
+ hostname: "api.example.com",
+ port: 443,
+ sslConfig: {
+ alertDaysBeforeExpiry: 45,
+ handshakeTimeout: 5000,
+ securityBaseline: {
+ enabled: true,
+ minTLSVersion: { value: "TLS1.2", severity: "fail" },
+ recommendedTLSVersion: { value: "TLS1.3", severity: "degrade" },
+ weakCipherSuite: { severity: "fail" },
+ ocspMustStapleRespected: { severity: "degrade" },
+ },
+ },
+ assertions: [
+ SslAssertionBuilder.connection("chainTrusted").equals(true),
+ SslAssertionBuilder.connection("hostnameVerified").equals(true),
+ SslAssertionBuilder.connection("tlsVersion").equals(TlsVersion.TLS1_3),
+ SslAssertionBuilder.certificate("daysUntilExpiry").greaterThan(45),
+ ],
+ },
+})
+```
+
+
+
+## Configuration
+
+SSL monitors have SSL-specific settings and inherit the standard monitor options shared across all check types.
+
+
+
+
+| Parameter | Type | Required | Default | Description |
+|-----------|------|----------|---------|-------------|
+| `request` | `object` | ✅ | - | SSL connection and certificate configuration |
+| `degradedResponseTime` | `number` | ❌ | `3000` | Handshake time in milliseconds at which the monitor is marked degraded |
+| `maxResponseTime` | `number` | ❌ | `10000` | Handshake time in milliseconds at which the monitor is marked failed |
+
+
+
+
+
+
+
+
+
+### `SslMonitor` Options
+
+
+
+SSL connection configuration that defines the target host and all TLS-specific options.
+
+**Usage:**
+
+```ts
+new SslMonitor("my-ssl-monitor", {
+ name: "Example SSL Monitor",
+ request: {
+ hostname: "example.com",
+ sslConfig: {
+ alertDaysBeforeExpiry: 30,
+ },
+ },
+})
+```
+
+**Parameters:**
+
+| Parameter | Type | Required | Default | Description |
+|-----------|------|----------|---------|-------------|
+| `hostname` | `string` | ✅ | - | The hostname to connect to and validate (no scheme or port) |
+| `port` | `number` | ❌ | `443` | TCP port to connect to (1–65535) |
+| `ipFamily` | `string` | ❌ | `'IPv4'` | IP family: `'IPv4'` \| `'IPv6'` |
+| `sslConfig` | `SslConfig` | ✅ | - | TLS handshake and certificate options (see below) |
+| `assertions` | `SslAssertion[]` | ❌ | `[]` | Certificate/handshake assertions using `SslAssertionBuilder` |
+
+
+
+
+TLS handshake time in milliseconds at or above which the monitor is marked as degraded (warning state). Range: 0–30,000 ms. Must be ≤ `maxResponseTime`.
+
+**Usage:**
+
+```ts highlight={3}
+new SslMonitor("my-ssl", {
+ name: "Example SSL",
+ degradedResponseTime: 1000,
+ maxResponseTime: 5000,
+ request: {
+ hostname: "example.com",
+ sslConfig: {},
+ },
+})
+```
+
+
+
+TLS handshake time in milliseconds at or above which the monitor is marked as failed. Range: 0–30,000 ms.
+
+**Usage:**
+
+```ts highlight={4}
+new SslMonitor("my-ssl", {
+ name: "Example SSL",
+ degradedResponseTime: 1000,
+ maxResponseTime: 5000,
+ request: {
+ hostname: "example.com",
+ sslConfig: {},
+ },
+})
+```
+
+
+### `SslConfig` Options
+
+
+Raise a degraded alert when the certificate is within this many days of expiry. Range: 1–365.
+
+```ts
+sslConfig: {
+ alertDaysBeforeExpiry: 30, // Warn 30 days before expiry
+}
+```
+
+
+
+SNI server name to send during the TLS handshake. Useful when a single IP hosts multiple certificates. Defaults to `hostname` when unset.
+
+```ts
+sslConfig: {
+ serverName: "tenant-a.example.com",
+}
+```
+
+
+
+Maximum milliseconds to wait for the TLS handshake to complete. Range: 1,000–30,000 ms.
+
+```ts
+sslConfig: {
+ handshakeTimeout: 5000,
+}
+```
+
+
+
+When `true`, the certificate chain is not verified against system trusted roots. The certificate is still inspected for expiry and the security baseline. Use for internal or self-signed certificates.
+
+```ts
+sslConfig: {
+ skipChainValidation: true,
+}
+```
+
+
+
+Enables mutual TLS by sending a client certificate during the handshake.
+
+- `'auto'` — Checkly selects a stored client certificate automatically.
+- `'explicit'` — uses the certificate referenced by `sslClientCertificateId`.
+
+Omit to connect without a client certificate.
+
+```ts
+sslConfig: {
+ clientCertificateMode: "explicit",
+ sslClientCertificateId: "cert_abc123",
+}
+```
+
+
+
+The ID of the stored client certificate to present during the TLS handshake. Required when `clientCertificateMode` is `'explicit'`. Client certificates are managed under **Settings → Client Certificates** in the Checkly dashboard.
+
+```ts
+sslConfig: {
+ clientCertificateMode: "explicit",
+ sslClientCertificateId: "cert_abc123",
+}
+```
+
+
+
+Override the account-level security baseline for this monitor. Omit to inherit the account default.
+
+```ts
+sslConfig: {
+ securityBaseline: {
+ enabled: true,
+ minTLSVersion: { value: "TLS1.2", severity: "fail" },
+ recommendedTLSVersion: { value: "TLS1.3", severity: "degrade" },
+ weakCipherSuite: { severity: "fail" },
+ weakSignatureAlgorithm: { severity: "fail" },
+ knownBadCA: { severity: "fail" },
+ ocspMustStapleRespected: { severity: "degrade" },
+ },
+}
+```
+
+**`SecurityBaseline` parameters:**
+
+| Parameter | Type | Default severity | Description |
+|-----------|------|-----------------|-------------|
+| `enabled` | `boolean` | `true` | Enable or disable baseline evaluation |
+| `minTLSVersion` | `{ value?: string, severity? }` | `fail` | Minimum required TLS version (e.g. `'TLS1.2'`) |
+| `minKeySizeBits` | `{ value?: number, severity? }` | `fail` | Minimum RSA key size in bits |
+| `weakSignatureAlgorithm` | `{ severity? }` | `fail` | Reject weak signature algorithms (MD2-RSA, MD5-RSA, SHA1-RSA, DSA-SHA1, ECDSA-SHA1) on non-root certificates |
+| `weakCipherSuite` | `{ severity? }` | `fail` | Reject known-weak cipher suites |
+| `knownBadCA` | `{ severity? }` | `fail` | Reject distrusted certificate authorities |
+| `recommendedTLSVersion` | `{ value?: string, severity? }` | `ignore` | Advisory minimum TLS version |
+| `recommendedKeySizeBits` | `{ value?: number, severity? }` | `ignore` | Advisory minimum RSA key size |
+| `ocspMustStapleRespected` | `{ severity? }` | `ignore` | Alert when must-staple cert is missing an OCSP staple |
+| `sctPresent` | `{ severity? }` | `ignore` | Alert when no Signed Certificate Timestamp is observed |
+
+Each rule's `severity` can be `'fail'` \| `'degrade'` \| `'ignore'`.
+
+
+### `SslMonitor` Assertions
+
+Define `assertions` using the `SslAssertionBuilder`. Five entry points map to the assertion sources — `certificate`/`connection` take a `property` (a field selector); `jsonResponse`/`textResponse` take a JSONPath/regex:
+
+| Builder | Source | Description |
+|---------|--------|-------------|
+| `certificate(property)` | `CERTIFICATE` | Assert on a leaf-certificate field |
+| `connection(property)` | `CONNECTION` | Assert on a TLS handshake / connection field |
+| `responseTime()` | `RESPONSE_TIME` | TLS handshake duration in milliseconds |
+| `jsonResponse(jsonPath)` | `JSON_RESPONSE` | Assert on any response field via a JSONPath expression |
+| `textResponse(regex?)` | `TEXT_RESPONSE` | Extract a value from the serialized response with a regex, then compare |
+
+`certificate(property)` properties: `daysUntilExpiry` / `keySizeBits` (number — `EQUALS` / `NOT_EQUALS` / `GREATER_THAN` / `LESS_THAN`), `subjectCN` / `issuerCN` (string — also `CONTAINS` / `NOT_CONTAINS`), `serialNumber` / `fingerprintSha256` / `issuerFingerprintSha256` / `keyAlgorithm` / `signatureAlgorithm` (exact — `EQUALS` / `NOT_EQUALS` only), `sans` (list — `CONTAINS` / `NOT_CONTAINS`), `selfSigned` / `isCA` (boolean — `EQUALS`).
+
+`connection(property)` properties: `tlsVersion` (version — `EQUALS` / `NOT_EQUALS` / `GREATER_THAN` / `LESS_THAN`, ordered TLS1.0 < TLS1.3), `cipherSuite` / `resolvedIp` (string — also `CONTAINS` / `NOT_CONTAINS`), `ocspStatus` (exact — `good` / `revoked` / `unknown`), `hostnameVerified` / `chainTrusted` / `ocspStapled` (boolean — `EQUALS`).
+
+Examples:
+
+```ts
+// Alert when fewer than 30 days remain before expiry
+SslAssertionBuilder.certificate("daysUntilExpiry").greaterThan(30)
+// Equivalent to: { source: 'CERTIFICATE', property: 'daysUntilExpiry', comparison: 'GREATER_THAN', target: '30' }
+
+// The certificate chain must be trusted
+SslAssertionBuilder.connection("chainTrusted").equals(true)
+// Equivalent to: { source: 'CONNECTION', property: 'chainTrusted', comparison: 'EQUALS', target: 'true' }
+
+// Assert an exact TLS version — tlsVersion is ordered, so greaterThan/lessThan also work
+// (the security baseline's minTLSVersion is the usual way to enforce a "1.2-or-newer" floor)
+SslAssertionBuilder.connection("tlsVersion").equals(TlsVersion.TLS1_3)
+// Equivalent to: { source: 'CONNECTION', property: 'tlsVersion', comparison: 'EQUALS', target: 'TLS1.3' }
+
+// Pin the cipher suite
+SslAssertionBuilder.connection("cipherSuite").equals(CipherSuite.TLS_AES_256_GCM_SHA384)
+// Equivalent to: { source: 'CONNECTION', property: 'cipherSuite', comparison: 'EQUALS', target: 'TLS_AES_256_GCM_SHA384' }
+
+// Verify a specific issuer
+SslAssertionBuilder.certificate("issuerCN").equals("Let's Encrypt")
+// Equivalent to: { source: 'CERTIFICATE', property: 'issuerCN', comparison: 'EQUALS', target: "Let's Encrypt" }
+
+// Reject weak keys (the security baseline's minKeySizeBits enforces a hard minimum)
+SslAssertionBuilder.certificate("keySizeBits").greaterThan(1024)
+// Equivalent to: { source: 'CERTIFICATE', property: 'keySizeBits', comparison: 'GREATER_THAN', target: '1024' }
+
+// Assert on a nested chain field with a JSONPath expression
+SslAssertionBuilder.jsonResponse("$.chain[0].keySizeBits").greaterThan(2048)
+// Equivalent to: { source: 'JSON_RESPONSE', property: '$.chain[0].keySizeBits', comparison: 'GREATER_THAN', target: '2048' }
+```
+
+Use the `TlsVersion` and `CipherSuite` constants for type-safe comparisons:
+
+```ts
+import {
+ CipherSuite,
+ SslAssertionBuilder,
+ TlsVersion,
+} from "checkly/constructs"
+
+SslAssertionBuilder.connection("tlsVersion").equals(TlsVersion.TLS1_3)
+SslAssertionBuilder.connection("cipherSuite").equals(CipherSuite.TLS_AES_128_GCM_SHA256)
+```
+
+Learn more in our docs on [Assertions](/detect/assertions).
+
+### General Monitor Options
+
+
+Friendly name for your SSL Monitor, displayed in the Checkly dashboard and used in notifications.
+
+```ts highlight={2}
+new SslMonitor("my-ssl-monitor", {
+ name: "Homepage Certificate",
+ /* More options ... */
+})
+```
+
+
+
+How often the SSL Monitor should run. Use the `Frequency` enum to set the check interval.
+
+```ts highlight={3}
+new SslMonitor("my-ssl-monitor", {
+ name: "Homepage Certificate",
+ frequency: Frequency.EVERY_1H,
+ /* More options ... */
+})
+```
+
+**Available frequencies**: `EVERY_1M`, `EVERY_2M`, `EVERY_5M`, `EVERY_10M`, `EVERY_15M`, `EVERY_30M`, `EVERY_1H`, `EVERY_2H`, `EVERY_3H`, `EVERY_6H`, `EVERY_12H`, `EVERY_24H`. SSL monitors do not support sub-minute frequencies (`EVERY_10S` / `EVERY_20S` / `EVERY_30S`).
+
+
+
+Array of [public location codes](/concepts/locations/#public-locations) where the monitor should run from. Multiple locations provide geographic coverage.
+
+```ts highlight={3}
+new SslMonitor("global-ssl", {
+ name: "Global Certificate Monitor",
+ locations: ["us-east-1", "eu-west-1", "ap-southeast-1"],
+ /* More options ... */
+})
+```
+
+
+
+Whether the SSL Monitor is enabled and will run according to its schedule.
+
+```ts highlight={3}
+new SslMonitor("my-ssl-monitor", {
+ name: "Homepage Certificate",
+ activated: false, // Disabled monitor
+ /* More options ... */
+})
+```
+
+
+## Examples
+
+
+
+ ```ts
+ import { Frequency, SslMonitor } from "checkly/constructs"
+
+ new SslMonitor("homepage-ssl", {
+ name: "Homepage Certificate",
+ frequency: Frequency.EVERY_1H,
+ locations: ["us-east-1", "eu-west-1"],
+ request: {
+ hostname: "example.com",
+ sslConfig: {
+ alertDaysBeforeExpiry: 30,
+ },
+ },
+ })
+ ```
+
+
+
+ ```ts
+ import { Frequency, SslAssertionBuilder, SslMonitor, TlsVersion } from "checkly/constructs"
+
+ new SslMonitor("api-strict-tls", {
+ name: "API — Strict TLS Policy",
+ frequency: Frequency.EVERY_10M,
+ locations: ["us-east-1", "eu-central-1"],
+ request: {
+ hostname: "api.example.com",
+ sslConfig: {
+ alertDaysBeforeExpiry: 45,
+ securityBaseline: {
+ enabled: true,
+ minTLSVersion: { value: "TLS1.2", severity: "fail" },
+ recommendedTLSVersion: { value: "TLS1.3", severity: "degrade" },
+ },
+ },
+ assertions: [
+ SslAssertionBuilder.connection("chainTrusted").equals(true),
+ SslAssertionBuilder.connection("hostnameVerified").equals(true),
+ SslAssertionBuilder.connection("tlsVersion").equals(TlsVersion.TLS1_3),
+ ],
+ },
+ })
+ ```
+
+
+
+ ```ts
+ import { Frequency, SslAssertionBuilder, SslMonitor } from "checkly/constructs"
+
+ new SslMonitor("internal-ssl", {
+ name: "Internal Service Certificate",
+ frequency: Frequency.EVERY_6H,
+ privateLocations: ["my-private-location"],
+ request: {
+ hostname: "internal.corp.example.com",
+ port: 8443,
+ sslConfig: {
+ skipChainValidation: true,
+ alertDaysBeforeExpiry: 14,
+ },
+ assertions: [
+ SslAssertionBuilder.certificate("selfSigned").equals(true),
+ SslAssertionBuilder.certificate("daysUntilExpiry").greaterThan(7),
+ ],
+ },
+ })
+ ```
+
+
+
+ ```ts
+ import { Frequency, SslAssertionBuilder, SslMonitor } from "checkly/constructs"
+
+ new SslMonitor("mtls-endpoint", {
+ name: "mTLS API Gateway",
+ frequency: Frequency.EVERY_5M,
+ request: {
+ hostname: "mtls.api.example.com",
+ sslConfig: {
+ clientCertificateMode: "explicit",
+ sslClientCertificateId: "cert_abc123",
+ alertDaysBeforeExpiry: 30,
+ },
+ assertions: [
+ SslAssertionBuilder.certificate("daysUntilExpiry").greaterThan(0),
+ SslAssertionBuilder.connection("hostnameVerified").equals(true),
+ ],
+ },
+ })
+ ```
+
+
+
+ ```ts
+ import { Frequency, SslMonitor } from "checkly/constructs"
+
+ // Monitor a certificate served for a specific SNI name
+ // on a shared IP (e.g. a CDN or multi-tenant server).
+ new SslMonitor("tenant-ssl", {
+ name: "Tenant Certificate via SNI",
+ frequency: Frequency.EVERY_1H,
+ request: {
+ hostname: "shared-cdn.example.net",
+ sslConfig: {
+ serverName: "tenant-a.example.com",
+ alertDaysBeforeExpiry: 20,
+ },
+ },
+ })
+ ```
+
+
diff --git a/detect/uptime-monitoring/ssl-monitors/configuration.mdx b/detect/uptime-monitoring/ssl-monitors/configuration.mdx
new file mode 100644
index 00000000..19b95cbd
--- /dev/null
+++ b/detect/uptime-monitoring/ssl-monitors/configuration.mdx
@@ -0,0 +1,151 @@
+---
+title: 'SSL Monitor Configuration'
+description: 'Configure your SSL monitor to track certificate health, expiry, and TLS security posture.'
+sidebarTitle: 'Configuration'
+canonical: 'https://www.checklyhq.com/docs/detect/uptime-monitoring/ssl-monitors/configuration/'
+---
+
+
+To configure an SSL monitor using code, learn more about the [SSL Monitor Construct](/constructs/ssl-monitor).
+
+
+### Basic Setup
+
+Configure your SSL monitor by specifying the target host:
+
+* **Hostname:** The domain or IP address to connect to (e.g. `api.example.com`). Do not include a scheme or port.
+* **Port:** The TCP port to connect to. Defaults to `443`.
+* **IP family:** Choose between IPv4 (default) or IPv6.
+
+### SSL Configuration
+
+Fine-grained control over the TLS handshake behavior:
+
+* **SNI server name (`serverName`):** Override the Server Name Indication sent during the TLS handshake. Useful when a single IP serves multiple certificates and you want to request a specific one. Defaults to the configured hostname when unset.
+
+* **Handshake timeout:** Maximum time in milliseconds to wait for the TLS handshake to complete. Range: 1,000–30,000 ms. Default: 10,000 ms.
+
+* **Alert days before expiry (`alertDaysBeforeExpiry`):** Raise a degraded alert when the certificate is within this many days of expiry. Range: 1–365 days. Default: 20 days. The monitor always fails immediately if the certificate is already expired.
+
+* **Skip chain validation:** When enabled, Checkly does not verify the certificate chain against trusted roots. The certificate is still inspected for expiry and the security baseline. Use this when monitoring internal or self-signed certificates.
+
+### Client Certificate / mTLS
+
+SSL monitors can present a client certificate during the TLS handshake to verify mTLS-protected endpoints:
+
+* **`clientCertificateMode: 'auto'`** — Checkly automatically selects a stored client certificate for the target host.
+* **`clientCertificateMode: 'explicit'`** — Use the specific certificate identified by `sslClientCertificateId`.
+
+Omit `clientCertificateMode` to connect without a client certificate (the default).
+
+Client certificates are stored at the account level under **Settings → Client Certificates**.
+
+### Security Baseline
+
+The security baseline is a configurable rule set that evaluates the negotiated TLS session against best-practice requirements. It runs on each successful handshake and certificate verification, and produces a **verdict** (`pass` / `warn` / `fail`) and a **grade** (`A-`, `B`, or `F`). It is skipped when an earlier step fails (e.g. hostname mismatch or chain untrusted).
+
+You can override the default severity for each rule:
+
+| Rule | Default severity | Configurable |
+|------|-----------------|:---:|
+| `minTLSVersion` | `fail` | ✅ |
+| `minKeySizeBits` | `fail` | ✅ |
+| `weakSignatureAlgorithm` | `fail` | ✅ |
+| `weakCipherSuite` | `fail` | ✅ |
+| `knownBadCA` | `fail` | ✅ |
+| `recommendedTLSVersion` | `ignore` | ✅ |
+| `recommendedKeySizeBits` | `ignore` | ✅ |
+| `ocspMustStapleRespected` | `ignore` | ✅ |
+| `sctPresent` | `ignore` | ✅ |
+
+Each rule accepts a `severity` of `fail`, `degrade`, or `ignore`. Set `enabled: false` to disable the baseline entirely for a monitor.
+
+
+Omitting the `securityBaseline` field inherits the account-level default baseline. Override it on a per-monitor basis only when you need non-standard thresholds for a specific endpoint.
+
+
+### Assertions
+
+Use assertions to validate specific certificate and TLS handshake properties beyond what the security baseline covers.
+
+Each assertion has a `source`, an optional `property` (a field selector), a `comparison`, and a `target`. Available sources:
+
+| Source | `property` | Description |
+|--------|-----------|-------------|
+| `CERTIFICATE` | a certificate field (see below) | Assert on a leaf-certificate field |
+| `CONNECTION` | a connection field (see below) | Assert on a TLS handshake / connection field |
+| `RESPONSE_TIME` | — | TLS handshake duration in milliseconds |
+| `JSON_RESPONSE` | JSONPath | Assert on any field of the response document via a JSONPath expression (e.g. `$.chain[0].keySizeBits`) |
+| `TEXT_RESPONSE` | regex | Extract a value from the serialized response with a regex, then compare |
+
+`CERTIFICATE` `property` values:
+
+| `property` | Type | Description |
+|-----------|------|-------------|
+| `daysUntilExpiry` | number | Days until the certificate expires |
+| `subjectCN` | string | Certificate subject common name |
+| `issuerCN` | string | Common name of the certificate issuer |
+| `serialNumber` | exact | Certificate serial number |
+| `fingerprintSha256` | exact | SHA-256 fingerprint of the leaf certificate |
+| `issuerFingerprintSha256` | exact | SHA-256 fingerprint of the issuer certificate |
+| `keySizeBits` | number | Leaf certificate public key size in bits |
+| `keyAlgorithm` | exact | Public key algorithm (e.g. `RSA`, `ECDSA`) |
+| `signatureAlgorithm` | exact | Leaf certificate signature algorithm (e.g. `SHA256-RSA`, `ECDSA-SHA256`) |
+| `sans` | list | Subject Alternative Names |
+| `selfSigned` | boolean | The leaf certificate is self-signed |
+| `isCA` | boolean | The leaf certificate is a CA certificate |
+
+`CONNECTION` `property` values:
+
+| `property` | Type | Description |
+|-----------|------|-------------|
+| `tlsVersion` | version | Negotiated TLS protocol version (e.g. `TLS1.2`, `TLS1.3`) |
+| `cipherSuite` | string | IANA cipher suite name negotiated during the handshake |
+| `hostnameVerified` | boolean | The certificate's SANs cover the monitored hostname |
+| `chainTrusted` | boolean | The full certificate chain is trusted by the system root store |
+| `ocspStapled` | boolean | A stapled OCSP response was included in the handshake |
+| `ocspStatus` | exact | Decoded stapled-OCSP status: `good`, `revoked`, or `unknown` |
+| `resolvedIp` | string | The IP address the hostname resolved to |
+
+The comparisons available depend on the property's type:
+
+| Type | Comparisons |
+|------|-------------|
+| `number` | `EQUALS`, `NOT_EQUALS`, `GREATER_THAN`, `LESS_THAN` |
+| `version` | `EQUALS`, `NOT_EQUALS`, `GREATER_THAN`, `LESS_THAN` (ordered `TLS1.0` < `TLS1.1` < `TLS1.2` < `TLS1.3`) |
+| `string` | `EQUALS`, `NOT_EQUALS`, `CONTAINS`, `NOT_CONTAINS` |
+| `exact` | `EQUALS`, `NOT_EQUALS` |
+| `list` | `CONTAINS`, `NOT_CONTAINS` (SAN membership) |
+| `boolean` | `EQUALS` |
+
+The other sources take their own comparison sets: `RESPONSE_TIME` uses the `number` set; `JSON_RESPONSE` accepts `EQUALS`, `NOT_EQUALS`, `GREATER_THAN`, `LESS_THAN`, `CONTAINS`, `NOT_CONTAINS`, `IS_EMPTY`, `NOT_EMPTY`, `IS_NULL`, `NOT_NULL`; `TEXT_RESPONSE` accepts `EQUALS`, `NOT_EQUALS`, `CONTAINS`, `NOT_CONTAINS`, `IS_EMPTY`, `NOT_EMPTY`, `GREATER_THAN`, `LESS_THAN`.
+
+
+Certificate validity, minimum TLS version and minimum key size are also enforced by the security baseline above — reach for assertions when you need an exact value check (a specific issuer, fingerprint pinning, a SAN, etc.).
+
+
+For more details, see our documentation on [Assertions](/detect/assertions).
+
+### Response Time Limits
+
+Set performance thresholds based on the TLS handshake duration:
+
+* **Degraded After:** Handshake time (in milliseconds) at or above which the monitor is marked as degraded but not failed. Range: 0–30,000 ms. Default: 3,000 ms.
+* **Failed After:** Handshake time at or above which the monitor fails. Must be greater than or equal to the degraded threshold. Range: 0–30,000 ms. Default: 10,000 ms.
+
+### Frequency
+
+Set how often the monitor runs. SSL monitors run at most **once per minute** (every 1 minute to 24 hours) — sub-minute frequencies aren't available, since certificate state changes slowly.
+
+### Scheduling & Locations
+
+* **Strategy:** Choose between round-robin or parallel execution. Learn more about [scheduling strategies](/concepts/scheduling).
+* **Locations:** Select [public](/concepts/locations/#public-locations) or [private](/platform/private-locations/overview) locations to run the monitor from.
+
+### Additional Settings
+
+* **Name:** Give your monitor a clear name to identify it in dashboards and alerts.
+* **Description:** Add context about what this monitor does and why it matters. Supports markdown, max 500 characters. When a failure occurs, [Rocky AI](/ai/rocky-ai) uses the description to provide more accurate [root cause and user impact analysis](/resolve/ai-root-cause-analysis/overview).
+* **Tags:** Use tags to organize monitors across [dashboards](/communicate/dashboards/overview/) and [maintenance windows](/communicate/maintenance-windows/overview).
+* **Retries:** Define how failed runs should be retried. See [retry strategies](/communicate/alerts/retries).
+* **Alerting:** Configure your [alert settings](/communicate/alerts/configuration), [alert channels](/communicate/alerts/channels), or set up [webhooks](/integrations/alerts/webhooks) for custom integrations.
diff --git a/detect/uptime-monitoring/ssl-monitors/overview.mdx b/detect/uptime-monitoring/ssl-monitors/overview.mdx
new file mode 100644
index 00000000..00ad370b
--- /dev/null
+++ b/detect/uptime-monitoring/ssl-monitors/overview.mdx
@@ -0,0 +1,95 @@
+---
+title: 'SSL Monitor Overview'
+description: 'Monitor TLS certificate health, expiry, and security posture of your HTTPS endpoints.'
+sidebarTitle: Overview
+canonical: 'https://www.checklyhq.com/docs/detect/uptime-monitoring/ssl-monitors/overview/'
+---
+
+
+**Monitoring as Code**: Learn more about the [SSL Monitor Construct](/constructs/ssl-monitor).
+
+
+## What are SSL Monitors?
+
+SSL monitors verify the health of a TLS certificate by connecting to a hostname, completing a full TLS handshake, and inspecting the resulting certificate and negotiated protocol parameters. Typical use cases include:
+
+* Detecting certificate expiry before it causes user-facing errors
+* Verifying that a recently-renewed certificate is deployed to all your servers
+* Enforcing minimum TLS version and cipher strength across your infrastructure
+* Catching chain trust failures caused by missing intermediate certificates
+* Alerting on weak or compromised certificate authority usage
+
+## How do SSL Monitors work?
+
+Each SSL monitor run performs the following steps:
+
+1. **DNS resolution** — If a hostname is provided, Checkly resolves it to an IP address
+2. **TCP connect** — A TCP connection is opened to the target host and port (default: 443)
+3. **TLS handshake** — A full TLS handshake is performed; an optional SNI server name override can be sent during this step
+4. **Certificate inspection** — The leaf certificate and chain are examined: expiry date, hostname verification, chain trust, signature algorithm, key size, SANs, and fingerprints are all captured
+5. **Security baseline evaluation** — On a successful handshake and certificate verification, a configurable rule set evaluates the negotiated protocol (TLS version, cipher suite, CA trust) and produces a per-rule verdict plus an overall **grade** and **verdict** (`pass` / `warn` / `fail`). The baseline is skipped when an earlier step fails (e.g. hostname mismatch or chain untrusted).
+6. **Assertions** — Optional assertions are evaluated against the certificate and handshake data
+
+## SSL Monitor Results
+
+Select a specific check run to inspect its results:
+
+* **Summary:** Shows the target hostname and port, the monitor state (passed, degraded, or failed), and the TLS handshake time
+
+* **Error details:** If the run failed, the error category and message explain what went wrong. Categories map to the stage where the failure occurred: `dns` (name resolution), `connect` (TCP connection), `timeout` or `handshake` (TLS handshake), `hostname` (certificate does not cover the monitored hostname), or `chain` (certificate chain not trusted). An expired certificate surfaces as a failure via a negative `daysUntilExpiry` value rather than a separate category.
+
+* **Handshake data:** The negotiated TLS version, cipher suite, and handshake duration in milliseconds
+
+* **Certificate details:** Subject, issuer, serial number, validity window (not-before / not-after), days until expiry, signature algorithm, key algorithm and size, subject alternative names (SANs), and SHA-256 fingerprint
+
+* **Security baseline:** The per-rule verdict table and the aggregate grade (`A-`, `B`, or `F`) produced by the security baseline evaluation
+
+* **Certificate chain:** The intermediate and root certificates presented during the handshake, with their subjects, issuers, validity dates, and fingerprints
+
+Learn more in our documentation on [Results](/concepts/results).
+
+## The Security Baseline
+
+The security baseline is a built-in rule set that evaluates each successful handshake and certificate verification against current best practices without requiring explicit assertions. Rules cover:
+
+| Rule | Default severity | What it checks |
+|------|-----------------|----------------|
+| `minTLSVersion` | `fail` | Negotiated TLS version is at or above the required minimum |
+| `minKeySizeBits` | `fail` | RSA key size meets the required minimum |
+| `weakSignatureAlgorithm` | `fail` | No non-root certificate in the chain uses MD2-RSA, MD5-RSA, SHA1-RSA, DSA-SHA1, or ECDSA-SHA1 |
+| `weakCipherSuite` | `fail` | The negotiated cipher suite is not in the known-weak list |
+| `knownBadCA` | `fail` | No certificate in the chain was issued by a distrusted CA |
+| `recommendedTLSVersion` | `ignore` (advisory) | Negotiated version meets the recommended minimum |
+| `recommendedKeySizeBits` | `ignore` (advisory) | RSA key meets the advisory size recommendation |
+| `ocspMustStapleRespected` | `ignore` (advisory) | If the leaf declares OCSP must-staple, a stapled response was provided |
+| `sctPresent` | `ignore` (advisory) | A Signed Certificate Timestamp was observed in the handshake |
+
+The aggregate **grade** is computed from the worst rule outcome:
+
+| Grade | Meaning |
+|-------|---------|
+| `A-` | All rules pass (or baseline disabled) |
+| `B` | At least one advisory or degrade-severity rule violated |
+| `F` | At least one fail-severity rule violated |
+
+You can override the default severity for each rule or disable the baseline entirely in the [SSL Config](/detect/uptime-monitoring/ssl-monitors/configuration#security-baseline).
+
+## Troubleshooting Common Issues
+
+
+This can happen when a server hosts multiple certificates and the default certificate served to Checkly does not cover the monitored hostname. The runner reports this as failure category `hostname`. Use the **SNI server name** (`serverName`) option in the SSL config to explicitly request the certificate for your hostname.
+
+If the certificate is legitimately shared across hostnames (e.g. a wildcard `*.example.com` that covers `api.example.com`) and hostname verification still fails, confirm the SAN list on the certificate contains the monitored hostname.
+
+
+
+A chain trust failure (failure category `chain`) means one or more intermediate certificates are missing or the root is not trusted. Common causes:
+
+* The server is not sending the full intermediate chain — verify with `openssl s_client -connect hostname:443 -showcerts`
+* The certificate was issued by a CA whose root was recently distrusted (e.g. Entrust 2024)
+* Self-signed or internal CA certificates — enable **Skip chain validation** in the SSL config if you only want to monitor expiry and other properties without trust verification
+
+
+
+The `alertDaysBeforeExpiry` setting (default: 20 days) controls when a degraded alert is raised due to approaching expiry. If you use aggressive certificate rotation you may want to lower this value; if you need more lead time to trigger your renewal process, raise it. The monitor always fails immediately when the certificate is expired (`daysUntilExpiry < 0`), regardless of this setting.
+
diff --git a/docs.json b/docs.json
index 42d520e4..91c52253 100644
--- a/docs.json
+++ b/docs.json
@@ -170,6 +170,13 @@
"detect/uptime-monitoring/icmp-monitors/configuration"
]
},
+ {
+ "group": "SSL Monitors",
+ "pages": [
+ "detect/uptime-monitoring/ssl-monitors/overview",
+ "detect/uptime-monitoring/ssl-monitors/configuration"
+ ]
+ },
{
"group": "Heartbeat Monitors",
"pages": [
@@ -484,6 +491,7 @@
"constructs/dns-monitor",
"constructs/tcp-monitor",
"constructs/icmp-monitor",
+ "constructs/ssl-monitor",
"constructs/heartbeat-monitor",
"constructs/agentic-check",
"constructs/api-check",
diff --git a/sitemap.xml b/sitemap.xml
index 73b08849..b7b969a3 100644
--- a/sitemap.xml
+++ b/sitemap.xml
@@ -891,6 +891,9 @@
https://www.checklyhq.com/docs/constructs/sms-alert-channel/
+
+ https://www.checklyhq.com/docs/constructs/ssl-monitor/
+
https://www.checklyhq.com/docs/constructs/status-page/
@@ -1068,6 +1071,12 @@
https://www.checklyhq.com/docs/detect/uptime-monitoring/overview/
+
+ https://www.checklyhq.com/docs/detect/uptime-monitoring/ssl-monitors/configuration/
+
+
+ https://www.checklyhq.com/docs/detect/uptime-monitoring/ssl-monitors/overview/
+
https://www.checklyhq.com/docs/detect/uptime-monitoring/tcp-monitors/configuration/