DNS and TLS: Why Your First Prod URL Fails

I pointed a real subdomain at the live service, deployed, typed the address into a browser, and got an error. First it was “site cannot be reached.” Once that cleared on its own, the page loaded over plain HTTP but https:// showed a red “your connection is not private” warning. I assumed I had broken the deploy and spent the better part of an hour re-pushing images that were already correct. Nothing was wrong with the deploy. The name had not finished spreading across caches I do not control, and the TLS certificate had not been issued yet. Those were two separate clocks running, and I did not know either one existed.

In the last lesson the promotion flow moved a single validated image digest from dev to prod, and the service answered at the URL the platform handed over, a host-assigned name like svc-abc.<platform-domain>. That URL never imposed a wait, because the platform already owned the name and the certificate for it. This lesson is the first time the URL is owned by the developer: a purchased domain, with a subdomain pointed at the running service. The moment the name belongs to the developer, two delays appear that the host-assigned URL hid. The name has to spread across resolvers nobody pushes to. A certificate has to be issued for a name the issuer can reach. The work of this lesson is to understand both clocks well enough to wait calmly instead of breaking a service that already works.

Two terms carry the whole lesson, so ground them before anything else. The name resolves means a caller that types predict.myproduct.com gets back the IP address of the host. The certificate is issued means a trusted authority has signed a statement that the server controls that name, so https:// loads without a warning. The opening incident is the gap between creating the domain and both of those becoming true, and the second cannot even begin until the first finishes.

I pointed a real subdomain at the live service, deployed, typed the address into a browser, and got an error. First it was “site cannot be reached.” Once that cleared on its own, the page loaded over plain HTTP but https:// showed a red “your connection is not private” warning. I assumed I had broken the deploy and spent the better part of an hour re-pushing images that were already correct. Nothing was wrong with the deploy. The name had not finished spreading across caches I do not control, and the TLS certificate had not been issued yet. Those were two separate clocks running, and I did not know either one existed.

In the last lesson the promotion flow moved a single validated image digest from dev to prod, and the service answered at the URL the platform handed over, a host-assigned name like svc-abc.<platform-domain>. That URL never imposed a wait, because the platform already owned the name and the certificate for it. This lesson is the first time the URL is owned by the developer: a purchased domain, with a subdomain pointed at the running service. The moment the name belongs to the developer, two delays appear that the host-assigned URL hid. The name has to spread across resolvers nobody pushes to. A certificate has to be issued for a name the issuer can reach. The work of this lesson is to understand both clocks well enough to wait calmly instead of breaking a service that already works.

Two terms carry the whole lesson, so ground them before anything else. The name resolves means a caller that types predict.myproduct.com gets back the IP address of the host. The certificate is issued means a trusted authority has signed a statement that the server controls that name, so https:// loads without a warning. The opening incident is the gap between creating the domain and both of those becoming true, and the second cannot even begin until the first finishes.

Resolve the name before assuming the route is broken

The plausible mental model is that creating a DNS record is like assigning a variable. Set predict.myproduct.com = <host IP>, and from that instant every machine on earth that types the name reaches the host. The record is a fact; the fact is now true. Under that model, if the name fails to load the moment after the record is created, something must be broken: the record, the deploy, or the host, because an assignment takes effect immediately.

That model predicts something watchable. Run the same lookup from two networks right after creating the record, and they disagree at the same instant:

# From your laptop, on your home network's resolver:
$ dig +short predict.myproduct.com
203.0.113.42                      # resolves to the host — looks fine

# From a phone on cellular, against a different resolver, the same second:
$ dig +short predict.myproduct.com
                                  # empty — "site can't be reached"

If the record were a variable, both lookups would return the same value the instant after assignment. They do not. Re-creating the record changes nothing, and re-pushing the image changes nothing, because neither the record nor the deploy is the problem. Waiting fixes it. That is the tell that the variable model is wrong.

