Skip to main content
RunBook Academy

← All break/fix scenarios in Secrets, PKI & Certificates

advancedtls-config~40 min

The new portal presents another site certificate and the fix made the dashboard lie

Reported symptoms

  • At 09:20 UTC, ten minutes after the reports portal is announced, every user reaches a browser certificate warning that names a completely different internal site
  • The warning names the status page, so the first hypothesis in the channel is that DNS for the new name points at the wrong host, and two engineers spend fifteen minutes on DNS
  • The two other virtual hosts on the same listener, the API and the shop front end, are entirely unaffected and each presents its own correct certificate
  • The configuration test reports the running configuration as valid, and the reload performed at 09:12 logged no warnings and no errors
  • The certificate and private key for the new portal are present on the host, correspond to each other, and have 172 days of validity remaining
  • A full restart of the web server at 09:48, rather than a reload, changes nothing at all
  • At 10:05 an engineer reissues the status page certificate with the portal name added to its subjectAltName, the browser warnings stop, the synthetic check turns green, and every request to the portal returns the status page with HTTP 200

Evidence

  • · openssl s_client with the portal name sent as the server name returns a certificate whose subject is the status page, with the status page serial and the status page subjectAltName entries
  • · openssl s_client with the status page name sent as the server name returns a certificate with the identical serial, so both names are being answered by the same virtual host
  • · openssl s_client with the API name sent as the server name returns a different certificate with a different serial, so server name selection is working correctly for every name the server knows about
  • · curl to the portal name fails with error 60 reporting that no alternative certificate subject name matches the target hostname reports.example.com
  • · The running configuration dump contains three server_name directives, for the API, the shop and the status page, and no server block naming the portal at all
  • · The configuration directory contains a file for the portal whose name ends in a suffix the include pattern does not match, so the file is present on disk and absent from the running configuration
  • · The portal certificate on disk and its private key produce identical public key digests and the certificate is 172 days from expiry, so the material is correct and simply unused
  • · After the subjectAltName was widened at 10:05, the portal name still returns the same serial as the status page name and the response body is still the status page, so the change suppressed the warning without moving any traffic
Diagnosis and resolutionclick to reveal

Root cause

Requests for reports.example.com are not matched by any virtual host, so the listener answers them with its default server, and the default server holds the status page certificate. The client asks for one name, receives a certificate issued to another, and refuses. Neither certificate is wrong. The server simply had nothing configured for the name it was asked for and fell back to what it does have. The first defect is the file itself. The deployment wrote the portal virtual host with a suffix outside the pattern the main configuration includes, so the block never entered the running configuration. The configuration test passed because a glob matching no file is not an error, and no component can know a file you intended to load was skipped. The dump of the running configuration is the only place that difference is visible, and nobody read it until the certificates had been blamed. The second defect is what the protocol does when a name is unknown. The requested name arrives in the Server Name Indication extension and is used purely to select a certificate. There is no way for a server to answer that it does not host that name, because the certificate must be chosen and sent before any application layer exists to say so. Unmatched names therefore receive the default certificate rather than a refusal, which turns a routing mistake into what looks exactly like a certificate mistake. Widening the status page subjectAltName at 10:05 removed the only visible evidence of the routing fault while the portal went on serving the wrong site, and the monitoring, which checked only for TLS success and HTTP 200, reported the outage resolved.

Remediation

