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/