2026-04-17·14 min read·

NIS2 Art.25: Domain Name Registration Data — WHOIS Obligations for TLD Registries and Registrars (2026)

Post #418 in the sota.io EU Cyber Compliance Series

When most developers think about NIS2 compliance, they focus on the obligations that apply broadly to essential and important entities: Art.21 risk management measures, Art.23 incident reporting, Art.24 registration with the NCA. But NIS2 also imposes a distinct, sector-specific obligation on one particular category of infrastructure operators: those who run or interact with the domain name system.

Art.25 of NIS2 targets TLD name registries and entities providing domain name registration services — the registrars, resellers, and registry operators who sit at the foundation of internet addressing. If you run a registrar, operate a TLD, provide DNS hosting, or build tooling that integrates with domain registration workflows, Art.25 is your primary NIS2 compliance text.

This guide covers:


NIS2 Chapter IV Context: Where Art.25 Fits

Art.25 sits in Chapter IV alongside the general security obligations, but it is addressed specifically to DNS-layer infrastructure — not to all NIS2-covered entities:

ArticleTopicWho it Applies To
Art.20Management body obligationsEssential + Important entities
Art.21Cybersecurity risk management measuresEssential + Important entities
Art.22Supply chain security assessmentENISA + Member States (coordinated)
Art.23Incident reportingEssential + Important entities
Art.24NCA registration obligationsEssential + Important entities
Art.25Domain name registration data (WHOIS)TLD registries + Registrars + DNS providers
Art.26Coordinated vulnerability disclosureEssential + Important entities
Art.27Entity status change notificationsEssential + Important entities

Art.25 entities must comply with both Art.21 security measures (they are likely essential or important entities under Annex I Section 8 — DNS service providers) and the Art.25 WHOIS-specific obligations. Art.25 is additive, not a replacement.


Art.25 Text: What the Directive Actually Requires

NIS2 Art.25 has six paragraphs. The full compliance picture:

Art.25(1) — The core obligation: Member States shall ensure that TLD name registries and entities providing domain name registration services collect and maintain accurate and complete domain name registration data in a dedicated database. This obligation is subject to Union data protection law where the data are personal data.

Art.25(2) — Minimum dataset: The database shall contain at least:

Art.25(3) — Verification: Member States shall ensure that TLD name registries and entities providing domain name registration services implement policies and procedures — including verification procedures — to ensure that the databases contain accurate and complete information. The verification must cover at minimum the registrant's email address. Other data elements (name, phone number) should be verified where technically feasible.

Art.25(4) — Public disclosure: Member States shall ensure that TLD name registries and entities providing domain name registration services make publicly available — without undue delay after registration — domain name registration data that are not personal data. In practice, this means: domain name, registration date, registrar name and contact, expiration date, and DNS records.

Art.25(5) — Access for legitimate seekers: Member States shall ensure that TLD name registries and entities providing domain name registration services provide access to specific domain name registration data upon the request of legitimate access seekers. Legitimate access seekers include:

Access shall be granted without undue delay and in any case within 72 hours for abuse-related requests. Registrars must implement a documented access policy and request logging.

Art.25(6) — Interconnected registries: Member States shall ensure that TLD registries and registrars cooperate with each other and with ENISA to ensure that the domain name registration data ecosystem is interoperable and accessible. This enables cross-border abuse handling.


Who Is Subject to Art.25?

Art.25 applies to two categories, defined in Annex I Section 8 of NIS2:

Category 1: TLD Name Registries

A TLD (Top-Level Domain) name registry operates the authoritative registry for a top-level domain:

These are essential entities under NIS2 Annex I Section 8. They are among the most critical internet infrastructure operators.

Category 2: Domain Name Registration Service Providers

This covers any entity that provides domain name registration to end customers, including:

The size thresholds (Art.3) still apply for classification as essential vs. important. Large registrars (>250 employees, >€50M turnover) are essential; mid-sized ones are important.

Does This Apply to Me?