A name is not a global variable; it is an answer that millions of independent caches each fetch and expire on their own clock. Nothing pushes the change anywhere. A request never travels to a domain name in the first place. It travels to an IP address, and the name is only a lookup key the caller resolves first. That lookup walks down a delegated hierarchy where every step is allowed to cache its answer for a stated time. So a freshly-changed record is not a fact the world knows; it is a fact that becomes visible to each network only when that network’s older cached answer expires. Until then, “the deploy is broken” and “the name has not reached this resolver yet” produce the identical symptom, and only one of them is true.

The resolution walk has four players. Name them, and dig output becomes readable instead of a guess.

  • Recursive resolver: the server the caller is configured to ask (the network’s, or a public one like 8.8.8.8). It does the walking and holds the cache. On a cache hit it answers from memory; on a miss it recurses down the hierarchy.
  • Root / TLD / authoritative nameservers: the hierarchy the resolver walks on a miss. The root says who handles .com, the .com TLD server says who is authoritative for myproduct.com, and the authoritative nameserver, where the record was actually created, returns the answer for predict.myproduct.com.
  • Record: the entry that maps the name to a target.
  • TTL: the time-to-live the authoritative server returns with the record, meaning “this answer may be cached for this many seconds.” The TTL is the only lever over how long a stale answer survives.

The scrolly below traces one record’s propagation as caches expire over time. The insight is the temporal spread across resolvers that nobody coordinates, which is exactly what prose cannot show in a sentence.

The record is created at the authoritative nameserver

The record is added at the authoritative nameserver for the zone, the one server that holds the truth for predict.myproduct.com. At this instant the fact exists in exactly one place. Nothing has been pushed to any resolver, and no caller anywhere knows about it yet.

Resolver A has no prior cache, so it recurses and gets the new value

A caller behind resolver A looks up the name. Resolver A has never been asked, so it walks root, then TLD, then the authoritative server, fetches the record, and serves the new value with a fresh TTL. For this caller the name resolves immediately, because there was nothing stale to expire.

Resolver B still holds a cached miss from before the record existed

A caller behind resolver B had already looked the name up earlier, before the record existed. Resolver B cached the “no such name” answer and keeps serving it. Creating the record correctly does not reach into resolver B and clear that cached miss, which is why two networks disagree at the same instant.

Resolver B’s cached entry counts down its own clock

The cached miss in resolver B expires on a timer set when it was stored, not when the record was created. Nothing done at the authoritative server shortens it. The two resolvers are on different clocks because they cached at different times.

B expires, re-recurses, and both resolvers serve the new value

Resolver B’s cached entry finally expires. The next caller forces a fresh recursion, B reaches the authoritative server, and now both resolvers serve the new IP. The name is “propagated,” not because anything was pushed, but because every cache independently noticed its old answer expired and re-asked.

The record itself comes in two shapes, and the choice between them is a real decision because each fails differently. An A record points the name directly at an IPv4 address. A CNAME points the name at another name, which the resolver must then resolve to an address, an extra hop. Managed container platforms usually hand back a stable hostname rather than a fixed IP, so the CNAME exists precisely to let the host move the IP underneath that hostname without any manual edit.

A record (name → IP directly) When: the host has a stable IP and the name should point straight at it; apex/root domains (myproduct.com with no subdomain) generally require an A record, because the standard forbids a CNAME at the zone apex. Failure modes: if the host’s IP changes (common on managed platforms that do not assign a fixed IP), the A record now points at nothing and the new IP must be chased by hand. That is the exact churn a CNAME avoids.

CNAME (name → another name) When: the host assigns a stable hostname (svc-abc.<platform-domain>) and may move the IP underneath it; aliasing a subdomain to that hostname lets the host re-point the IP without any DNS edit. Failure modes: cannot be used at the zone apex; and it adds a resolution hop (resolve the CNAME target, then its address), so a misconfigured or slow target compounds the propagation delay rather than removing it.

