Diagnostic Domain Lifecycle

Domain nearing expiration without renewal action

Every domain has a hard expiration date sitting quietly at the registry. Most of the time auto-renew handles it and nobody thinks about it again. Then one day auto-renew is off, the card on file expired months ago, or the renewal reminder emails went to an inbox nobody reads, and the domain drifts through its 30 or 60 day warning window with no action taken. If it lapses, the registry deletes it and the site, DNS, and email for that domain all go dark at the same time.

Python and Node.js RDAP expiry check + alert Safe by default (dry run)
Diagram, venn diagram
Photo by Planet Volumes on Unsplash
The short answer

A domain's expiration date lives at the registry, not in any DNS record, so it cannot be fixed by editing A, MX, or TXT records. It gets missed when auto-renew is off, the payment method on file has expired, or the registrar's renewal reminder emails go to an address nobody actively reads. Check the real expiration date with RDAP, the modern replacement for WHOIS, and if it is inside your warning window, log into the registrar and renew now or turn on auto-renew with a valid card. Full code, tests, and the check are below.

The problem in plain words

Every domain name has a hard expiration date set at the registry, the authoritative database that tracks who owns a domain and until when. When a domain is registered or renewed, that date moves forward by a year or more, up to a maximum registration length ICANN sets at ten years. Nothing about your website or DNS zone changes when that clock is running out. The domain simply keeps working, right up until the moment it does not.

The gap that causes trouble is that nobody is watching that expiration date on a schedule. Auto-renew was supposed to handle it, but auto-renew silently fails when the payment method on file has expired, or it was never turned on in the first place. ICANN requires registrars to send renewal reminder emails, roughly one month and one week before expiration, but those emails go to whatever registrant or admin contact address was set when the domain was first registered, which is often stale, filtered as spam, or simply not checked by anyone anymore. The domain drifts inside the 30 or 60 day warning window with no renewal ever triggered, and if it lapses, the registry deletes it and the site, DNS, and email for that domain all go dark at once.

Expiry approaches 30-60 day window Auto-renew off or card expired reminder emails unread Domain lapses, registry deletes it Site, DNS, mail all go dark nobody is checking the real expiration date on a schedule
Nothing in the DNS zone changes as the deadline nears. The domain works fine right up until the registry actually deletes it.

Why it happens

This is a registrar and registry billing and account state, not a DNS zone record, so it cannot be fixed by editing A, MX, or TXT records. A few common reasons a domain drifts toward expiration unnoticed:

Once the registration date passes with no renewal, the registry marks the domain as expired and it can move through an Auto-Renew Grace Period and then a Redemption Grace Period before actual deletion, at which point it becomes available for anyone else to register.

The key insight

The DNS zone has nothing to do with this problem. A domain can have perfect A, MX, and TXT records right up until the day it is deleted at the registry, because expiration is an account and billing state above the zone, not inside it. The only reliable source of truth is the expiration date returned by RDAP or WHOIS, checked on a schedule, not whatever a reminder email inbox happens to catch.

The fix, as a flow

Do not wait for a reminder email to arrive. Query RDAP for the real expiration date on a schedule, compare it against a warning threshold, and when the domain crosses into the warning window, act at the registrar directly: renew now or confirm auto-renew is on with a valid payment method. If the domain already lapsed, use the registrar's restore flow immediately, since a later restore from the Redemption Grace Period usually costs an extra fee.

Query RDAP on a schedule Compute days remaining until expiry Classify severity 30 / 14 / 7 day thresholds Inside a threshold? yes no, all clear Alert + renew at the registrar
Detection can run on a schedule and alert automatically. The actual renewal always happens at the registrar, since it is a billing action, not a DNS record change.

How to fix it

1

Check the real expiration date with RDAP

RDAP is the modern, ICANN-mandated replacement for WHOIS as of January 2025. It follows the bootstrap redirect to the authoritative registry server, for example rdap.verisign.com for .com, and returns a clean, structured date. A bad result is an expiration date less than your warning threshold, commonly 30 or 60 days, away from today.

Terminal
check-rdap-expiry.sh
curl -s https://rdap.org/domain/example.com \
  | jq -r '.events[] | select(.eventAction=="expiration") | .eventDate'

# Example good output:
# 2027-08-05T04:00:00Z
2

Fall back to legacy WHOIS if RDAP is unavailable