Decide first whether this is a certificate problem or a selection problem, because they look identical from a client and are repaired in different files. Request the failing name and one known-good name from the same listener and compare the serials of the certificates returned. Two different names answered by one serial means the server is falling back rather than selecting, and no reissue of any certificate will change that. Repair the selection. Rename the virtual host file so it matches the include pattern, validate the configuration, and then confirm from the dump of the running configuration that a server block for the portal is now present, since validation proves only that what did load is syntactically sound. Reload rather than restart, then verify by serial from outside the host. Next, revert the widened subjectAltName by reissuing the status page certificate with its own names alone, because a certificate that vouches for a name its virtual host does not serve is a false entry in the inventory and will mislead the next person to read it. Finally, give the listener a catch-all default server that fails loudly on an unknown name instead of quietly lending it another site certificate. Do not add names to whichever certificate happens to answer. It is fast, it clears the browser warning, and it converts a loud routing failure into a silent one that serves the wrong content with a valid certificate and a green check. Do not tell users to click through the interstitial, and do not suppress the check that was correctly reporting the problem. Do not deploy a wildcard to make the mismatch disappear, because a wildcard would have hidden this fault from the first minute.

Verification

Verify from outside the host, against the address users actually reach, and verify identity rather than availability. For every name the listener is meant to serve, request it with that name as the server name and compare the serial of the returned certificate against the serial expected for that name. Every name must map to its own certificate, and no two names may share a serial unless they are deliberately on the same certificate. Then verify the routing separately from the handshake, because this incident proved that a valid certificate and an HTTP 200 can both be true while the wrong application answers. Request a path that exists only on the portal and confirm the portal response, and read a response header that identifies the serving virtual host so the answer is unambiguous rather than inferred from page content. Confirm the reissued status page certificate carries only its own names, and that the portal name no longer appears anywhere in it. Confirm the catch-all default server rejects an unknown name rather than presenting a real certificate, by requesting a name that is deliberately not configured. Finally, confirm the synthetic check now fails when pointed at the pre-repair configuration, since a check that passed throughout the outage has not yet been shown to work.

Prevention

Verify the loaded configuration, not the written one. A deployment step that writes a file and then reads the running configuration dump back, failing when the block it wrote is absent, would have caught this before the announcement. A syntax test cannot report a file that was never included, and that gap is the entire incident. Make virtual host selection fall through to a hard failure. Configure the default server for each listening address as a catch-all that closes the connection or answers with a dedicated certificate that matches nothing real, so an unmatched name produces an unmistakable error rather than borrowing the identity of whichever site happens to be first. Assert served identity per name after every reload: a post-deploy check that maps each configured name to the serial of the certificate it receives, and fails on any name whose serial is not the expected one, runs in seconds and detects both this fault and its opposite. Make synthetic checks assert which application answered, not only that something did, since a check that accepts any HTTP 200 will report a green service for as long as any virtual host on that address is healthy. Keep certificate name sets minimal and reviewed, and treat any addition of a name to an existing certificate as a change requiring the same scrutiny as issuing a new one. Finally, alert within five minutes on any name whose served certificate serial changes unexpectedly, because that single signal covers misrouting, accidental reissue and fall-through alike.

Reported symptoms

web-03 is an ordinary name-based virtual hosting node. One address, one TLS listener, four sites: the API, the shop front end, the status page, and, as of this morning, a new internal reports portal at reports.example.com. Certificate, key, virtual host file and DNS record were all prepared the previous week. The configuration was reloaded at 09:12 and the portal was announced at 09:10.

By 09:20 the announcement has been retracted:

  • Every user who follows the link reaches a browser certificate interstitial. The name in the warning is not the portal. It is the status page.
  • 09:24, that name sends the channel to DNS. Two engineers spend fifteen minutes confirming that reports.example.com resolves to the same address as the other three sites, which it does, and which is correct.
  • The API and the shop are untouched. Both present their own certificates, both serve normally, both are on the same listener.
  • The configuration test passes. The 09:12 reload logged nothing at all, neither warning nor error.
  • 09:40, somebody checks the portal certificate on disk. It is there, the key matches it, and it has 172 days left.
  • 09:48, the server is fully restarted rather than reloaded, on the theory that a reload missed something. Nothing changes.

At 10:05 an engineer takes the pragmatic route and reissues the status page certificate with reports.example.com added to its subjectAltName. The browser warnings stop instantly. The synthetic check flips to green. The incident is marked resolved at 10:11.