My SaaS includes domain management features
         │
         ├─ Do I run a TLD? ──────────────────────────────────► YES → Essential entity, Art.25 applies fully
         │
         ├─ Do I register domains on behalf of customers? ───► YES → Registrar/Reseller, Art.25 applies
         │    (even via API to upstream registrar)
         │
         ├─ Do I provide authoritative DNS hosting? ──────────► MAYBE → Check if you are "providing domain
         │                                                              name registration services"
         │
         └─ I only consume DNS (resolve domains in my app) ──► NO → Art.25 does not apply to you

If you integrate with a registrar API (e.g., Cloudflare Registrar API, HEXONET, OpenSRS) to provision domains for your customers, you are likely a reseller subject to Art.25, even if your core product is not domain registration.


The WHOIS Dataset: What You Must Maintain

Art.25(2) defines the minimum dataset. In practice, a compliant WHOIS database for a registrar looks like this:

Mandatory Fields (Art.25(2))

FieldDescriptionPersonal Data?
domain_nameThe registered domain (e.g., example.de)No
registration_dateDate domain was registeredNo
expiration_dateDomain expiry dateNo
registrant_nameLegal name of registrantYES (natural persons)
registrant_emailPrimary contact emailYES
registrant_phoneContact telephone numberYES
registrar_nameName of registrar/resellerNo (if legal entity)
registrar_contactRegistrar contact detailsDepends

Extended Fields (Best Practice)

FieldDescription
technical_contact_nameTechnical contact for DNS administration
technical_contact_emailTechnical email
nameserversAuthoritative nameservers for the domain
dnssec_statusWhether DNSSEC is enabled
last_modifiedLast update timestamp
status_codesEPP status codes (clientTransferProhibited, etc.)
verification_statusResult of registrant verification
verification_dateWhen verification was completed

Art.25 × GDPR: The Core Compliance Tension

Art.25 creates a direct tension with GDPR. Registrant data — name, email, phone — is almost always personal data. Art.25(1) acknowledges this explicitly ("subject to Union law on data protection").

The Pre-GDPR Problem

Before GDPR, WHOIS databases were fully public: domain name, registrant name, email, address, phone — all freely accessible. This was standard practice for 30+ years. GDPR ended that model in 2018 because public disclosure of contact information without a legal basis violates Art.6 GDPR.

ICANN and European registrars scrambled. The interim solution was RDAP (Registration Data Access Protocol) with tiered access — public data for non-personal fields, gated access for personal data.

The NIS2 Solution (Art.25 + GDPR)

NIS2 Art.25(4) aligns with the post-GDPR WHOIS model by requiring public disclosure only of non-personal data. The personal data (registrant name, email, phone) is subject to the access-for-legitimate-seekers regime in Art.25(5).

This creates a compliant tiered model:

PUBLIC (Art.25(4)):                 GATED (Art.25(5)):
─────────────────────────           ──────────────────────────
domain_name                         registrant_name
registration_date                   registrant_email
expiration_date                     registrant_phone
registrar_name                      technical_contact_name
nameservers                         technical_contact_email
dnssec_status
status_codes
Processing ActivityLegal Basis
Collecting registrant dataArt.6(1)(b) GDPR — performance of contract
Maintaining WHOIS databaseArt.6(1)(c) GDPR — legal obligation (NIS2 Art.25)
Disclosing non-personal data publiclyNo legal basis needed (non-personal data)
Disclosing personal data to legitimate seekersArt.6(1)(c) GDPR — legal obligation via Art.25(5)
Retaining data after domain expiryArt.6(1)(f) GDPR — legitimate interest (abuse resolution)

Retention period for expired domain registration data: Most Member State implementations require retention for at least 2 years after expiry. Document this in your ROPA.


Verification Obligations (Art.25(3))

Art.25(3) requires verification procedures. At minimum:

Email Verification (Mandatory)

Within 15 days of registration, send a verification email to the registrant address. If no response within 15 days, suspend the domain (after warning). This mirrors ICANN's 2013 Registrar Accreditation Agreement (RAA) requirements but is now a NIS2 legal obligation for EU registrars.