Some registries still answer WHOIS more reliably than RDAP in certain client setups. Look for the Registry Expiry Date line. A bad result is the same as above, a date inside your warning window.

Terminal
check-whois-expiry.sh
whois example.com | grep -i "Registry Expiry Date"

# Example good output:
# Registry Expiry Date: 2027-08-05T04:00:00Z
3

Check whether the domain already lapsed into a grace period

Also check the domain status field. A bad result is pendingDelete, redemptionPeriod, or autoRenewPeriod, which signal the domain is already past expiration and inside the roughly 0 to 45 day Auto-Renew Grace Period or the 30 day Redemption Grace Period.

Terminal
check-domain-status.sh
whois example.com | grep -i "Domain Status"

# Concerning statuses:
# Domain Status: autoRenewPeriod
# Domain Status: redemptionPeriod
# Domain Status: pendingDelete
4

Turn on auto-renew or renew now at the registrar

There is no DNS record to change here. Log into the registrar account, for example Cloudflare Registrar, Namecheap, or GoDaddy, and turn on auto-renew for the domain, or manually click Renew now and pay for the extension. Registrations typically renew a year at a time, often capped at ten years total per ICANN rules.

5

Confirm the payment method on file is valid

An expired card is the most common reason auto-renew silently fails. Update the payment method in the registrar account so future renewals actually go through instead of failing quietly again.

6

Restore the domain immediately if it already lapsed

If the domain is inside the Auto-Renew Grace Period, typically 0 to 45 days post-expiry depending on the registrar, or the 30 day Redemption Grace Period after registry deletion, use the registrar's restore flow right away. A Redemption Grace Period restore usually carries an extra redemption fee on top of the normal renewal fee, and the longer you wait the more likely the domain is released to anyone else once it reaches pendingDelete.

7

Point the contact email at an address someone actually reads

As a safety net going forward, set the registrant and admin contact email on the registrar account to an address that is actively monitored. ICANN requires registrars to send reminders roughly one month and one week before expiration, plus a post-expiration notice if the registration is deleted, and those reminders are worthless if they land in an inbox nobody checks.

How to check it worked

Re-run the RDAP check and confirm the expiration event date has moved out beyond your warning threshold and the domain status no longer shows a pending or grace period state.

Terminal
verify.sh
curl -s https://rdap.org/domain/example.com \
  | jq -r '.events[] | select(.eventAction=="expiration") | .eventDate'

curl -s https://rdap.org/domain/example.com | jq -r '.status'

whois example.com | grep -iE "Expiry|Status"

dig +short example.com
dig +short MX example.com

A good result looks like this: the expiration event date has jumped roughly one year or more further out, for example from 2026-08-05 to 2027-08-05, and the status array contains active with no pendingDelete, redemptionPeriod, or autoRenewPeriod flags. The dig commands still return the expected A and MX records, proving the domain was never actually pulled from the zone.

The full code

This one automates detection end to end. The script queries RDAP for the domain's expiration event, computes days remaining with a pure, I/O-free function, classifies the severity against a set of thresholds, and logs or alerts when the domain crosses one. There is no Cloudflare zone DNS API call in the repair path, since renewal is a registrar and billing action, not a DNS record change, and even Cloudflare's own Registrar API does not expose a renew endpoint as of 2025-2026, only an auto_renew toggle for domains registered through Cloudflare Registrar itself.

View this code on GitHub Full runnable folder with tests in the dns-fixes repo.

check_domain_expiry.py
"""Check a domain's real expiration date over RDAP and alert when it crosses
a warning threshold. Renewal itself always happens at the registrar, since
this is a billing state, not a DNS zone record. DRY_RUN only reports until
turned off, and even then this script only sends alerts, it never renews
anything on your behalf.
"""
import os
import logging
from datetime import datetime, timezone

logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s %(message)s")
log = logging.getLogger("check_domain_expiry")

DNS_DOMAIN = os.environ.get("DNS_DOMAIN", "example.com")
CLOUDFLARE_API_TOKEN = os.environ.get("CLOUDFLARE_API_TOKEN", "")
CLOUDFLARE_ZONE_ID = os.environ.get("CLOUDFLARE_ZONE_ID", "")
DRY_RUN = os.environ.get("DRY_RUN", "true").lower() == "true"
WARN_THRESHOLDS_DAYS = [30, 14, 7, 1]


