Reported symptoms
The failover of rbdr-shop finished at 09:38. Application healthy, database
current to the declared recovery point, every backend green, the synthetic check
returning 200. The bridge stood down.
At 09:50 the support queue starts filling with connection timeouts on
rbdr-shop.example.net, and the rate climbs all morning rather than settling.
Traffic at the recovery site is about 4% of a normal weekday, nearly all of it
from the office range and the monitoring probes.
A responder on a mobile connection cannot reach the site; the same responder on the office VPN reaches it immediately. That observation convinces the bridge the problem is in the network path, and somebody asks for an emergency firewall change at the recovery site.
Nothing failed. The change record shows the DNS update applied at 09:34 without error, and the runbook’s own verification step returns the recovery address every time anyone runs it.
Evidence provided
Ask each published authority directly, with recursion off, so the answer comes from the source rather than from anything in between:
$ for NS in ns1.example.net ns2.example.net ns3.example.net; do dig +norecurse +noall +comments +answer @"$NS" rbdr-shop.example.net A; doneAll three return 203.0.113.45 with aa set and identical SOA serials. The
change is live, everywhere, and the secondaries have it.
Now ask from a client position resembling a customer: a host outside the estate, on its own provider’s resolver.
$ ssh rbdr-probe-ext 'dig +noall +comments +answer rbdr-shop.example.net A; sleep 120; dig +noall +answer rbdr-shop.example.net A'Both answers give 198.51.100.20 — the dead primary. The first reports a time to
live of 43891, the second 43771. There is no aa flag on either.
The public zone explains the number; the internal zone explains why nobody noticed:
; public view — served to the internet
$TTL 86400
rbdr-shop IN A 203.0.113.45
; internal view — served to the estate resolver from a separate copy
$TTL 60
rbdr-shop IN A 203.0.113.45
And the firewall theory, tested rather than argued — pin the name to the recovery address and make the request from outside anyway:
$ ssh rbdr-probe-ext "curl -sS -o /dev/null --resolve rbdr-shop.example.net:443:203.0.113.45 -w 'HTTP %{http_code} verify %{ssl_verify_result}\n' https://rbdr-shop.example.net/healthz"It returns 200 with a certificate that validates.
Step 14 of the runbook reads, in full: Update DNS, then confirm the name resolves to the recovery address before proceeding.
Work the evidence before reading on
- The external answer decrements by exactly 120 over 120 seconds and carries
no
aaflag. What does a countdown tell you that a single reading cannot? - The firewall theory explains why internal clients work and external ones do not. Which single piece of evidence kills it outright?
- The authorities publish the right address and the estate resolver returns the right address. Why did neither observation help?
- Somebody suggests dropping the record to a 60-second value right now. What does that change, and for whom?
Root cause
Take the firewall theory first, because it fits the symptoms perfectly and is wrong. The pinned request from the external host reached 203.0.113.45 over the public internet, completed a TLS handshake against a certificate that validates for the name, and got a 200 back. The path is open, the service answers on it, and the certificate is right. Nothing between the customer and the recovery site is dropping anything. What the customers cannot do is find the address at all.
The published answer had a day of life left in it
The check was run from the one place it could not fail
example.net is served in two views. The public view carries 86400. The
internal view, on a separate authoritative copy the estate resolver is pointed
at, carries 60 — chosen years ago so internal renumbering would settle fast.
Step 14 says confirm the name resolves and never says from where. Run on the jump host, it queries the internal view, which had been correct since 09:35. The check was not weak or skipped: it measured a different system from the one that was broken, and it will pass during every future failover for the same reason.
Resolution
Start from what is not available: a cached answer cannot be recalled. No message clears a resolver you do not operate, and these caches belong to other people. Any plan beginning with “flush the caches” is a plan to do nothing while feeling busy.
One lever acts inside the window — make the old address answer. If 198.51.100.20 can be moved to the recovery site by reassignment, by a routing announcement or by an upstream translation, the customers holding the cached answer arrive without knowing anything changed. Arrange that first: it is the only remedy that helps anyone already affected.
Republish with a short value in the same change so the next cutover is not hostage to this one:
$ sudo rndc reload example.net && dig +norecurse +noall +answer @ns1.example.net rbdr-shop.example.net AIf the address cannot be moved, stop quoting a restoration time the name cannot deliver. Tell customers when the cached answers expire rather than when the service came back, on a channel not bound to that name.
Repair step 14 before the next failover inherits it: name the client position, and run the confirmation from outside the estate.
Verification
$ ssh rbdr-probe-ext 'dig +noall +answer rbdr-shop.example.net A && curl -sS -o /dev/null -w "HTTP %{http_code}\n" https://rbdr-shop.example.net/'The name returns the recovery address from at least three external vantage points, each with a time to live consistent with the newly published value rather than a countdown from 86400, and the fetch returns the expected page on a certificate that validates.
Request volume at the recovery site returns to the shape of a normal weekday rather than a figure dominated by the office range and the probes — the measurement that says customers arrived, independent of anything a responder queried.
The repaired step fails when it should: run it against a name deliberately left on the old address and confirm it reports the mismatch.
Prevention
Set the time to live before the incident, not during it. A name published at 86400 carries a cutover floor of roughly a day however fast the failover runs. Reduce it on every recovery-path name at least one old interval ahead of a planned cutover, and keep the standing value a deliberate trade against query volume.
Make every validation step name the client position. This is a general failure wearing a DNS costume: a check run from somewhere that does not resemble the affected party keeps passing while the service is unusable.
Record which names are served in more than one view, and where the values differ. A split view is a legitimate design and an excellent trap: it makes the operator’s experience of a change permanently unlike the customer’s.
Rehearse the cutover from outside and time it. How long the answer takes to turn over at external resolvers is the part of the recovery nobody here can shorten once the incident has started.