Verification flow:
─────────────────
1. Domain registered → send verification email (t=0)
2. Registrant clicks link → mark email as verified
3. t=15d: no response → send reminder + 24h warning
4. t=16d: no response → suspend domain, notify registrant
5. Verification completed within 30d → restore
6. t=45d: still not verified → cancel (flag for review)

Name and Phone Verification (Best Effort)

For essential entity registrars, name verification is increasingly expected. Practical approaches:

Periodic Re-verification

For domains older than 12 months: send annual re-verification request. If registrant data has changed but was not updated (violating Art.25(3)), this catches drift.


Access for Legitimate Seekers (Art.25(5))

This is the operationally complex part of Art.25. You must have a documented process for handling WHOIS data access requests.

Who Counts as a Legitimate Access Seeker?

Seeker TypeExamplesResponse SLA
NCA / Law enforcementBSI, Bundeskriminalamt, Europol24 hours
CSIRT / CERTCERT-Bund, ENISA, national CERTs48 hours
Cybersecurity researchersAcademic researchers, OSINT analysts acting in public interest72 hours
Dispute resolution providersWIPO UDRP panelists72 hours
Intellectual property rights holdersTrademark attorneys (with documented IP claim)72 hours

Required Access Policy Elements

Your access policy must include:

  1. Identity verification: How you verify the requester's identity and legitimacy
  2. Purpose limitation: Access is granted only for the stated cybersecurity or legal purpose
  3. Data minimization: Provide only the fields relevant to the request
  4. Logging: Every access request and response must be logged (with timestamp, requester identity, fields disclosed)
  5. Abuse prevention: Rate limiting and detection of bulk-harvesting attempts
  6. Denial procedure: How to reject illegitimate requests with explanation

The 72-Hour Clock

Art.25(5) requires access "without undue delay." Member State implementations typically define this as 72 hours for standard requests. Law enforcement requests with documented urgency (e.g., active abuse, ransomware C2) should be handled within 24 hours.


Python: NIS2WhoisCompliance Implementation

import hashlib
import logging
import smtplib
from dataclasses import dataclass, field
from datetime import datetime, timedelta
from enum import Enum
from typing import Optional
import json

logger = logging.getLogger(__name__)


class VerificationStatus(Enum):
    PENDING = "pending"
    EMAIL_SENT = "email_sent"
    VERIFIED = "verified"
    SUSPENDED = "suspended"
    EXPIRED = "expired"


class RequesterType(Enum):
    NCA = "nca"
    LAW_ENFORCEMENT = "law_enforcement"
    CSIRT = "csirt"
    RESEARCHER = "researcher"
    DISPUTE_RESOLUTION = "dispute_resolution"
    IP_RIGHTS_HOLDER = "ip_rights_holder"
    UNKNOWN = "unknown"


@dataclass
class DomainRegistration:
    domain_name: str
    registration_date: datetime
    expiration_date: datetime
    registrant_name: str
    registrant_email: str
    registrant_phone: str
    registrar_name: str
    registrar_contact: str
    nameservers: list[str] = field(default_factory=list)
    dnssec_enabled: bool = False
    verification_status: VerificationStatus = VerificationStatus.PENDING
    verification_date: Optional[datetime] = None
    verification_token: Optional[str] = None
    last_modified: datetime = field(default_factory=datetime.utcnow)

    def public_whois(self) -> dict:
        """Art.25(4): non-personal data only — safe for public disclosure."""
        return {
            "domain_name": self.domain_name,
            "registration_date": self.registration_date.isoformat(),
            "expiration_date": self.expiration_date.isoformat(),
            "registrar_name": self.registrar_name,
            "registrar_contact": self.registrar_contact,
            "nameservers": self.nameservers,
            "dnssec_enabled": self.dnssec_enabled,
            "last_modified": self.last_modified.isoformat(),
        }

    def full_whois(self) -> dict:
        """Art.25(5): full dataset for legitimate access seekers."""
        return {
            **self.public_whois(),
            "registrant_name": self.registrant_name,
            "registrant_email": self.registrant_email,
            "registrant_phone": self.registrant_phone,
            "verification_status": self.verification_status.value,
            "verification_date": (
                self.verification_date.isoformat() if self.verification_date else None
            ),
        }

    def is_email_verified(self) -> bool:
        return self.verification_status == VerificationStatus.VERIFIED


