Skip to content

Hostname Validator

Confirms the certificate was actually issued for the host you connected to. hostname matches the host against the certificate's Subject Alternative Names (SANs) including wildcard certificates (*.example.com). A mismatch is what your browser shows as "this certificate is not valid for this site."

Enabled by default

hostname is one of the three default validators. The host you pass to CertMonitor(...) is the name it checks against. To check a different name, pass validator_args={"hostname": {"expected_identity": "..."}}.

Try it

from certmonitor import CertMonitor

with CertMonitor("example.com") as monitor:
    monitor.get_cert_info()
    print(monitor.validate()["hostname"])

A matching hostname reports which name matched and the SANs it considered:

These examples show selected fields from illustrative scans. validate() also adds status and code, described in the result contract.

{
  "is_valid": true,
  "alt_names": [
    "example.com",
    "*.example.com"
  ],
  "identity_source": "subjectAltName",
  "common_name": "example.com",
  "common_name_matches": true,
  "matched_name": "example.com"
}

A mismatch fails with a reason:

{
  "is_valid": false,
  "alt_names": [
    "*.badssl.com",
    "badssl.com"
  ],
  "identity_source": "subjectAltName",
  "common_name": "*.badssl.com",
  "common_name_matches": false,
  "reason": "Hostname wrong.host.badssl.com doesn't match any of the certificate's subject alternative names"
}

How matching works

  1. Common Name: common_name and common_name_matches report the CN comparison for reference. CN does not determine is_valid or replace missing SANs.
  2. DNS/IP SANs: DNS names are checked case-insensitively after IDNA normalization. IP addresses match IP Address SANs by address equality.
  3. Wildcards: a *.example.com SAN matches exactly one label (api.example.com), but not the bare apex (example.com) or nested subdomains (a.b.example.com).

Checking with an IP address?

Connecting by IP will usually fail hostname unless the certificate carries that IP as a SAN (most don't). See Using IP Addresses for how CertMonitor handles IP targets.

SAN-based identity validation follows RFC 9525.

Reference

certmonitor.validators.hostname.HostnameValidator

Bases: BaseCertValidator

A validator for checking the hostname in an SSL certificate.

Attributes:

Name Type Description
name str

The name of the validator.

name class-attribute instance-attribute

name: str = 'hostname'

validate

validate(cert: dict[str, Any], host: str, port: int, *, expected_identity: str | None = None) -> HostnameResult

Validates the hostname against the Subject Alternative Names (SANs) in the provided SSL certificate.

Common Name is also reported in common_name and common_name_matches for inspection. It never overrides the SAN-based is_valid result. DNS matching is case-insensitive, and IP identities match IP Address SANs. matched_name is the SAN entry that matched: the exact name, the wildcard pattern, or the IP address.

Parameters:

Name Type Description Default
cert dict

The SSL certificate.

required
host str

The hostname to validate.

required
port int

The port number.

required
expected_identity str

A DNS name or IP address to check instead of host. Use it when the monitor connects by one name or address but the certificate must be valid for another. Defaults to None.

None

Returns:

Name Type Description
dict HostnameResult

A dictionary containing the validation results, including whether the hostname is valid, the reason for validation failure, and the alternative names (SANs) in the certificate.

Examples:

Example output (success): This example shows a certificate where the hostname matches one of the DNS SANs, so validation passes and the matched name is shown.

{
    "is_valid": true,
    "matched_name": "example.com",
    "alt_names": [
        "example.com",
        "www.example.com"
    ]
}

Example output (failure): This example shows a certificate where the hostname does not match any DNS SAN, so validation fails and a reason is provided.

{
    "is_valid": false,
    "reason": "Hostname test.example.com doesn't match any of the certificate's subject alternative names",
    "alt_names": [
        "example.com",
        "www.example.com"
    ]
}
Source code in certmonitor/validators/hostname.py
def validate(
    self,
    cert: dict[str, Any],
    host: str,
    port: int,
    *,
    expected_identity: str | None = None,
) -> HostnameResult:
    """
    Validates the hostname against the Subject Alternative Names (SANs) in the provided SSL certificate.

    Common Name is also reported in `common_name` and `common_name_matches`
    for inspection. It never overrides the SAN-based `is_valid` result.
    DNS matching is case-insensitive, and IP identities match IP Address SANs.
    `matched_name` is the SAN entry that matched: the exact name, the
    wildcard pattern, or the IP address.

    Args:
        cert (dict): The SSL certificate.
        host (str): The hostname to validate.
        port (int): The port number.
        expected_identity (str, optional): A DNS name or IP address to check instead
            of `host`. Use it when the monitor connects by one name or address but
            the certificate must be valid for another. Defaults to `None`.

    Returns:
        dict: A dictionary containing the validation results, including whether the hostname is valid,
              the reason for validation failure, and the alternative names (SANs) in the certificate.

    Examples:
        Example output (success):
            This example shows a certificate where the hostname matches one of the DNS SANs, so validation passes and the matched name is shown.

            {
                "is_valid": true,
                "matched_name": "example.com",
                "alt_names": [
                    "example.com",
                    "www.example.com"
                ]
            }

        Example output (failure):
            This example shows a certificate where the hostname does not match any DNS SAN, so validation fails and a reason is provided.

            {
                "is_valid": false,
                "reason": "Hostname test.example.com doesn't match any of the certificate's subject alternative names",
                "alt_names": [
                    "example.com",
                    "www.example.com"
                ]
            }
    """
    host = expected_identity or host
    if not host:
        return {
            "is_valid": False,
            "status": "unsupported",
            "reason": (
                "No identity to check: load the certificate with a host, or pass "
                "expected_identity."
            ),
            "alt_names": [],
            "identity_source": "subjectAltName",
            "common_name": None,
            "common_name_matches": False,
        }
    info = cert.get("cert_info", {})
    sans = normalize_sans(info.get("subjectAltName"))
    match = match_identity(host, sans)
    subject = info.get("subject", {})
    if not isinstance(subject, dict):
        subject = dict(pair for rdn in subject for pair in rdn)
    common_name = subject.get("commonName")
    if not isinstance(common_name, str):
        common_name = None
    result: HostnameResult = {
        "is_valid": match.is_valid,
        "alt_names": sans["DNS"] + sans["IP Address"],
        "identity_source": "subjectAltName",
        "common_name": common_name,
        "common_name_matches": common_name is not None
        and dns_match(host, common_name),
    }
    if match.is_valid:
        result["matched_name"] = match.matched_name or host
        return result

    if "subjectAltName" not in info:
        reason = "Certificate does not contain a Subject Alternative Name extension"
    elif match.kind == "dns":
        reason = (
            f"Hostname {host} doesn't match any of the certificate's subject alternative names"
            if sans["DNS"]
            else "Certificate does not contain any DNS SANs"
        )
    else:
        reason = match.reason
    result["reason"] = reason
    return result