The apex restriction is not a platform quirk; it follows from the DNS standard. The zone apex must hold the records that define the zone itself, and the standard states that if a CNAME is present at a node, no other data may be present there, so a CNAME cannot coexist with the apex’s required records. That is why a subdomain can be a CNAME but the bare domain must use an A record (or a platform’s “alias” extension).

The failure mode that catches everyone is negative caching. A resolver caches not only positive answers but misses. If any caller looked up predict.myproduct.com before the record existed, the resolver cached an NXDOMAIN (the “no such name” answer) and keeps answering “does not exist” until that negative entry expires. The trap is that the negative-cache timer is governed by the zone’s SOA record, independent of the new record’s TTL. So the record can be created correctly, with a short TTL on it, and a resolver that already cached the miss still answers NXDOMAIN until the SOA-derived negative timer runs out. The symptom in the opening incident, “site can’t be reached” lingering after the record is clearly correct, is usually a cached miss, not a cached stale value. This is why two networks disagree: one may be expiring a stale value while the other expires a stale miss, on two unrelated clocks.

Because the only thing bounding how long any stale answer survives is the TTL each resolver cached, the TTL set before a change decides the size of the window. This is a tunable, and the staff move is to learn the response curve rather than memorize a number.

Low TTL (for example, 60 seconds) When: a change is planned or the record might move soon, such as a migration, a cutover, or a first deploy expected to need correction. A short window means a mistake or a move is visible everywhere within a minute. Failure modes: every resolver re-asks the authoritative server constantly, raising query load and removing the caching that DNS exists to provide. Left permanently low, it turns the authoritative nameserver into a hot path and a single point of latency for every request to the site.

Default TTL (minutes to an hour) When: steady state, no change expected. Balances “changes are visible in a reasonable window” against “resolvers are not hammering the authoritative server.” Failure modes: a record changed without pre-lowering the TTL stays stale for up to the full TTL on resolvers that cached just before the change, the surprise hour in the opening incident.

High TTL (for example, 24 hours) When: a record that genuinely never changes, where maximum cache offload and resilience to an authoritative-server outage are the goal. Failure modes: a mistake is now baked in for up to a day on already-cached resolvers, and there is no way to expire it early, because other people’s resolvers cannot be un-cached on demand. A high TTL turns “just fix the record” into “wait a day for the fix to be visible.”

The lever follows from the curve: lower the TTL the day before a planned change, wait for the old high TTL to expire everywhere so every resolver is now caching at the short value, then make the change so every resolver re-fetches within a minute, and raise it back afterward to cut load on the authoritative server. One question decides which way to move: is a change coming? If yes, lower it first and accept the query load for a day. If the record is stable and low cost plus outage resilience is the goal, raise it and accept that the next change needs a day of pre-planning. No setting gives both fast change and high offload at once; that tension is the whole reason TTL is a tunable and not a constant.

dig does not run inside this lesson, but the thing it makes a reader reason about is computable: the worst-case stale window. The block below takes a TTL and reports the longest time a resolver that cached just before the change can keep serving the old answer. Watch that the answer depends only on the TTL, not on how correct the new record is.

python
"""Compute the worst-case stale window: the longest a resolver serves the old answer."""


def worst_case_stale_seconds(
    ttl_seconds: int, cached_just_before_change: bool = True
) -> int:
    """Longest a resolver can serve the OLD answer after you change a record.

    A resolver that cached the old answer one second before your change holds it
    for the full remaining TTL. The record being correct at the authoritative
    server does nothing to shorten this -- only the cached TTL bounds it.
    """
    if not cached_just_before_change:
        return (
            0  # a resolver with no cached entry recurses and sees the new value at once
        )
    return ttl_seconds