@dataclass
class AccessRequest:
    request_id: str
    domain_name: str
    requester_name: str
    requester_organization: str
    requester_type: RequesterType
    stated_purpose: str
    timestamp: datetime = field(default_factory=datetime.utcnow)
    granted: bool = False
    fields_disclosed: list[str] = field(default_factory=list)
    denial_reason: Optional[str] = None

    def to_audit_log(self) -> dict:
        return {
            "request_id": self.request_id,
            "domain_name": self.domain_name,
            "requester": f"{self.requester_name} ({self.requester_organization})",
            "requester_type": self.requester_type.value,
            "purpose": self.stated_purpose,
            "timestamp": self.timestamp.isoformat(),
            "granted": self.granted,
            "fields_disclosed": self.fields_disclosed,
            "denial_reason": self.denial_reason,
        }


class NIS2WhoisCompliance:
    """
    Art.25 NIS2 WHOIS compliance manager.
    Handles database maintenance, verification, public disclosure,
    and legitimate access requests.
    """

    VERIFICATION_WINDOW_DAYS = 15
    SUSPENSION_GRACE_DAYS = 1
    RETENTION_AFTER_EXPIRY_YEARS = 2
    ACCESS_SLA = {
        RequesterType.NCA: timedelta(hours=24),
        RequesterType.LAW_ENFORCEMENT: timedelta(hours=24),
        RequesterType.CSIRT: timedelta(hours=48),
        RequesterType.RESEARCHER: timedelta(hours=72),
        RequesterType.DISPUTE_RESOLUTION: timedelta(hours=72),
        RequesterType.IP_RIGHTS_HOLDER: timedelta(hours=72),
        RequesterType.UNKNOWN: timedelta(hours=72),
    }

    def __init__(self, db_path: str = "whois.db"):
        self.db_path = db_path
        self._registry: dict[str, DomainRegistration] = {}
        self._access_log: list[AccessRequest] = []

    def register_domain(self, registration: DomainRegistration) -> str:
        """Register a new domain and initiate email verification."""
        domain = registration.domain_name.lower()
        token = hashlib.sha256(
            f"{domain}{registration.registrant_email}{datetime.utcnow().isoformat()}".encode()
        ).hexdigest()
        registration.verification_token = token
        registration.verification_status = VerificationStatus.EMAIL_SENT
        self._registry[domain] = registration
        self._send_verification_email(registration)
        logger.info("Domain %s registered, verification email sent", domain)
        return token

    def _send_verification_email(self, reg: DomainRegistration) -> None:
        """Send Art.25(3) mandatory email verification."""
        verify_url = f"https://registrar.example.com/verify?token={reg.verification_token}"
        logger.info(
            "Sending verification to %s for domain %s (URL: %s)",
            reg.registrant_email,
            reg.domain_name,
            verify_url,
        )

    def confirm_email_verification(self, domain: str, token: str) -> bool:
        """Process registrant's email verification click."""
        reg = self._registry.get(domain.lower())
        if not reg or reg.verification_token != token:
            return False
        reg.verification_status = VerificationStatus.VERIFIED
        reg.verification_date = datetime.utcnow()
        logger.info("Email verified for domain %s", domain)
        return True

    def run_verification_sweep(self) -> dict:
        """
        Periodic sweep: suspend unverified domains past the 15-day window.
        Run daily via cron.
        """
        now = datetime.utcnow()
        suspended = []
        warnings_sent = []

        for domain, reg in self._registry.items():
            if reg.verification_status == VerificationStatus.EMAIL_SENT:
                age = now - reg.registration_date
                deadline = timedelta(days=self.VERIFICATION_WINDOW_DAYS)
                grace = timedelta(days=self.SUSPENSION_GRACE_DAYS)

                if age > deadline + grace:
                    reg.verification_status = VerificationStatus.SUSPENDED
                    suspended.append(domain)
                    logger.warning("Domain %s suspended: no email verification", domain)
                elif age > deadline:
                    warnings_sent.append(domain)
                    logger.info("Verification warning sent for %s", domain)

        return {"suspended": suspended, "warnings_sent": warnings_sent}

    def get_public_whois(self, domain: str) -> Optional[dict]:
        """Art.25(4): return non-personal data — safe for public API."""
        reg = self._registry.get(domain.lower())
        if not reg:
            return None
        return reg.public_whois()

    def process_access_request(
        self,
        domain: str,
        requester_name: str,
        requester_org: str,
        requester_type: RequesterType,
        purpose: str,
        identity_verified: bool = False,
    ) -> AccessRequest:
        """
        Art.25(5): Handle legitimate access seeker request for personal WHOIS data.
        Logs every request regardless of outcome.
        """
        import uuid

        request = AccessRequest(
            request_id=str(uuid.uuid4()),
            domain_name=domain,
            requester_name=requester_name,
            requester_organization=requester_org,
            requester_type=requester_type,
            stated_purpose=purpose,
        )

        reg = self._registry.get(domain.lower())
        if not reg:
            request.denial_reason = "Domain not in registry"
            self._access_log.append(request)
            return request

        if not identity_verified:
            request.denial_reason = "Requester identity not verified"
            self._access_log.append(request)
            return request

        if requester_type == RequesterType.UNKNOWN:
            request.denial_reason = "Requester type not recognized as legitimate seeker"
            self._access_log.append(request)
            return request

        # Grant access — provide full WHOIS including personal data
        sla = self.ACCESS_SLA[requester_type]
        request.granted = True
        request.fields_disclosed = list(reg.full_whois().keys())
        self._access_log.append(request)
        logger.info(
            "WHOIS access granted for %s to %s (SLA: %s)",
            domain,
            requester_org,
            sla,
        )
        return request

    def export_access_audit_log(self) -> list[dict]:
        """Export access log for supervisory reporting."""
        return [r.to_audit_log() for r in self._access_log]

    def check_retention_compliance(self) -> list[str]:
        """
        Identify expired domains whose data must still be retained
        under the 2-year post-expiry retention rule.
        """
        now = datetime.utcnow()
        retention_cutoff = now - timedelta(days=self.RETENTION_AFTER_EXPIRY_YEARS * 365)
        expired_but_retain = []

        for domain, reg in self._registry.items():
            if reg.expiration_date < now:
                if reg.expiration_date > retention_cutoff:
                    expired_but_retain.append(domain)

        return expired_but_retain

    def compliance_report(self) -> dict:
        """Generate an Art.25 compliance status report."""
        total = len(self._registry)
        verified = sum(
            1
            for r in self._registry.values()
            if r.verification_status == VerificationStatus.VERIFIED
        )
        suspended = sum(
            1
            for r in self._registry.values()
            if r.verification_status == VerificationStatus.SUSPENDED
        )
        pending = total - verified - suspended

        return {
            "total_domains": total,
            "email_verified": verified,
            "email_verification_rate": round(verified / total * 100, 1) if total else 0,
            "suspended_unverified": suspended,
            "verification_pending": pending,
            "access_requests_total": len(self._access_log),
            "access_requests_granted": sum(1 for r in self._access_log if r.granted),
            "access_requests_denied": sum(1 for r in self._access_log if not r.granted),
            "expired_domains_in_retention": len(self.check_retention_compliance()),
        }