The portal is still completely unavailable. Every request to it now returns the status page, with a valid certificate and HTTP 200, and it stays that way for another fifty minutes while the dashboard says the service is healthy.

Evidence provided

Read-only / Safeasking for the portal at 09:31 and being handed the status page identity
$ openssl s_client -connect web-03.example.com:443 -servername reports.example.com </dev/null 2>/dev/null | openssl x509 -noout -subject -serial
subject=CN=status.example.com
serial=7F3C08A6D149B25E0C83F71A4B62D905E1A8347C

Illustrative output

Read-only / Safethe same listener, a different name, the identical serial
$ openssl s_client -connect web-03.example.com:443 -servername status.example.com </dev/null 2>/dev/null | openssl x509 -noout -subject -serial
subject=CN=status.example.com
serial=7F3C08A6D149B25E0C83F71A4B62D905E1A8347C

Illustrative output

Read-only / Safea third name proving that name-based selection works for everything the server knows
$ openssl s_client -connect web-03.example.com:443 -servername api.example.com </dev/null 2>/dev/null | openssl x509 -noout -subject -serial
subject=CN=api.example.com
serial=4E60B3D8172AC59F0B47E2D6318A9C05F7B12D63

Illustrative output

Read-only / Safethe same fault as the browser reports, stated without an interstitial
$ curl -sS https://reports.example.com/
curl: (60) SSL: no alternative certificate subject name matches target hostname 'reports.example.com'

Illustrative output

Read-only / Safeevery name the running configuration actually knows about
$ sudo nginx -T 2>/dev/null | grep server_name
    server_name api.example.com www.api.example.com;
  server_name shop.example.com;
  server_name status.example.com www.status.example.com;

Illustrative output

Read-only / Safethe same directory on disk, where the portal file has been all along
$ ls /etc/nginx/conf.d/
api.example.com.conf
default.conf
reports.example.com.conf.new
shop.example.com.conf
status.example.com.conf

Illustrative output

Work the evidence before reading on

Two of these captures differ by one word on the command line and return byte-identical results. That coincidence is the diagnosis, and everything else in the timeline is a consequence of not having looked at it.

  1. Two different requested names produced one serial. Name a mechanism that can do that, and say what it implies about how many virtual hosts are involved.
  2. The API name returned its own certificate from the same listener in the same second. What does that exclude, and which popular theory does it kill?
  3. The configuration test passed and the reload was clean. Compare what a syntax test reads with what the configuration dump reads, and say which of the two could have caught this.
  4. After the subjectAltName was widened, the warning stopped and the portal still did not work. Say precisely which of the two faults that change addressed, and which check would still have failed.

Before continuing: explain why a TLS server cannot answer that it does not host a name, and say what it does instead.

Root cause

The name matched no virtual host, so the listener fell back

Name-based virtual hosting selects a server block by comparing the requested name against configured names. When nothing matches, the listener uses its default server, and the default server on this node is the status page. The portal name therefore received the status page certificate, which is a correct action taken on an incorrect premise.

The evidence is the pair of serials. Asking for the portal and asking for the status page returned the same certificate, byte for byte, while asking for the API returned a different one. One serial answering two names is fall-through. It cannot be a certificate fault, because the certificate being served is the correct certificate for the site that is genuinely serving it.

The file was on disk and outside the include pattern