def to_human(seconds: int) -> str:
    if seconds >= 3600:
        return str(seconds // 3600) + "h"
    if seconds >= 60:
        return str(seconds // 60) + "m"
    return str(seconds) + "s"


if __name__ == "__main__":
    for ttl in [60, 300, 3600, 86400]:
        window = worst_case_stale_seconds(ttl)
        print(
            "TTL "
            + to_human(ttl).rjust(4)
            + "  ->  worst-case stale window "
            + to_human(window)
        )

    # The lever in one line: lowering TTL the day BEFORE shrinks every future window.
    print(
        "\nPre-lowered to 60s before the change: worst case is "
        + to_human(worst_case_stale_seconds(60))
    )

The output shows the window is the TTL, full stop: a 24-hour TTL means a resolver that cached one second before the change keeps the old answer for nearly a full day, and nothing at the authoritative server can pull it back. That is why “just fix the record” is a sentence about the next change, not this one. The only TTL that helps is the one that was already in place before it was needed.

This clock is also the precondition for everything in the next section. A certificate authority cannot prove a name it cannot reach, and it reaches the name by resolving it. So the certificate cannot even start until this DNS clock finishes: the two clocks run in series, not in parallel.


Try It 1

Predict, before running, what this prints for each TTL: specifically, how long a resolver that cached the old answer one second before the change will keep serving it. Then fix the one line that is wrong so the function reports the worst-case stale window correctly.

python
"""Try It: report the worst-case stale window correctly.

Predict how long a resolver that cached the old answer one second before your
change will keep serving it, then fix the one line that is wrong.
"""


def worst_case_stale_seconds(ttl_seconds: int) -> int:
    # BUG: this returns 0, claiming a change is visible everywhere instantly.
    # A resolver that cached just before the change holds the OLD answer for how long?
    return 0  # <- replace with the correct expression


if __name__ == "__main__":
    for ttl in [60, 3600, 86400]:
        print(
            "TTL "
            + str(ttl)
            + "s -> worst-case stale window "
            + str(worst_case_stale_seconds(ttl))
            + "s"
        )
Hint Re-read "the TTL set before a change decides the size of the window." Nothing at the authoritative server can shorten an answer a resolver already cached, so what is the only quantity that bounds how long it survives? The fix is not a new calculation; it is returning the value the resolver was told to cache for.

Solution

The solution returns the TTL itself as the worst-case window, because nothing at the authoritative server can shorten an answer a resolver already cached. Watch the fix replace the wrong calculation with the one value the resolver was told to cache for, the TTL.

python
"""Solution: the worst-case stale window equals the cached TTL."""


def worst_case_stale_seconds(ttl_seconds: int) -> int:
    """A resolver that cached one second before the change serves the old
    answer for the full remaining TTL -- bounded only by the cached TTL."""
    return ttl_seconds


if __name__ == "__main__":
    for ttl in [60, 3600, 86400]:
        print(
            "TTL "
            + str(ttl)
            + "s -> worst-case stale window "
            + str(worst_case_stale_seconds(ttl))
            + "s"
        )

The worst-case window equals the TTL because the authoritative record being correct does not reach into a resolver that already cached the old value. This is why lowering the TTL the day before a change is the only move that shrinks the window: by the time the record changes, the TTL that matters is the one already cached everywhere.

A certificate is a provisioned artifact, not a configuration flag

The name resolves now, the page loads over plain HTTP, but over https:// the browser shows a hard red warning: “not secure,” “certificate invalid,” “your connection is not private.” The plausible hypothesis is that TLS is misconfigured: a setting is wrong, a port is closed, the image needs to be re-pushed with the right flag. So the reflex is to re-deploy. Nothing changes, because the deploy was never the problem.

Here is the diagnostic that distinguishes the two cases, run while the warning is showing:

# Re-pushing the image answers none of these. Ask the connection what it sees:
$ curl -v https://predict.myproduct.com 2>&1 | grep -iE "issuer|subject|expire|verify"
*  subject: CN=svc-abc.<platform-domain>  # cert is for the HOST's default name...
*  issuer: CN=svc-abc.<platform-domain>   # ...self-signed, chains to nothing trusted
*  SSL certificate verify failed         # the browser refuses because of THIS line

The certificate the server presents is for the host’s default name and signed by itself; it does not exist for the custom name yet, so it chains to nothing the browser trusts. Re-pushing the image would not change a single line of that output, because the image does not control whether a certificate has been issued. The mental shift is from “configure HTTPS” to “provision a certificate”: https:// is not a toggle, it is a signed artifact that has to be issued for one exact name, and issuance takes time and has a precondition.

https:// works only when the server can present a TLS certificate, a signed statement that “the holder of this key pair controls predict.myproduct.com,” signed by a certificate authority (CA) whose own signing certificate is already in the browser’s trust store. When the browser connects, the server presents this certificate plus the chain of intermediate certificates up to a trusted root. The browser checks that the signature chain is valid, that the certificate’s name matches the domain, and that it has not expired, and only then uses the certificate’s public key to set up the encrypted session. The hard warning fires when any of those checks fails. Right after a deploy the most common cause is not a bad setting. It is that no certificate has been issued for the name yet, so the host serves a default or self-signed certificate that chains to nothing trusted.

That precondition, namely that issuance takes time and cannot start until DNS resolves, is the entire reason the first https:// load fails. Automated issuance runs over ACME, a challenge-response protocol where the CA refuses to sign a certificate for a name until control is proven, and the proof is the CA reaching the name over the public internet. There are two ways to answer the challenge, and which one the host uses changes exactly what must already be working.

HTTP-01 challenge (serve a token at a URL) When: the host can serve content at http://predict.myproduct.com/.well-known/acme-challenge/<token>, the default for most managed platforms with a single public hostname. Failure modes: requires the name to already resolve to the host over the public internet with port 80 reachable. While DNS is still propagating, the CA cannot fetch the token, so validation fails and retries. This is the cert-error window in the opening incident. Cannot validate wildcards (*.myproduct.com).

DNS-01 challenge (publish a TXT record) When: a wildcard certificate is needed, or the host cannot serve HTTP for the name; the ACME client publishes a TXT record at _acme-challenge.<name> that the CA then queries. Failure modes: requires DNS to be live and propagated for the CA’s query to see the TXT record, so it inherits the entire propagation delay from the previous section, plus the negative-cache trap if the CA’s resolver cached a miss. A stale TTL on the TXT record can make validation flap between attempts.

Both paths make the same demand: DNS must already resolve before validation can succeed. That is why the two clocks chain in series. The certificate clock cannot start until the DNS clock finishes, because the CA reaches the name by resolving it. “I created the domain and enabled HTTPS in the same minute” reliably produces a certificate error for the first several minutes precisely because validation fires while DNS is still propagating, fails, and retries on a backoff. The scrolly below traces that ordering: step 2 must fail before step 3 can complete.

The DNS record is created and still propagating

The clock from the previous section is still running. The record exists at the authoritative nameserver, but resolvers have not all picked it up; some still serve a cached miss. This is the state in the minute HTTPS is enabled.

ACME validation fires and fails: the CA cannot reach the name yet

Issuance triggers immediately. The CA tries to fetch the HTTP-01 token at the name (or query the DNS-01 TXT record), but the name does not resolve for the CA’s resolver yet. Validation fails. This failure is not a misconfiguration; it is the certificate clock starting before the DNS clock has finished.

DNS finishes propagating

Resolvers that mattered expire their old entries and re-fetch. Now the name resolves to the host from the public internet, including from the CA’s resolver. The precondition for validation is finally true.

ACME retries and validation succeeds

On its backoff, the ACME client retries. This time the CA reaches the name, fetches the HTTP-01 token (or reads the DNS-01 TXT record), and confirms control of the domain. The proof-of-control demand is satisfied.

The CA signs the certificate: status goes provisioning to active

Now the CA signs a certificate for the exact name, chaining to a trusted root, and returns it to the host. The managed certificate’s status moves from provisioning to active. Only now does a certificate for the name exist anywhere.

The browser completes the handshake and https:// loads green

A browser connects, the server presents the new certificate and its chain, and the browser validates the chain, the name match, and the expiry. The warning is gone. The two clocks have run in series, and the second only finished because the first did.

When the warning shows, the diagnostic move is to stop re-deploying and instead check, in order: does the name resolve (dig)? Is the managed certificate’s status provisioning or active? What does curl -v https://... report for the issuer and the chain? The answer to those three names which clock is the one being waited on. Re-pushing the image answers none of them, which is exactly why the hour in the opening incident was wasted.

A second failure does not arrive for weeks. Certificates expire, and a managed certificate that worked on deploy day can take a working site fully down months later if nothing renews it. Automated issuers renew by running the same challenge again, which means renewal silently inherits the same precondition. If the domain later stops resolving (a registrar lapse, a moved record, a deleted zone), auto-renewal fails the exact way first issuance would, and the site goes dark the day the old certificate lapses. Because that failure arrives long after the deploy that “worked,” it is easy to misattribute to a recent change when the real cause is something that stopped being maintained.

The numbers make the runway concrete. Let’s Encrypt certificates have a default validity of 90 days, and the recommendation is to renew every 60 days, leaving a 30-day window for a failed renewal to retry before the old certificate lapses. On a one-person product there is no platform team quietly handling this. “Nothing renewed it” means the owner. The block below computes days until expiry from a certificate’s notAfter date and classifies the state, so a renewal failure has runway instead of a customer screenshot as its first alert.

python
"""Compute days until expiry and classify renewal state against 90/60-day guidance."""

from datetime import date


def days_until_expiry(not_after: date, today: date) -> int:
    """Days remaining on a cert. curl -v reports notAfter; this is the runway."""
    return (not_after - today).days


def renewal_state(
    days_left: int, validity_days: int = 90, renew_every: int = 60
) -> str:
    """Classify against Let's Encrypt's 90-day validity / 60-day renew guidance.
    The 30-day gap between renew-by and expiry is the retry runway."""
    renew_by_days_left = validity_days - renew_every  # 90 - 60 = 30 days of runway
    if days_left <= 0:
        return "EXPIRED -- site is down until reissued"
    if days_left <= renew_by_days_left:
        return "RENEW NOW -- inside the runway; a failed renewal can still retry"
    return "healthy"


if __name__ == "__main__":
    not_after = date(2026, 5, 30)  # issued 2026-03-01, valid 90 days
    for d in [date(2026, 3, 15), date(2026, 5, 5), date(2026, 6, 2)]:
        left = days_until_expiry(not_after, d)
        print(
            str(d) + ": " + str(left).rjust(3) + " days left -> " + renewal_state(left)
        )

The classification shows why the 30-day gap exists: a renewal attempted at the 60-day mark that fails has the remaining 30 days to retry before the certificate actually expires. Remove that gap, renewing on the last day, and a single failed renewal takes the site down with no runway. The discipline is to confirm auto-renewal is actually configured and to know the validity window, so a failed renewal is an alert with weeks of slack, not an outage discovered from a customer’s screenshot.


Try It 2

Monitoring needs one function that turns a certificate’s notAfter date into an actionable state. Fill in the body so it returns "DOWN" if the certificate has already expired, "RENEW" if it is inside the 30-day runway (60-day renewal on a 90-day certificate), and "OK" otherwise. The starter returns a placeholder so it runs without crashing.

python
"""Try It: turn a cert's notAfter date into an actionable monitoring state.

Return "DOWN" if the cert already expired, "RENEW" if it is inside the 30-day
runway (60-day renewal on a 90-day cert), and "OK" otherwise.
"""

from datetime import date


def cert_state(not_after: date, today: date) -> str:
    days_left = (not_after - today).days
    # Fill in the three cases. Runway = validity (90) - renew_every (60) = 30 days.
    return "OK"  # placeholder so this runs; replace with the real branching


if __name__ == "__main__":
    print(
        cert_state(date(2026, 5, 30), date(2026, 6, 2))
    )  # past notAfter -> expect DOWN
    print(
        cert_state(date(2026, 5, 30), date(2026, 5, 10))
    )  # 20 days left  -> expect RENEW
    print(
        cert_state(date(2026, 5, 30), date(2026, 3, 20))
    )  # 71 days left  -> expect OK
Hint Re-read the renewal section: there are exactly three states and they are ordered by how many days remain. Check the worst case first (already expired), then the runway window, then the healthy case. The runway boundary is the validity minus the renew-every interval; that number is stated in the paragraph above the worked example.

Solution

The solution checks the states in order of severity: already expired, then inside the 30-day runway, then healthy. Watch the runway boundary fall out of validity minus the renew-every interval, the 90-minus-60 figure from the section above.

python
"""Solution: classify cert state by days remaining against the renewal runway."""

from datetime import date


def cert_state(
    not_after: date, today: date, validity_days: int = 90, renew_every: int = 60
) -> str:
    days_left = (not_after - today).days
    runway = validity_days - renew_every  # 30-day window to retry a failed renewal
    if days_left <= 0:
        return "DOWN"
    if days_left <= runway:
        return "RENEW"
    return "OK"


if __name__ == "__main__":
    print(cert_state(date(2026, 5, 30), date(2026, 6, 2)))  # DOWN
    print(cert_state(date(2026, 5, 30), date(2026, 5, 10)))  # RENEW
    print(cert_state(date(2026, 5, 30), date(2026, 3, 20)))  # OK

The three states map directly to the runway: DOWN is the outage to never reach, RENEW is the 30-day window where a failed attempt still has time to retry, and OK is everything before that. Wiring this into monitoring turns the weeks-later renewal failure into an alert with slack, which is the difference between a quiet fix and a customer-reported outage.


Summary

  • A DNS record is not a global assignment; it is an answer that independent recursive resolvers each fetch and expire on their own clock. “Propagation” is every cache noticing its old answer expired and re-asking; nothing pushes the change anywhere.
  • The TTL is the only lever over how long a stale answer survives, and it is a response curve: low means fast change and high query load, high means cache offload and a mistake baked in for up to the full TTL. Lower it before a planned change; nothing shortens an answer a resolver already cached.
  • Negative caching is the trap behind “site can’t be reached” after the record is correct: a resolver that cached an NXDOMAIN keeps serving it on an SOA-governed timer independent of the record’s TTL.
  • https:// is a provisioned certificate, not a toggle. The CA proves control of the name by reaching it, so the certificate clock cannot start until DNS resolves: the two clocks run in series.
  • Auto-renewal re-runs the same challenge, so a domain that stops resolving fails renewal the same way first issuance would. The 90-day validity with 60-day renewal exists to leave a 30-day runway for a failed renewal to retry.

Check your understanding:

  • Two networks resolve the same fresh record differently at the same instant. Without re-reading: what is the one action that fixes it, and why does re-creating the record not?
  • You must move a record tomorrow. What do you do to the TTL today, and what does the high TTL currently in place cost you if you skip that step?
  • The page loads over HTTP but https:// shows a certificate warning right after deploy. What are the three things you check, in order, and why is re-pushing the image not one of them?
  • A site that worked for months suddenly shows a certificate error. What changed, and why did the failure arrive now rather than at deploy time?

This lesson is part of Pro

The Ship a Machine Learning Product path — every lesson, capstone, and the failure modes free tutorials skip. Sign in if you already have Pro, or unlock it below.

Unlock with Pro Sign in