Art.25 × Art.21: Double Obligations for DNS Providers

If you are a DNS service provider that also provides domain registration (common for web hosting companies), you face both Art.25 WHOIS obligations and Art.21 security measures. The Art.21 obligations for DNS operators include:

Art.21 MeasureDNS-Specific Implementation
Art.21(2)(a) — Risk analysisDNS amplification risk assessment, registrar-level abuse monitoring
Art.21(2)(b) — Incident handlingDNS hijacking response procedure; domain suspension protocol
Art.21(2)(c) — Business continuityAnycast DNS infrastructure; secondary DNS failover
Art.21(2)(d) — Supply chainRegistrar accreditation vetting; upstream registry SLA review
Art.21(2)(h) — CryptographyDNSSEC mandatory offer; DANE/TLSA support
Art.21(2)(j) — MFAMFA for registrar control panel; EPP client authentication

DNSSEC is the highest-impact single measure for DNS providers. Art.21(2)(h) requires cryptography where appropriate — for a DNS operator, DNSSEC is "appropriate." Offering DNSSEC without enabling it by default is increasingly considered non-compliant.


Common Art.25 Mistakes Registrars Make

Mistake 1: Treating Old WHOIS as Compliant

Pre-GDPR WHOIS databases with public personal data are not Art.25 compliant — they violate GDPR. Tiered access (public for non-personal, gated for personal) is the only compliant model.