def days_until_expiry(expiration_iso: str, now_iso: str, warn_thresholds_days: list = None) -> dict:
    """
    Pure decision logic, no I/O.
    Input: expiration_iso (RDAP eventDate string, e.g. '2026-08-05T04:00:00Z'),
           now_iso (current time as ISO string, injected for testability),
           warn_thresholds_days (sorted descending list of alert thresholds).
    Output: {
        'days_remaining': int,
        'severity': 'ok' | 'warning' | 'critical' | 'expired',
        'triggered_threshold': int | None  # smallest threshold crossed, or None
    }
    """
    if warn_thresholds_days is None:
        warn_thresholds_days = [30, 14, 7, 1]

    expiration = datetime.fromisoformat(expiration_iso.replace("Z", "+00:00")).astimezone(timezone.utc)
    now = datetime.fromisoformat(now_iso.replace("Z", "+00:00")).astimezone(timezone.utc)
    days_remaining = int((expiration - now).total_seconds() // 86400)

    if days_remaining < 0:
        return {"days_remaining": days_remaining, "severity": "expired", "triggered_threshold": None}

    candidates = [t for t in warn_thresholds_days if t >= days_remaining]
    triggered_threshold = min(candidates) if candidates else None

    if triggered_threshold is None:
        severity = "ok"
    elif triggered_threshold <= 7:
        severity = "critical"
    else:
        severity = "warning"

    return {
        "days_remaining": days_remaining,
        "severity": severity,
        "triggered_threshold": triggered_threshold,
    }


def fetch_rdap_expiration(domain: str) -> str:
    """Query RDAP for a domain and return the expiration eventDate string."""
    import requests

    r = requests.get(f"https://rdap.org/domain/{domain}", timeout=30)
    r.raise_for_status()
    data = r.json()
    for event in data.get("events", []):
        if event.get("eventAction") == "expiration":
            return event["eventDate"]
    raise ValueError(f"No expiration event found in RDAP response for {domain}")


def fetch_rdap_status(domain: str) -> list:
    """Query RDAP for a domain and return its status list."""
    import requests

    r = requests.get(f"https://rdap.org/domain/{domain}", timeout=30)
    r.raise_for_status()
    return r.json().get("status", [])


def send_alert(domain: str, result: dict, statuses: list) -> None:
    """Send an alert. Replace with email, Slack, or a webhook call in production."""
    if DRY_RUN:
        log.info(
            "[dry run] would alert: %s is %s, %d day(s) remaining, statuses=%s",
            domain, result["severity"], result["days_remaining"], statuses,
        )
        return
    log.warning(
        "ALERT: %s is %s, %d day(s) remaining, statuses=%s. Renew at the registrar, "
        "this cannot be fixed through the DNS provider API.",
        domain, result["severity"], result["days_remaining"], statuses,
    )


def run():
    log.info("Checking domain expiration for %s (DRY_RUN=%s)", DNS_DOMAIN, DRY_RUN)

    expiration_iso = fetch_rdap_expiration(DNS_DOMAIN)
    statuses = fetch_rdap_status(DNS_DOMAIN)
    now_iso = datetime.now(timezone.utc).isoformat()

    result = days_until_expiry(expiration_iso, now_iso, WARN_THRESHOLDS_DAYS)
    log.info(
        "%s expires %s: %d day(s) remaining, severity=%s",
        DNS_DOMAIN, expiration_iso, result["days_remaining"], result["severity"],
    )

    grace_flags = {"pendingdelete", "redemptionperiod", "autorenewperiod"}
    lowered_statuses = [s.lower() for s in statuses]
    if any(flag in lowered_statuses for flag in grace_flags):
        log.warning("%s is already in a grace or pending-delete state: %s", DNS_DOMAIN, statuses)
        send_alert(DNS_DOMAIN, result, statuses)
        return

    if result["severity"] in ("warning", "critical", "expired"):
        send_alert(DNS_DOMAIN, result, statuses)
    else:
        log.info("OK: %s has plenty of runway left before renewal is needed.", DNS_DOMAIN)


if __name__ == "__main__":
    run()
check-domain-expiry.js
/**
 * Check a domain's real expiration date over RDAP and alert when it crosses
 * a warning threshold. Renewal itself always happens at the registrar, since
 * this is a billing state, not a DNS zone record. DRY_RUN only reports until
 * turned off, and even then this script only sends alerts, it never renews
 * anything on your behalf.
 */
import { pathToFileURL } from "node:url";

const DNS_DOMAIN = process.env.DNS_DOMAIN || "example.com";
const CLOUDFLARE_API_TOKEN = process.env.CLOUDFLARE_API_TOKEN || "";
const CLOUDFLARE_ZONE_ID = process.env.CLOUDFLARE_ZONE_ID || "";
const DRY_RUN = (process.env.DRY_RUN || "true").toLowerCase() === "true";
const WARN_THRESHOLDS_DAYS = [30, 14, 7, 1];

/**
 * Pure decision logic, no I/O.
 * Input: expirationIso (RDAP eventDate string, e.g. '2026-08-05T04:00:00Z'),
 *        nowIso (current time as ISO string, injected for testability),
 *        warnThresholdsDays (sorted descending array of alert thresholds).
 * Output: {
 *   daysRemaining: number,
 *   severity: 'ok' | 'warning' | 'critical' | 'expired',
 *   triggeredThreshold: number | null
 * }
 */
export function daysUntilExpiry(expirationIso, nowIso, warnThresholdsDays = [30, 14, 7, 1]) {
  const expiration = new Date(expirationIso);
  const now = new Date(nowIso);
  const daysRemaining = Math.floor((expiration.getTime() - now.getTime()) / 86400000);

  if (daysRemaining < 0) {
    return { daysRemaining, severity: "expired", triggeredThreshold: null };
  }

  const candidates = warnThresholdsDays.filter((t) => t >= daysRemaining);
  const triggeredThreshold = candidates.length > 0 ? Math.min(...candidates) : null;

  let severity;
  if (triggeredThreshold === null) {
    severity = "ok";
  } else if (triggeredThreshold <= 7) {
    severity = "critical";
  } else {
    severity = "warning";
  }

  return { daysRemaining, severity, triggeredThreshold };
}

/** Query RDAP for a domain and return the expiration eventDate string. */
async function fetchRdapExpiration(domain) {
  const res = await fetch(`https://rdap.org/domain/${domain}`);
  if (!res.ok) throw new Error(`RDAP returned ${res.status} for ${domain}`);
  const data = await res.json();
  const event = (data.events || []).find((e) => e.eventAction === "expiration");
  if (!event) throw new Error(`No expiration event found in RDAP response for ${domain}`);
  return event.eventDate;
}

/** Query RDAP for a domain and return its status list. */
async function fetchRdapStatus(domain) {
  const res = await fetch(`https://rdap.org/domain/${domain}`);
  if (!res.ok) throw new Error(`RDAP returned ${res.status} for ${domain}`);
  const data = await res.json();
  return data.status || [];
}

/** Send an alert. Replace with email, Slack, or a webhook call in production. */
function sendAlert(domain, result, statuses) {
  if (DRY_RUN) {
    console.log(
      `[dry run] would alert: ${domain} is ${result.severity}, ${result.daysRemaining} day(s) remaining, statuses=${JSON.stringify(statuses)}`
    );
    return;
  }
  console.warn(
    `ALERT: ${domain} is ${result.severity}, ${result.daysRemaining} day(s) remaining, statuses=${JSON.stringify(statuses)}. ` +
    "Renew at the registrar, this cannot be fixed through the DNS provider API."
  );
}

export async function run() {
  console.log(`Checking domain expiration for ${DNS_DOMAIN} (DRY_RUN=${DRY_RUN})`);

  const expirationIso = await fetchRdapExpiration(DNS_DOMAIN);
  const statuses = await fetchRdapStatus(DNS_DOMAIN);
  const nowIso = new Date().toISOString();

  const result = daysUntilExpiry(expirationIso, nowIso, WARN_THRESHOLDS_DAYS);
  console.log(
    `${DNS_DOMAIN} expires ${expirationIso}: ${result.daysRemaining} day(s) remaining, severity=${result.severity}`
  );

  const graceFlags = new Set(["pendingdelete", "redemptionperiod", "autorenewperiod"]);
  const loweredStatuses = statuses.map((s) => s.toLowerCase());
  if (loweredStatuses.some((s) => graceFlags.has(s))) {
    console.warn(`${DNS_DOMAIN} is already in a grace or pending-delete state: ${JSON.stringify(statuses)}`);
    sendAlert(DNS_DOMAIN, result, statuses);
    return;
  }

  if (["warning", "critical", "expired"].includes(result.severity)) {
    sendAlert(DNS_DOMAIN, result, statuses);
  } else {
    console.log(`OK: ${DNS_DOMAIN} has plenty of runway left before renewal is needed.`);
  }
}

if (import.meta.url === pathToFileURL(process.argv[1]).href) {
  run().catch((e) => { console.error(e); process.exit(1); });
}

Add a test

The part worth testing is the pure decision logic: how many days remain, and what severity that maps to against the threshold list. The function takes plain ISO strings in and returns a plain object, so the tests use fixed timestamps and need no network access.

test_domain_expiry.py
from check_domain_expiry import days_until_expiry


def test_ok_when_plenty_of_runway():
    result = days_until_expiry("2026-09-15T00:00:00Z", "2026-08-01T00:00:00Z")
    assert result["severity"] == "ok"
    assert result["triggered_threshold"] is None
    assert result["days_remaining"] == 45


def test_warning_at_thirty_day_boundary():
    result = days_until_expiry("2026-08-31T00:00:00Z", "2026-08-01T00:00:00Z")
    assert result["days_remaining"] == 30
    assert result["severity"] == "warning"
    assert result["triggered_threshold"] == 30


def test_warning_inside_fourteen_day_window():
    result = days_until_expiry("2026-08-10T00:00:00Z", "2026-08-01T00:00:00Z")
    assert result["days_remaining"] == 9
    assert result["severity"] == "warning"
    assert result["triggered_threshold"] == 14


def test_critical_at_seven_day_boundary():
    result = days_until_expiry("2026-08-08T00:00:00Z", "2026-08-01T00:00:00Z")
    assert result["days_remaining"] == 7
    assert result["severity"] == "critical"
    assert result["triggered_threshold"] == 7


def test_critical_one_day_left():
    result = days_until_expiry("2026-08-02T00:00:00Z", "2026-08-01T00:00:00Z")
    assert result["days_remaining"] == 1
    assert result["severity"] == "critical"
    assert result["triggered_threshold"] == 1


def test_expired_when_negative():
    result = days_until_expiry("2026-07-25T00:00:00Z", "2026-08-01T00:00:00Z")
    assert result["days_remaining"] == -7
    assert result["severity"] == "expired"
    assert result["triggered_threshold"] is None


def test_custom_thresholds():
    result = days_until_expiry(
        "2026-08-21T00:00:00Z", "2026-08-01T00:00:00Z", warn_thresholds_days=[60, 20, 5]
    )
    assert result["days_remaining"] == 20
    assert result["severity"] == "warning"
    assert result["triggered_threshold"] == 20
domain-expiry.test.js
import { test } from "node:test";
import assert from "node:assert/strict";
import { daysUntilExpiry } from "./check-domain-expiry.js";

test("ok when plenty of runway", () => {
  const result = daysUntilExpiry("2026-09-15T00:00:00Z", "2026-08-01T00:00:00Z");
  assert.equal(result.severity, "ok");
  assert.equal(result.triggeredThreshold, null);
  assert.equal(result.daysRemaining, 45);
});

test("warning at thirty day boundary", () => {
  const result = daysUntilExpiry("2026-08-31T00:00:00Z", "2026-08-01T00:00:00Z");
  assert.equal(result.daysRemaining, 30);
  assert.equal(result.severity, "warning");
  assert.equal(result.triggeredThreshold, 30);
});

test("warning inside fourteen day window", () => {
  const result = daysUntilExpiry("2026-08-10T00:00:00Z", "2026-08-01T00:00:00Z");
  assert.equal(result.daysRemaining, 9);
  assert.equal(result.severity, "warning");
  assert.equal(result.triggeredThreshold, 14);
});

test("critical at seven day boundary", () => {
  const result = daysUntilExpiry("2026-08-08T00:00:00Z", "2026-08-01T00:00:00Z");
  assert.equal(result.daysRemaining, 7);
  assert.equal(result.severity, "critical");
  assert.equal(result.triggeredThreshold, 7);
});

test("critical one day left", () => {
  const result = daysUntilExpiry("2026-08-02T00:00:00Z", "2026-08-01T00:00:00Z");
  assert.equal(result.daysRemaining, 1);
  assert.equal(result.severity, "critical");
  assert.equal(result.triggeredThreshold, 1);
});

test("expired when negative", () => {
  const result = daysUntilExpiry("2026-07-25T00:00:00Z", "2026-08-01T00:00:00Z");
  assert.equal(result.daysRemaining, -7);
  assert.equal(result.severity, "expired");
  assert.equal(result.triggeredThreshold, null);
});

test("custom thresholds", () => {
  const result = daysUntilExpiry("2026-08-21T00:00:00Z", "2026-08-01T00:00:00Z", [60, 20, 5]);
  assert.equal(result.daysRemaining, 20);
  assert.equal(result.severity, "warning");
  assert.equal(result.triggeredThreshold, 20);
});

Case studies

Stale contact email

The domain registered by an employee who left three years ago

A small business's main domain was originally registered under a former employee's personal email address. Every renewal reminder for two years landed in an inbox nobody at the company could access. Auto-renew had been on, but the card tied to it had expired the previous year, so it failed silently every time.

A routine RDAP check as part of an unrelated migration turned up an expiration date eleven days out. The team logged into the registrar, reset the account recovery email to a shared team address, updated the card, and renewed manually with days to spare.

Redemption period

The domain that actually lapsed over a holiday week

A side project's domain expired while its owner was traveling. Auto-renew was never enabled, and the one reminder email that would have caught it was buried under vacation-mode inbox noise. By the time anyone noticed, the site was down and dig returned nothing for the domain.

Whois showed status redemptionPeriod. The registrar's restore flow brought the domain back within a day, for the renewal fee plus a redemption fee, and DNS and mail resumed working as soon as the restore completed, since the zone records had never actually been touched.

What good looks like

Once a scheduled RDAP check is watching the real expiration date, a domain lapsing becomes something that gets caught weeks in advance instead of discovered the day the site goes dark. Keep auto-renew on with a valid payment method as the primary defense, and treat the scheduled check as the backup that catches it when auto-renew quietly fails.

FAQ

Why is my domain about to expire even though I never got a warning?

Registrars are required to send renewal reminder emails, but they usually go to whatever contact address was set years ago, which may be unmonitored, filtered as spam, or simply the wrong inbox. Nobody is checking the actual expiration date at the registry on a schedule, so the domain drifts through its warning window unnoticed.

Can I fix an expiring domain by editing DNS records?

No. Expiration is a registrar and registry billing state, not a DNS zone record. There is no A, MX, or TXT record to change. You have to log into the registrar account and renew the domain, or turn on auto-renew and fix the payment method on file.

What happens if the domain actually expires and I do nothing?

The domain first enters the Auto-Renew Grace Period, typically 0 to 45 days depending on the registrar, where the site, DNS, and email may already be down but the registrar can often still restore it for the normal renewal price. After that it enters a 30 day Redemption Grace Period at the registry, where restoring it usually costs an extra redemption fee on top of renewal.

Related field notes

Citations

On the problem:

  1. ICANN: FAQs for Registrants, Domain Name Renewals and Expiration. icann.org/resources/pages/domain-name-renewal-expiration-faqs
  2. ICANN: Auto-Renew Grace Period, Acronyms and Terms. icann.org/en/icann-acronyms-and-terms/auto-renew-grace-period-en
  3. RFC 9083, JSON Responses for the Registration Data Access Protocol (RDAP). datatracker.ietf.org/doc/rfc9083

On the solution:

  1. ICANN: 5 Things every Registrant Should Know about ERRP. icann.org/resources/pages/registrant-about-errp
  2. RDAP.ORG, the bootstrap lookup service. about.rdap.org
  3. Cloudflare Registrar API documentation, Update Domain (auto_renew). developers.cloudflare.com/api/resources/registrar/subresources/domains/methods/update

Stuck on a tricky one?

If you have a DNS, domain, or registrar automation problem you would rather hand off, this is the kind of work I do. Message me and we can work through it together.

Contact me on LinkedIn

Did this save your domain from lapsing?

If this caught an expiring domain before it went dark, you can buy me a coffee. It is the best way to keep these field notes free and growing.

Buy me a coffee on Ko-fi

Back to all DNS and Domains field notes