The main configuration includes conf.d/*.conf. The deployment wrote reports.example.com.conf.new. The file is present in the directory, readable, syntactically fine, and never loaded.

Nothing reported this, and nothing could. A glob that matches fewer files than the author expected is not an error condition, and the syntax test validates the configuration that was assembled rather than the configuration that was intended. The dump of the running configuration shows three server_name directives where there should be four, and it is the only artefact in the entire incident that states the fault directly.

The repair at 10:05 removed the evidence and kept the outage

Adding the portal name to the status page certificate made the client stop complaining, because the client only ever complained about identity. It changed nothing about which virtual host answers, so the portal name kept reaching the status page, now with a certificate that vouched for the lie.

The monitoring made this worse rather than catching it. The synthetic check asserted a successful handshake and an HTTP 200 and asked for nothing else, so a healthy status page answering on the portal name satisfied it completely. Fifty minutes of outage were recorded as uptime, and the certificate inventory now contained a name that no virtual host served, which is a fault waiting for whoever reads it next.

Resolution

  1. Establish selection versus certification before editing anything. Request the failing name and one known-good name from the same listener and compare the returned serials. One serial answering two names is a virtual host fault, and no certificate change can repair it.
  2. Read the running configuration dump rather than the files on disk, and count the server_name directives against the sites you expect. This is the artefact that names the fault, and the syntax test cannot substitute for it.
  3. Rename the virtual host file so that it matches the include pattern, and check whether the deployment tooling wrote any other file with the same suffix, because a mistake in a template is rarely used only once.
  4. Validate the configuration, then confirm from the dump that the portal server block is now present. Validation proves that what loaded is well formed, which is a different claim from the block having loaded at all.
  5. Reload rather than restart so that connections drain, then verify by serial from outside the host that the portal name now receives the portal certificate.
  6. Reissue the status page certificate with its own names alone, removing the portal name that was added at 10:05. Leaving it in place keeps a false assertion in the inventory and preserves the exact conditions that made the outage invisible.
  7. Give the listener a catch-all default server that fails on an unknown name instead of lending it a real site certificate, so the next fall-through announces itself rather than impersonating something.
  8. Strengthen the synthetic check before closing the incident, so that it asserts which virtual host answered rather than accepting any HTTP 200. The check was green throughout the outage and is part of the fault.
  9. Do not add names to whichever certificate happens to answer, do not advise anyone to click through the interstitial, and do not deploy a wildcard to make the mismatch disappear. A wildcard would have concealed this fault from the very first request.

Verification

  1. From outside the host, request every configured name with that name sent as the server name and record the serial returned for each. Compare the result against the expected serial for that name rather than against your memory of it.
  2. Confirm no two names share a serial unless they are deliberately carried on one certificate. A repeated serial is the exact signature of the fault being repaired.
  3. Request a path that exists only on the portal and confirm the portal response, since a valid certificate and an HTTP 200 were both true throughout the outage and neither proved anything about routing.
  4. Read a response header that identifies the serving virtual host, so the answer is stated by the server rather than inferred from page content that somebody has to recognise.
  5. Confirm the reissued status page certificate carries only its own names, and that reports.example.com appears nowhere in its subjectAltName.
  6. Request a name that is deliberately not configured and confirm the catch-all default server rejects it, rather than presenting a certificate belonging to a real site.
  7. Point the synthetic check at the pre-repair configuration and require it to fail. It passed for the entire incident, so until it has failed once it has not been shown to measure anything.

Prevention

  • Verify the loaded configuration, not the written one. A deploy step that writes a file and then greps the running configuration dump for the block it wrote, failing when it is absent, closes this gap permanently. A syntax test can never report a file that was never included.
  • Make fall-through a hard failure. A catch-all default server that refuses unknown names, rather than the first real site on the listener, converts a silent misrouting into an unmistakable error the first time it happens.
  • Assert served identity per name after every reload. A check that maps each configured name to the serial it receives and fails on any mismatch takes seconds, and it detects fall-through, a wrong file deployed, and an unplanned reissue with one rule.
  • Make synthetic checks name the application that answered. A check that accepts any HTTP 200 will report a healthy service for as long as any virtual host on that address is alive, which is exactly what it did here for fifty minutes.
  • Treat adding a name to an existing certificate as issuance. It changes what an identity asserts, so it deserves the same review as a new certificate. Most instances of it are somebody silencing a symptom.
  • Alert within five minutes on an unexpected change of served serial for any name. That single signal covers misrouting, accidental reissue, a failed deploy and a fall-through, and it is cheap enough to run against every name in the estate.