Mistake 2: No Formal Legitimate Access Procedure

Many small registrars handle WHOIS requests ad hoc via support tickets. Art.25(5) requires a documented access policy — not just informal handling. NCAs will audit this.

Mistake 3: Missing Verification for Existing Registrations

Art.25(3) is not just for new registrations. Registrars must have a plan to re-verify existing registrant data. A one-time campaign for your existing domain portfolio is required.

Mistake 4: No Access Request Audit Log

Every Art.25(5) request — granted or denied — must be logged. Logging only granted requests is insufficient. NCAs want to see the full picture including denied attempts (which may indicate bulk-harvesting).

Mistake 5: Confusing Suspension with Cancellation

When a domain is suspended for non-verification (Art.25(3)), DNS resolution should stop (SERVFAIL or NXDOMAIN) but the registration should not be immediately cancelled. The registrant has a cure period before cancellation.

Mistake 6: Ignoring Reseller Obligations

If you resell domains via an upstream registrar API, you are still subject to Art.25 for the registration data you collect. You cannot outsource the compliance obligation entirely to the upstream registrar.


Art.25 in the NIS2 Compliance Timeline

DateArt.25 Milestone
17 Jan 2025NIS2 transposition deadline — Art.25 legally in force
Q1 2025NCAs begin supervising registrars; Art.25 audits expected Q2 2025
Q2 2025First Art.25 enforcement actions expected (largest registrars first)
2026Smaller registrars (important entities) in supervisory perimeter
OngoingAnnual verification sweeps, access log retention (3 years recommended)

Most German registrars have been implementing WHOIS tiering since GDPR (2018). The NIS2 Art.25 delta is primarily: (1) mandatory verification procedures, (2) formal access policy, (3) 72-hour SLA for legitimate seekers, and (4) cooperating with ENISA on interoperability.


15-Item Art.25 Compliance Checklist

Database and Data Quality (Art.25(1)–(2))

Verification (Art.25(3))

Public Disclosure (Art.25(4))

Legitimate Access (Art.25(5))


Conclusion

NIS2 Art.25 is one of the more specific obligations in the directive — it targets a narrow but critical category of infrastructure operators. If you are building or maintaining registrar software, a TLD backend, or a domain management API, Art.25 is not optional fine-print. It defines the data model, the verification workflow, the access control architecture, and the public API contract.

The GDPR tension is real but manageable: tiered WHOIS (public non-personal, gated personal) is the established compliance pattern. The verification obligation is new for many smaller registrars who never had formal email verification. And the legitimate access procedure — with its SLA clock, identity verification, and audit log — requires dedicated engineering work, not just a support inbox.

The NIS2WhoisCompliance class above gives you the core primitives. Extend it with your database backend, SMTP integration, and RDAP endpoint to build a fully Art.25-compliant registrar stack.


Related posts: