What the web module tests on your HTTPS setup, why each check matters and how it is scored – including the rating caps a single serious finding can trigger.
Check your domain →
Last updated: September 2026 · 25 checks documented
Website configured (A/AAAA records) #
The Website and HTTP security headers modules check a running website. If neither the domain itself nor www has A or AAAA records, there is no website to connect to – a perfectly normal setup for domains that are used for e-mail only.
RatingOnly appears when the domain itself exists (NOERROR) but has no A/AAAA records, www has none either (conclusive NOERROR/NXDOMAIN answers) and no CNAME is configured on either name: skip; the module is reported as not applicable instead of being graded F. Everything else – a DNS failure (e.g. a broken DNSSEC chain), a nonexistent domain or a dangling CNAME – keeps the regular checks, so a broken site still shows up as unreachable. Before the claim is made, the name is also asked for an HTTPS record: RFC 9460 lets a name reach its service through an AliasMode record with no address records of its own. If one is found, the module reports skip and not measured and is NOT declared not applicable – the site exists, it simply was not tested here; if that lookup does not answer conclusively, nothing is claimed in either direction.
How to fix itNothing to fix if the domain is intentionally mail-only. If a website is supposed to exist, add A/AAAA records for the domain or www and re-run the scan.
HTTPS reachable (port 443) #
Every web check starts with a TLS connection to port 443. If no connection can be established (no service listening, firewall, DNS pointing nowhere), none of the certificate, protocol or cipher checks can run – and visitors cannot reach the site securely either.
RatingOnly appears when the connection fails: fail, weight 3. The remaining web checks are then skipped. If neither the domain nor www has any A/AAAA records at all, the module is not graded – see “Website configured (A/AAAA records)”.
How to fix itMake sure a web server listens on port 443 for this name, the firewall permits it, and the DNS records point to the right address. Then re-run the scan.
Target address blocked (scanner protection) #
The domain resolves to a private, loopback or otherwise internal IP range (e.g. RFC 1918). The scanner deliberately refuses to contact such addresses: following them would let arbitrary domains turn the scanner into a probe of its own internal network (server-side request forgery). This is a property of the scan target’s DNS setup, not of your server’s security.
RatingOnly appears when the scan is aborted for this reason: fail, weight 3 – as the only rating-relevant finding it leaves the module at grade F. That F describes our refusal to contact an internal address, not the security of your server. Point the name at a public address and re-scan to get a real result.
How to fix itFor a public website, the domain must resolve to a publicly routable IP address. Split-horizon or internal-only names cannot be scanned from the outside.
Certificate chain valid & trusted #
Browsers only trust certificates whose chain leads to a publicly recognized certificate authority (CA). A self-signed certificate, or one issued by an unknown CA, triggers a security warning. The chain is validated against a combined root store (operating system + Node bundle + the weekly CCADB/Mozilla TLS snapshot). That store is a UNION: it only ever adds anchors and never removes one, so a root Mozilla has distrusted stays trusted here as long as the operating system or the Node bundle still ships it (32 such anchors when this was last measured, in August 2026). The scan therefore checks separately whether the anchor the chain ends in is still part of the current Mozilla/CCADB TLS set and warns when it is not – three-valued, so a missing or implausibly small snapshot never becomes the claim "anchor outdated". Mozilla has a SECOND, dated form of distrust, and set membership cannot express it: a root can stay in the set while NSS refuses every end-entity certificate whose notBefore lies after a cut-off date (CKA_NSS_SERVER_DISTRUST_AFTER, published by CCADB as "Distrust for TLS After Date"). Those dates are therefore a separate weekly data set, and the check reads it the same way: a missing or stale copy says nothing, while a copy that names a cut-off still proves one – an entry does not become wrong by ageing, an absence does. If the served chain is merely incomplete, it is re-validated once using the publicly disclosed CCADB intermediates (refreshed weekly) as chain-building material – the same set Firefox preloads and Chrome fetches via AIA – to distinguish a repairable chain from a genuinely untrusted one.
Ratingfail → special grade T (Trust); this completely overrides the A+…F grade. Weight 3. Exception 1: an incomplete chain that validates with a publicly disclosed CCADB intermediate is warn instead (no T – the grade is capped at B via "Complete certificate chain"). Exception 2: a chain that IS trusted but ends in a root that is no longer part of the current Mozilla/CCADB TLS set is warn without a cap – the certificate itself is sound, but it is no longer in Mozilla's current TLS root set, so Firefox will stop accepting it (NSS releases trail CCADB, so the exact moment is not ours to state). Exception 3: a chain whose root IS still in the set but carries a Mozilla cut-off date that this certificate's notBefore lies past is warn without a cap as well – deliberately the same grade as Exception 2, because for this certificate the two mean the same thing: Firefox refuses it, and grading the narrower case harder than the broader one would be the inconsistency. Where the cut-off exists but the certificate predates it the grade stays pass and only a sentence is added – nothing deployed is broken, but the next renewal under that root is refused. All three verdicts are three-valued and are never given without a usable data set. The hostname is checked separately (see "matches the hostname").
How to fix itUse a certificate from a public CA (e.g. free via Let’s Encrypt) and serve the complete chain. ACME automates issuance and renewal.
Certificate checks #
A placeholder that appears only when the TLS handshake completed without the server presenting any certificate. Everything the certificate findings describe – hostname match, validity, signature algorithm, key strength, chain completeness, revocation information – needs a certificate to exist. Rather than emit six verdicts about something nobody saw, the module reports one entry that says so. There are two ways to end up here, and they are kept apart: the peer really sent none, or this scanner resumed a TLS session and therefore never saw the certificate repeated. A server legitimately omits the Certificate message whenever the key exchange is not certificate-based — RFC 8446 §4.4.2 requires one for every method defined there "except PSK" — and in TLS 1.3 a resumption IS such a PSK handshake (§2.2), which is why the second case looks the same on the wire. What browsers do not offer is an ANONYMOUS key exchange: BoringSSL defines no anonymous cipher suite at all, and the configurable suite table in NSS carries neither an anonymous nor a TLS 1.2 PSK entry.
Ratingskip, weight 7 – the combined weight of the six certificate findings it stands in for. A skip leaves numerator and denominator alike, so it never counts against the domain. It is marked as not measured only in the resumption case, because that is our blind spot; where the server genuinely presented no certificate the checks are not applicable rather than ungauged, and cert-trust carries that verdict with fail + Cap T.
How to fix itConfigure the server with a certificate from a publicly trusted CA. If this appeared without a configuration change, re-run the scan – a resumed session can hide the certificate from a single measurement.
Certificate matches the hostname #
The requested hostname must appear in the certificate’s Subject Alternative Names (SAN). Otherwise there is no proof that the certificate belongs to exactly this website.
Ratingfail → special grade M (Mismatch); overrides the letter grade. Weight 2. A certificate with NO Subject Alternative Name that matches only via the deprecated commonName is warn (no cap): Chrome, Firefox and Safari all ignore the CN, so such a certificate fails hostname matching outright — Chromium returns false as soon as both SAN lists are empty (X509Certificate::VerifyHostname), mozilla::pkix uses a policy that never falls back to searching within the subject name, and Apple has not trusted DNS names in the CommonName since iOS 13 / macOS 10.15. Issuing one is separately forbidden by CA/Browser Forum Baseline Requirements §7.1.2.7.12, which is a rule for CAs and says nothing about client behaviour; a SAN that carries only non-dNSName entries (an iPAddress, for example) is reported as exactly that, not as a missing SAN. Wildcards follow RFC 9525 §6.3: the * must be the complete left-most label and there may be only one of them, otherwise the entry is invalid and is ignored – the finding names such an entry rather than only stating that the host is not in the SAN. A wildcard is never matched across an ICANN public suffix (CA/Browser Forum BR §3.2.2.6).
How to fix itAdd the hostname (and www if applicable) to the SAN list, or have a matching certificate issued.
Certificate time remaining #
An expired certificate causes a hard error in every browser – the site is effectively unreachable.
Ratingpass while more time remains than the renewal threshold; warn at or below it; expired → fail + Cap F; not yet valid (validFrom in the future) → fail without a cap; no usable expiry date → skip. Weight 1. The threshold follows the certificate's own lifetime – a quarter of the total validity, never more than 15 days (15 days for a 90-day certificate, 12 for a 47-day one, 2 for a 6-day short-lived one); a flat 15 days would mean "warn always" for short-lived certificates.
How to fix itAutomate renewal (ACME/Let’s Encrypt, certbot, Caddy). Set up monitoring of expiry dates.
Certificate signature algorithm #
SHA-1 and MD5 are collision-prone – certificates signed with them are considered forgeable and are rejected by browsers.
Ratingpass with SHA-256+; SHA-1/MD5 → fail + special grade T. Weight 1. The check covers the leaf AND the intermediate CA certificates of the served chain – a weak signature anywhere in it gives the same fail + T, and the finding then names the intermediate. A self-signed root is excluded on purpose: a trust anchor is validated by its identity, not by its own signature. The same holds for a self-signed LEAF – RFC 5280 §6.1 keeps a self-signed anchor out of the certification path, so no verifier ever checks that signature; the finding then reports info without a cap, and cert-trust has already capped the module at T for the actual reason. The CA/Browser Forum does not merely deprecate this: BR §7.1.3.2.1 requires the issuing CA to revoke any unexpired SHA-1-signed subordinate CA certificate before 2026-09-15 (ballot SC-097).
How to fix itHave the certificate reissued with SHA-256 (or stronger) – standard at modern CAs.
Key strength #
Keys that are too short can be broken. Recommended: RSA ≥ 2048 bits or ECDSA P-256 (≈ RSA 3072). Size is not the only failure mode: some keys are broken by the way they were GENERATED – the ROCA flaw in Infineon’s RSA key-generation library (CVE-2017-15361), shipped in that vendor’s TPM firmware, smartcards and security tokens, and RSA moduli whose two primes sit close together (Fermat-factorable). A close-prime modulus is factorable at any key size; a ROCA key is practically factorable in the ranges the researchers name (512–704, 992–1216 and 1984–2144 bits – a 4096-bit key is not). The Baseline Requirements (§6.1.1.3, ballot SC-073) oblige CAs to reject either kind unconditionally.
Ratingpass with RSA ≥ 2048 / EC ≥ 256 / Ed25519 / Ed448; RSA < 2048 → warn + Cap B; RSA < 1024 → fail + Cap F; any other key below the minimum (e.g. EC < 256) → fail without a cap; a key type whose size cannot be determined → info with the reason, never a pass. Weight 1. Beyond the size, the RSA modulus is screened for known-broken key CONTENT (BR §6.1.1.3 / ballot SC-073): the ROCA fingerprint (CVE-2017-15361) and Fermat-factorable close primes. A hit is fail + Cap F at any key length – the Baseline Requirements forbid these keys without a length condition, so certificate AND key have to be replaced. That screen has a limit of its own: it stops at 8192 bits, where the Fermat test alone would cost seconds of blocked processing. Above it the finding says the modulus was not screened instead of reporting it clean, and the grade is unaffected – our own limit never costs a domain a point.
How to fix itGenerate a new key with RSA-2048+ or ECDSA P-256 and reissue the certificate.
Complete certificate chain #
If the intermediate certificates are not included, many clients cannot build the chain – even though the certificate is valid. Chrome fetches the missing certificate over AIA; Firefox does no AIA fetching and supplies it from a preloaded set instead; curl/OpenSSL do none at all, most mail servers do neither, and Java has AIA caIssuers fetching disabled by DEFAULT in current versions (com.sun.security.enableAIAcaIssuers), not only in old ones. Safari is not named here: Apple does not publish the code that would decide it. Browsers usually recover (Firefox preloads all publicly disclosed intermediates, Chrome fetches the missing certificate via AIA), but strict clients such as curl/OpenSSL, most mail servers and older Java fail.
Ratingfail → Cap B if only the leaf certificate is served or the issuer cannot be resolved. Weight 1. The scanner reports whether the missing link is a publicly disclosed CA certificate (checked against the ~1,700 CCADB intermediates, refreshed weekly); if not, the chain counts as untrusted (special grade T via "valid & trusted").
How to fix itConfigure the complete chain (leaf + all intermediates). Tools like "what’s my chain cert" help with assembling it.
DANE / TLSA (HTTPS) #
DANE binds the website certificate to a DNSSEC-secured TLSA record under _443._tcp, hardening TLS against MITM/mis-issuance. Note: mainstream browsers do not enforce DANE for HTTPS, so it mainly protects DANE-aware clients and documents certificate intent. For auto-renewed certificates, usage 2 (DANE-TA, pinning the CA) survives key rotation, whereas usage 3 (DANE-EE, leaf SPKI) must be republished on every renewal unless the key is reused.
Ratingpass if a DNSSEC-secured TLSA matches the served chain; fail on mismatch or missing DNSSEC (Cap C). PKIX-TA (usage 0) exceptions: pinning a sent intermediate is a mild warn (no cap; DANE-TA is cleaner), and a usage-0 record that pins the not-sent root is info (no penalty; validated via the client trust store) – as is a usage-0 record matching a cross-signed root the server DOES send, which is a valid root pin. Records whose usage, selector or matching type is unknown are unusable (RFC 6698 §4.1): DANE clients ignore them, so on their own they are warn without a cap, never a hard mismatch. A DANE-TA (usage 2) record whose only match is a sent root via a hash form (e.g. 2 1 1) still passes but carries a note recommending a 2 0 0 record (full root cert in DNS) – a sent cross-signed root leaves the chain once its cross-signature expires; a record that already pins the root via 2 0 0 is recognised as robust and passes cleanly. Absence is info only. If our own TLSA lookup does not come back conclusive – it failed outright, or every resolver answered SERVFAIL – the finding says "not determined" (also info) and deliberately gives no advice – an unanswered query is not the statement "this domain has no TLSA records". Weight 2 when records are present, 0 when they are absent or not determined.
How to fix itOptional but recommended: enable DNSSEC and publish a TLSA record under _443._tcp. Usage 2 (DANE-TA, pinning the CA) survives ACME renewals; on the selector RFC 7671 §5.2.1 says DANE-TA publishers "SHOULD publish records with a selector of Cert(0)", so 2 0 1 is the form to reach for. 2 1 1 stays permitted and additionally survives a re-issue of the same CA key, but with SPKI(1) "elements of the TA certificate other than the public key become mutable" – path-length and name constraints drop out of the pin. During a rollover, publish the old and new records in parallel for at least two TTLs of the TLSA RRset (RFC 7671 §8.1). The TLSA generator can fetch the live HTTPS certificate (port 443) and build the record for usage 2 or 3.
Certificate total lifetime #
The CA/Browser Forum limits the maximum validity of leaf certificates, and ballot SC-081 shrinks the cap stepwise by issuance date: 398 days → 200 days (issued from 2026-03-15) → 100 days (2027) → 47 days (2029). Long lifetimes increase the risk in the event of key compromise; browsers may reject over-long certificates outright.
Ratingfail + special grade T once the lifetime exceeds what a browser enforces, because that is a live outage. Chrome walks the same SC-081 ladder as the CA/Browser Forum – 200 days from 2026-03-15, 100 from 2027-03-15, 47 from 2029-03-15, 398 before that – and shows a full-page error above it; Apple publishes only the 398-day rule, so Safari is named for that threshold alone, and Firefox does not enforce these limits on the ordinary HTTPS path at all: NSSCertDBTrustDomain::CheckValidityIsAcceptable returns success unconditionally (read from mozilla-central on 2026-08-28), so the connection succeeds there. Firefox does cap lifetimes at 14 days for WebTransport serverCertificateHashes, but that is a different path, not TLS server authentication. warn (no cap) remains for the one case in which the certificate breaks the issuance rule without being blocked: Chrome measures the period exclusively, so a certificate sitting exactly on the limit is a violation by the inclusive count and is still accepted there. Apple defines the validity period inclusively for its own 398-day rule (RFC 5280 §4.1.2.5), so the same certificate need not be the milder case in Safari. Weight 1. The 398-day test uses the exclusive difference between the two dates, so a certificate the report shows as "399 days" (counted inclusively, the way the Baseline Requirements do) can still be the milder case.
How to fix itUse short-lived certificates with automatic renewal (ACME) – e.g. Let’s Encrypt (90 days).
Certificate Transparency (SCT) #
Certificate Transparency logs issued certificates publicly and makes mis-issuance detectable. Chrome has required valid SCTs for certificates issued since 2018; Firefox and Safari enforce Certificate Transparency as well, so a publicly trusted certificate without SCTs is refused by all three (Firefox: security.pki.certificate_transparency.mode defaults to 2 = enforce, read from mozilla-central on 2026-08-28). Embedded SCTs are universal at public CAs today, so this finding only appears when they are MISSING.
RatingOnly shown if no embedded SCTs are found: warn for certificates issued after 2018-04-30, info otherwise. Informational (weight 0). Delivering SCTs through OCSP is no longer a workable alternative: Chrome stopped accepting SCTs from stapled OCSP responses with version 148, and OCSP has become optional for CAs (CA/Browser Forum ballot SC-063, effective 2024-03-15) – Let's Encrypt shut its responders down entirely in August 2025 and Google Trust Services publishes no OCSP URL, while most other public CAs still run one, so this is a shift and not a switch-off. Embedded SCTs are in practice the only path CAs deliver – measured 2026-08-28 over 16 hosts from 12 issuing organisations: 16 of 16 leaf certificates carry the embedded SCT extension, 0 of 8 probed hosts served SCTs through the TLS extension – the TLS extension still works, but it has to be configured on the server.
How to fix itObtain the certificate from a CA with CT logging – standard at public CAs today (SCTs embedded).
Revocation (CRL/OCSP) #
If a certificate is compromised and revoked, clients must be able to check the revocation status – via CRL or OCSP (the addresses are in the certificate: CRL distribution points / AIA). With OCSP stapling, the server additionally delivers the signed revocation status directly in the handshake – faster and more privacy-friendly than having every client query the CA. NOTE: the industry is retiring OCSP (CA/B Forum ballot SC-063 made it optional in 2023). This scanner maintains a list of CAs that have retired OCSP or announced it – currently Let's Encrypt (responders shut down August 2025), Google Trust Services (no OCSP URLs in new certificates since H2 2025), and HARICA (no OCSP URLs since March 2026, responders off by May 2027). For certificates from these CAs a missing staple is never reported as a server shortcoming.
Ratingpass if CRL and/or OCSP information is present (weight 1; with active OCSP stapling weight 2 – a small bonus); pass as well for a short-lived certificate that carries neither – the Baseline Requirements exempt those from revocation information (total validity ≤ 7 days when issued from 2026-03-15, ≤ 10 days before), because the short lifetime replaces revocation; warn only when a normal certificate contains neither. CRL-only is the modern norm, but the two browser mechanisms differ in reach: Firefox aggregates the CAs' CRLs into CRLite (default since Firefox 137, covering the CT-logged revocations), while Chrome's CRLSets are primarily an emergency-blocking list that carries a subset of the crawled revocations – Chrome itself calls that "most security-relevant" revocations and performs no online OCSP or CRL checks at all. A stapling recommendation only appears while the issuing CA still operates OCSP; no LIVE OCSP query is performed. The stapled bytes are read for their RFC 6960 §4.2.1 responseStatus, and only successful counts as stapling: a response reporting tryLater or unauthorized is treated by clients exactly like no staple at all, so it earns neither the bonus weight nor the stapling wording – the finding names the status and asks for the responder connection to be fixed instead.
How to fix itObtain the certificate from a CA that publishes CRL and/or OCSP information (every publicly trusted CA does). Only if the CA keeps operating OCSP: optionally enable stapling (Caddy: automatic; nginx: ssl_stapling on).
OCSP Must-Staple #
If the certificate carries the Must-Staple flag, the server must staple an OCSP response into the handshake – otherwise Firefox refuses the connection. Firefox is the only mainstream browser that enforces the flag (checked against mozilla-central in August 2026: security.ssl.enable_ocsp_must_staple defaults to true). Chrome does not implement it – neither Chromium's certificate verifier nor BoringSSL's path builder knows the TLS Feature extension (OID 1.3.6.1.5.5.7.1.24) – and Apple's published trust evaluation code has no notion of it either: trustd does receive stapled OCSP responses, but the certificate parser's closed list of known extensions carries no TLS Feature entry, so nothing in the published code could enforce the flag. Safari's own TLS stack is not open source, so that is a reading of Apple's code and not a measurement of the browser. This protects against stapling failing unnoticed – but it also means a Must-Staple certificate whose stapling breaks locks Firefox users out completely.
Ratingpass if Must-Staple is required AND stapling is active; fail if the missing staple is confirmed in a second, separate handshake – Firefox then refuses the connection outright; warn if the first handshake carried no staple but the confirming second handshake did not complete, so a one-off cannot be ruled out. Weight 1.
How to fix itEither enable stapling reliably or do not set the Must-Staple flag.
TLS protocol versions #
TLS 1.2 and 1.3 are the secure, current protocols – TLS 1.3 is additionally faster (1-RTT) and removes outdated cryptography. TLS 1.0/1.1 are formally deprecated (RFC 8996): they rely on outdated primitives (SHA-1, CBC without AEAD); TLS 1.0 is additionally exposed to the BEAST attack, whose root cause – the predictable CBC initialization vector – is exactly what TLS 1.1 fixed with an explicit IV; and both have been rejected by all major browsers since 2020. The drill-down shows the full offered/disabled matrix per version.
Ratingpass only when TLS 1.2/1.3 are offered AND every version was actually measured; warn + Cap A- when TLS 1.3 is measurably absent; fail if neither 1.2 nor 1.3 is negotiated, or + Cap B if legacy TLS 1.0/1.1 is measurably still active. Weight 3. Every version probe is three-valued: a version whose probe got no answer is "not determined" – neither an all-clear nor a penalty. It blocks the "modern versions only" verdict (warn, no cap) and never triggers a cap or a "not offered" sentence. A version the main handshake demonstrably negotiated always counts as offered, even if its own probe failed.
How to fix itEnable TLS 1.2 and 1.3 (TLS 1.3 requires a current OpenSSL/server version) and disable TLS 1.0/1.1 on the server side.
HTTP versions (1.1 / 2 / 3) #
One verdict over the application-protocol generations. HTTP/1.1 is the baseline nearly every server still speaks – this scanner does not probe for it separately, so it carries no signal and is listed for completeness only. HTTP/2 multiplexes many requests over one TLS connection (negotiated via ALPN h2 in the handshake – also a marker of a modern TLS stack). HTTP/3 runs over QUIC (UDP 443) and reduces latency further; servers advertise it via Alt-Svc: h3=…. Besides the advertisement, the QUIC endpoint itself is probed: a long-header packet with a deliberately unknown version invites a Version Negotiation answer (RFC 8999/9000 §6). A Version Negotiation answer proves a live QUIC listener and reveals the supported versions (v1 = RFC 9000, v2 = RFC 9369) – without any TLS handshake. Any OTHER datagram from that endpoint proves only that something is listening on UDP 443, and the report says exactly that instead of naming QUIC; the answer is also checked against its source address and port (RFC 9000 §9 requires a client to discard packets from an unknown server address). Silence proves nothing: RFC 8999 §6 says a server "might" answer such a probe, and rate limiting, anti-amplification limits or a firewall are legitimate reasons to stay quiet. All of this is performance/quality, not a security control.
Ratingpass when the server negotiates h2 via ALPN (weight 1); HTTP/1.1 only → info only (weight 0, not penalized). The HTTP/3 state (advertised / QUIC endpoint answering / both) is reported in the value and detail without affecting the grade. A QUIC probe timeout is only interpreted as "endpoint silent" when reference probes (cached) confirm the scanner’s own outbound UDP/443 path works; otherwise it is reported as "not verifiable".
How to fix itEnable HTTP/2 in the web server (e.g. nginx ≥ 1.25.1 http2 on;, older nginx listen 443 ssl http2;, Apache Protocols h2 http/1.1). For HTTP/3: nginx listen 443 quic plus an Alt-Svc: h3=":443"; ma=86400 response header, open UDP 443, and ideally an HTTPS DNS record with alpn=h3.
Perfect Forward Secrecy #
With PFS (ECDHE/DHE), later compromise of the server's certificate private key does not retroactively expose recorded traffic — it is only a signature key there. That does not extend to every server-side secret: a TLS 1.2 session ticket carries the master secret encrypted under a long-lived ticket key (RFC 5077), and TLS 1.3 resumption without (EC)DHE (psk_ke) and 0-RTT data are not forward secret at all (RFC 8446, App. E.1). TLS 1.3 always provides PFS. The check answers two questions, not one: does OUR connection have forward secrecy, and can ANY client end up without it? The second one is about the whole set of TLS ≤ 1.2 suites the server still offers, so a server reached over TLS 1.3 can still be flagged for static-RSA suites it keeps enabled.
Ratingpass when our own connection uses ECDHE/DHE or TLS 1.3 AND no offered suite lacks forward secrecy; warn without a cap when the connection is forward-secret but the server additionally offers TLS ≤ 1.2 suites without it (static RSA) – a client that offers only those gets a connection whose recorded traffic can be decrypted later if the server key leaks, and RFC 9325 §4.1 requires servers to prefer forward-secret suites; warn + Cap B only when our own connection has no ephemeral key exchange at all. Weight 1. A suite that WAS seen counts even if the enumeration broke off early – but the reverse half is an ABSENCE claim and needs a complete list. If the enumeration broke off, the finding still passes on its measured half and says in plain words that this is not a statement about every suite the server offers. The grade is deliberately left untouched there: taking weight 1 out of the denominator would make the grade RISE on less measurement.
How to fix itPrefer cipher suites with ECDHE; disable static RSA key exchange.
Diffie-Hellman parameter strength #
Weak DH groups are attackable in two different ways: the Logjam downgrade forces a connection down to 512-bit export-grade DH (where export suites are offered), while 1024-bit groups – in practice a handful of shared standard primes – are within reach of a one-off precomputation by a well-funded attacker, which then decrypts recorded traffic passively. 2048 bits closes both; with ECDHE the question does not arise at all.
Ratingpass ≥ 2048 bits; warn 1024–2047; < 1024 → fail. Weight 1. A server that offers DHE only as a FALLBACK is covered as well: the group is taken from the cipher enumeration, which has already made that handshake, so this costs no extra connection. Such a group is reported with the same status but WITHOUT a grade cap – it only reaches clients that offer nothing better, the same line tls-pfs draws for static-RSA suites offered alongside a forward-secret one – and the finding says whether the group was negotiated or merely offered. On the grade caps, honestly: a cap (B below 2048, F below 1024) is reserved for a group our own connection actually negotiated, and measurement shows that combination cannot currently occur. This scanner's main handshake runs at its TLS library's default security level and refuses a DH group under 2048 bits outright – so a weak group can only ever be seen through the cipher enumeration, which lowers that level deliberately, i.e. only ever in the uncapped "offered" branch. The two cap letters are therefore dormant rather than wrong: they describe the rule, and it is our own probe settings that keep them from firing. Where that applies, the finding says so instead of implying the server merely preferred another suite.
How to fix itSet DH parameters to ≥ 2048 bits or use ECDHE exclusively.
Cipher suites & AEAD #
Weak cipher suites undermine encryption: RC4 and 3DES are broken/outdated, NULL means no encryption, and export/anonymous suites are trivially exploitable. AEAD availability is part of the same verdict: AEAD ciphers (AES-GCM, ChaCha20-Poly1305) encrypt and authenticate in one step and avoid the padding-oracle attacks of the older CBC suites. The drill-down lists the offered suites per TLS version: TLS ≤ 1.2 suites are enumerated iteratively, the TLS 1.3 suites (a fixed set of five per RFC 9846 Appendix B.4 – all AEAD by construction; a TLS 1.3 suite names only the AEAD and the hash, and forward secrecy comes from the (EC)DHE key exchange every full handshake uses, not from the suite) are probed individually. Note on naming: OpenSSL uses its own short names for TLS ≤ 1.2 suites (e.g. ECDHE-RSA-AES128-GCM-SHA256), while the naming convention introduced by RFC 2246/4346/5246 and the IANA registry name the same suite TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 (registered by RFC 5289) – the drill-down therefore shows both, so results are directly comparable with SSL Labs, testssl.sh and vendor documentation. Only for TLS 1.3 did OpenSSL adopt the RFC names unchanged.
RatingNULL/export/anonymous → fail + Cap F; single DES (56-bit key) → Cap C; RC4 → Cap B (Cap C with TLS 1.1+); 3DES with TLS 1.1+ → Cap C; no AEAD suites at all → warn + Cap B. TLS-1.3-only servers pass with the offered 1.3 suites. Weight 3. The verdict is deliberately asymmetric: a weak suite that WAS found stays fail even when the enumeration broke off early, while the ABSENCE of weak suites needs a complete list – an enumeration cut short by our time budget or our cap reports info (no statement) instead of a green pass, while one cut short by a failed probe of OURS reports skip and declares its 3 points as not measured, so the report says how much of the check the grade covers. A server whose TLS ≤ 1.2 support could not be established at all reports skip as well. Weak families this scanner's own TLS build cannot even offer (currently RC4, 3DES, export and single DES) are named in the finding as untested rather than absent: a server can only choose from what our ClientHello carries.
How to fix itAllow only modern AEAD suites (AES-GCM, ChaCha20-Poly1305); disable RC4/3DES/CBC-only/export/NULL.
Web server backends (per IP) #
A domain behind round-robin DNS or a CDN resolves to several A/AAAA addresses. The detailed TLS checks above connect by hostname and therefore reflect just one backend (whichever IP the resolver returned). To catch a server that is misconfigured on only some of its addresses without re-running the heavy cipher enumeration against every IP, this check fully profiles one representative per family (one IPv4 and one IPv6 – certificate, TLS versions, full cipher enumeration) and confirms the remaining IPs with a single handshake (certificate fingerprint + negotiated suite). Identical fingerprints across all of them mean the backends are interchangeable (the CDN/anycast case); a divergence is flagged. The connection targets the IP directly while still sending the hostname as SNI. A CDN provider is named when the IPs’ reverse DNS or the host’s CNAME reveals one (e.g. CloudFront, Akamai, Fastly, Cloudflare). Those two lookups are cosmetic – nothing in this rating reads the label – so they run with a short timeout and are dropped entirely once the module’s time budget is used up; in both cases the drill-down says which of the two happened instead of leaving the label silently absent.
Ratingpass when all reachable backends share one certificate + negotiated suite (weight 0 – there is nothing to credit); warn (weight 1) when they diverge; skip when not a single backend could be profiled. Addresses the scanner has no route to (e.g. IPv6 without an IPv6 egress) are marked “not testable”, and addresses whose probe failed on our side (our own timeout, a broken pipe) are marked “not determined” – neither is counted against the server. A connection the host refused or aborted is reported as such: a TCP reset and a conclusive “no such name” are the other side answering, not a gap of ours.
How to fix itServe an identical TLS configuration and certificate from every backend so the security a client gets does not depend on which IP it happens to reach.
Server cipher preference (TLS ≤ 1.2) #
If the server determines the cipher order, it can always enforce the strongest common suite. If it follows the client, a weak client can force a weaker suite – but only if weaker suites are offered at all. Modern stacks handle this differently: BoringSSL follows the client by default (its server-preference option is off unless someone sets it), while Go/Caddy always enforces its own order and reads the client’s order only as a hint – if the client’s top suite is not AES-GCM, Go switches to a ChaCha20-first list of its own, so devices without AES hardware still get ChaCha20. Go keeps the server-preference field but ignores it. Scope: the toggle (nginx ssl_prefer_server_ciphers, OpenSSL SSL_OP_SERVER_PREFERENCE, renamed from SSL_OP_CIPHER_SERVER_PREFERENCE in OpenSSL 3.6 with the old name kept as an alias) is measured here at TLS 1.2 – a single server-wide setting covering 1.0/1.1/1.2, which is why this check is labelled "TLS ≤ 1.2". Whether the same setting also governs the TLS 1.3 suite depends on the stack: with OpenSSL it does – ssl3_choose_cipher applies the option to TLS 1.3 as well. BoringSSL and Go ignore it there and decide by their own built-in rule instead: Go walks its fixed list and takes the first suite the client also offers, while BoringSSL scores the client's offer – preferring ChaCha20 only on a host without AES hardware – and lets the client's order settle equal scores (read from both sources 2026-08).
Ratingpass if the server enforces its own order; client-decided order with an all-strong suite set (AEAD + forward secrecy, nothing weak) is harmless (light-green check, not graded); warn only when client-decided AND weaker suites are offered; if the TLS ≤ 1.2 suite enumeration did not finish, neither verdict is available – the finding then reports info with weight 0 when our own cap or deadline ended it, and skip with weight 1 – declared as not measured – when a probe of OURS failed, because "harmless" needs the whole set to be strong and "risky" needs a known weaker suite. If the preference probe itself gets no answer, a placeholder line says so instead of the finding vanishing from the report. The verdict is measured at TLS 1.2 and is not graded separately for TLS 1.3 – whether the same setting governs the TLS 1.3 suite is stack-dependent (OpenSSL: yes; BoringSSL and Go ignore it there and follow their own built-in rule instead, which for BoringSSL lets the client's order settle equally scored suites — see the description above).
How to fix itOnly needed when weaker suites are offered: enable "honor server cipher order" (nginx: ssl_prefer_server_ciphers on) – or better, remove the weak suites.
Hash function for key exchange #
The server signs the ephemeral key exchange (ServerKeyExchange) of every forward-secret (ECDHE/DHE) suite. The hash used depends on the TLS version. TLS 1.0/1.1 have no signature_algorithms negotiation, so the hash is fixed by the protocol – RSA suites use MD5+SHA-1, ECDSA uses SHA-1 – meaning every forward-secret suite on these versions relies on broken SHA-1, which cannot be configured away. TLS 1.2 negotiates the hash. What matters is the hash a real client actually gets. None of the three engines we can check offers SHA-224 at all, and each puts SHA-256 and stronger first: Chrome offers no SHA-1 scheme whatsoever — BoringSSL defines no SHA-224 signature scheme, and Chromium's verify preferences carry only SHA-256+ and ML-DSA schemes — while Firefox keeps rsa_pkcs1_sha1 and ecdsa_sha1 at the very end of its list, after every strong scheme, so a server that supports any modern hash signs strong for them. Safari used to be excluded here, because Apple does not publish the engine it uses; it is now covered by measurement rather than by source: on 2026-08-29 this scanner captured 56 real Safari ClientHellos from three devices across both Apple platforms (iOS 26.6.1, iOS 27 beta and macOS 26.6.2), whose signature_algorithms list is byte-identical on all three, offering ten signature schemes — no SHA-224 scheme at all, no ecdsa_sha1, and rsa_pkcs1_sha1 in last place behind every strong scheme. That is a statement about those three engines and about the versions named, not about every browser, and it ages: a later Safari can change its list without anyone noticing. We therefore check whether the server can sign with SHA-256+ at all; only if it cannot (it tops out at SHA-224 or SHA-1) is a normal client downgraded. A server that merely tolerates SHA-224 when a client offers nothing else is not flagged – that would be a false positive. That is common but far from universal: of 20 well-known hosts probed in August 2026, 11 signed with SHA-224 when offered nothing else (CloudFront/Amazon among them) and 9 refused the handshake outright. TLS 1.3 enforces SHA-256+ by design. The drill-down lists, per TLS version, the exact cipher suites whose key exchange is signed with a weak hash. Static-RSA suites (TLS_RSA_*) send no ServerKeyExchange and are unaffected.
RatingChecked per offered TLS version. pass if normal clients get SHA-256+ AND every affected version was actually measured; skip with weight 1 – declared as not measured – if nothing weak was found but at least one version could not be probed because a probe of OURS failed, and info with weight 0 when our own deadline ended it; the "SHA-256+ only" all-clear is printed only over versions that were measured; warn (weight 1) if TLS 1.2 cannot sign with SHA-256+ and tops out at SHA-224; fail (weight 2) if SHA-1 is used – automatically on every forward-secret suite over TLS 1.0/1.1, or on TLS 1.2 if the server offers no hash stronger than SHA-1.
How to fix itDisable TLS 1.0 and TLS 1.1 (their SHA-1 key-exchange signatures are not configurable). On TLS 1.2 restrict the server signature algorithms to SHA-256+, e.g. nginx/OpenSSL: ssl_conf_command SignatureAlgorithms ECDSA+SHA256:ECDSA+SHA384:ECDSA+SHA512 (RSA certs: add RSA-PSS+SHA256/384/512).
Client compatibility (simulation) #
A simplified handshake simulation shows which reference clients (browsers, Android, Java, OpenSSL) can connect. Only clients whose OS/runtime still receives security updates are simulated – clients whose vendor stopped shipping fixes (Android ≤7, last covered by an Android security bulletin in October 2019; IE 11, desktop app retired June 2022; OpenSSL 1.0.x, end of public support) are deliberately excluded, because their compatibility carries no signal for a current TLS configuration. Java 8 is not simulated either, but for a different reason and not as end-of-life: Oracle lists it with extended support until December 2030 and still ships free public updates for personal and development use via java.com (commercial use has needed a support subscription since April 2019) – its TLS profile simply is not representative of the JDKs in production use. The profiles are this scanner's own, not a third party's capability table. Since 2026-08-29 each one declares where its cipher list came from, and the finding scopes its caveat to that. Three are captured from real ClientHellos on the wire – Chrome 152 on Linux, Safari on iOS 26.6.1, iOS 27 beta and macOS 26.6.2, and openssl 3.0.13 s_client – so a refusal of those is a property of the server. Three are derived from the vendor's published source: Firefox from nsNSSComponent.cpp, which switches off every suite NSS enables and then re-enables exactly its own list; Android 14 from Conscrypt's DEFAULT_X509_CIPHER_SUITES; Java 17 from CipherSuite.java minus the disabledAlgorithms line of java.security. That is close to the real client but not a measurement against one, and the difference is not pedantry: the first source-derived Chrome list of that day named two suites the shipped browser does not send, because BoringSSL excludes the ciphers it marks deprecated – only the capture caught it. One omission is named rather than hidden: Safari also offers three 3DES suites, which this scanner's TLS build can no longer express. One macOS device, measured on 2026-08-29, made its offer in two stages: its first connections carried 13 suites, the later ones the full 20 that iOS sends from its very first hello – the narrow set a strict subset of the wide one, missing exactly the static-RSA and 3DES suites. Repeating that measurement on 2026-08-31 did not reproduce it: a second macOS device on the same Safari version and an iPhone both offered the wide list from their very first hello at every endpoint, including one that reproduced the earlier instrument byte for byte. All four recordings carry the same JA3, so the narrow offer rests on a single device and is not an established property of Safari on macOS. The profile keeps the wider list, and the newer measurement supports that: three of the four devices measured offer it unconditionally, and only a server offering nothing but those legacy suites can tell the two apart – there the narrow list would accuse Safari of failing a handshake it does complete. A genuine handshake_failure(40) does not change the offer at all: 68 rejections across both platforms produced exactly one JA3 each, with no version fallback and no TLS_FALLBACK_SCSV. What is still undecided is whether Safari would carry a handshake over AES128-SHA through to the end – it offers the suite, which is not the same as using it. The three TLS 1.3 suites are in no list because they are configured separately – measured, they are exactly the ones this build already offers by default. All six profiles also floor at TLS 1.2 and cap at TLS 1.3, and that floor is measured rather than assumed: the captured ClientHellos offer nothing older, Firefox ships security.tls.version.min at TLS 1.2 with deprecated versions off, Java 17 disables TLSv1 and TLSv1.1 in java.security, and Android 14 has done the same since its quarterly release of December 2023 – only the snapshot frozen on release day still enabled them, which is why the Android stamp names a tree that actually ships.
Ratingwarn if a simulated reference client (Chrome/Firefox/Safari current, Android 14, Java 17, OpenSSL 3.x) is REFUSED by the server; skip with weight 1 – declared as not measured – if no profile was refused but one or more probes got no answer at all: that is our transport, not a property of the server, and the point is reported as missing coverage instead of quietly leaving the rating; otherwise pass. Weight 1.
How to fix itThe two outcomes do not carry the same weight, and both are about protocol and cipher negotiation only: "connects" is conclusive for that question – the real client offers at least what the profile offers, so it negotiates as well – whereas "fails" is only as strong as the profile behind it: conclusive where the list was captured from a real handshake, close but unmeasured where it was derived from published source. Certificate problems are not part of this simulation; the certificate findings cover them. If a supported client fails, check protocols and cipher suites, but verify against the actual client before changing the configuration.
HTTP-to-HTTPS redirect #
If someone visits the site over http://, it should immediately redirect to https:// – otherwise the session starts unencrypted and can be tampered with.
Ratingpass with any 3xx whose Location is an https URL (301 and 308 are both correct; 308 additionally preserves the method, RFC 9110 §15.4.9); warn when the first hop does upgrade to https but demonstrably switches host – HSTS can then no longer be set for the name the visitor typed; fail if HTTP is answered without a redirect, or if the first hop points at another plaintext http target; skip if port 80 refuses the connection – a host that does not listen on port 80 has no defect; skip when OUR own probe got no answer – that says nothing about the server, and the 2 points are declared as not measured instead of silently leaving the rating. Only the first hop is followed. Weight 2.
How to fix itPermanently redirect all HTTP requests to HTTPS via 301 (also set HSTS).