2026-04-15·14 min read·

NIS2 Art.23 Incident Reporting: 24-Hour, 72-Hour, and 1-Month Timelines — Developer Implementation Guide

NIS2 (Directive 2022/2555/EU) is now in force across all EU Member States. For the approximately 160,000 essential and important entities in 18 critical sectors, Article 23 imposes one of the most demanding incident reporting obligations in any regulatory framework: a mandatory three-stage notification process with strict time limits.

If your organisation suffers a significant cybersecurity incident, the clock starts immediately:

Missing these deadlines is not a minor compliance gap. NIS2 Art.32 and Art.33 authorise fines of up to €10 million or 2% of global annual turnover for essential entities, and €7 million or 1.4% for important entities — whichever is higher.

This guide explains exactly what triggers each reporting stage, what content each submission must include, how to build the engineering infrastructure to meet these timelines under real incident conditions, and what hosting choices affect your exposure.


1. Which Entities Must Report (Art.23(1))

NIS2 Art.23(1) applies to essential entities and important entities as defined in Art.3.

Essential entities (Art.3(1)) include organisations in:

Important entities (Art.3(2)) include organisations in:

Size thresholds (with some exceptions): Essential entities are generally medium-to-large organisations (>250 employees or >€50 million turnover or >€43 million balance sheet). Important entities include medium enterprises. Member States may lower these thresholds.

Even if you are not directly subject to NIS2, you may be in scope as a supplier to a NIS2-regulated entity under the Art.21(2)(d) supply chain security obligation.


2. What Is a "Significant Incident" (Art.23(3))

The Art.23 reporting obligation is triggered only by significant incidents. Art.23(3) defines these as incidents that:

"have had or are capable of having a significant impact on the provision of the services"

An incident is considered significant if it:

(a) Has caused or is capable of causing severe operational disruption of the service or financial loss for the entity concerned; or

(b) Has affected or is capable of affecting other natural or legal persons by causing considerable material or non-material damage.

NIS2 Recital 101 adds interpretive guidance: significance should be assessed considering:

The European Union Agency for Cybersecurity (ENISA) has published additional guidance on significance thresholds that NCAs may implement. In practice, most national implementations treat incidents involving data breaches, service outages >1 hour affecting substantial user populations, or ransomware as significant by default.

Significant vs. GDPR Data Breach

If a cybersecurity incident also involves personal data, you may have concurrent reporting obligations under both NIS2 Art.23 (to the NCA) and GDPR Art.33 (to the Data Protection Authority). GDPR Art.33 has a 72-hour deadline for DPA notification — identical to NIS2 Stage 2, but with a different authority and different content requirements. Coordinate both processes simultaneously.


3. The Three-Stage Reporting Timeline

Art.23(4) establishes the three mandatory stages. The clock starts from the moment your organisation becomes aware of the significant incident.

Stage 1: Early Warning — Within 24 Hours of Awareness (Art.23(4)(a))

The early warning must be submitted without undue delay, and in any event within 24 hours of becoming aware of the significant incident.

Required content (Art.23(4)(a)):

The early warning is a minimal notification — the NCA is not expecting a full technical analysis at this stage. The purpose is to give authorities advance notice so they can prepare resources, coordinate with other Member States if cross-border impact is expected, and provide immediate guidance if needed.

Key operational challenge: Your on-call team must have a process to reach the NCA contact within 24 hours, including on weekends and public holidays. NIS2 does not grant exemptions for business hours.

Stage 2: Incident Notification — Within 72 Hours of Awareness (Art.23(4)(b))

The full incident notification must be submitted without undue delay, and in any event within 72 hours of becoming aware.

Required content (Art.23(4)(b)):

Art.23(5) specifies that where a notification is not yet possible within 72 hours, an intermediate report should be provided together with reasons for the delay. The final report must still follow within 1 month.

Practical note: 72 hours is longer than 24 hours but often still mid-incident. Your security operations team may still be in active containment. The report does not need to be complete — it needs to reflect your current understanding. Overcomplicating Stage 2 in pursuit of perfection causes missed deadlines, which is worse than an accurate-but-incomplete Stage 2 notification.

Stage 3: Final Report (or Intermediate + Final) — Within 1 Month

Option A — Resolved incident: Submit a final report within one month of submitting the Stage 2 notification.

The final report must include:

Option B — Ongoing incident: Where the incident is still ongoing at the 1-month mark, submit an intermediate report with an update on the situation. Then submit a final report within one month after the incident has been handled.

Day 0: Incident discovered / Awareness triggered
  ↓
Day 0 + 24h: STAGE 1 — Early Warning
  (malicious/accidental? cross-border impact?)
  ↓
Day 0 + 72h: STAGE 2 — Incident Notification
  (severity, IoCs, ongoing status)
  ↓
Day 0 + 30d: STAGE 3A — Final Report (if resolved)
  OR
Day 0 + 30d: STAGE 3B — Intermediate Report (if ongoing)
  ↓ (for ongoing only)
Resolution + 30d: STAGE 3C — Final Report

4. Determining "Awareness" — The Starting Gun

The Art.23 clock starts from awareness. This is a deliberately flexible concept that gives regulators room to interpret in your organisation's disfavour if your detection capabilities are poor.

Awareness begins when any person within your organisation responsible for incident management, security, or operations has actual knowledge that a significant incident has occurred or is occurring. This is NOT limited to your CISO or security team — it can include a developer who discovers a breach, a NOC engineer who sees anomalous traffic, or a customer service agent who receives reports of service disruption.

ENISA guidance suggests that for regulatory purposes, organisations should document the precise timestamp at which any employee became aware of the incident, not just when it was escalated to the security team. Poor internal escalation processes do not reset the Art.23 clock.

Practical implication: Your monitoring and alerting pipeline determines your effective reporting window. If your SIEM takes 18 hours to correlate an alert into an incident classification, you may have only 6 hours left for Stage 1.


5. National Competent Authority Reporting Channels

NIS2 requires Member States to designate NCAs for cybersecurity. Each NCA operates reporting channels that essential and important entities in their jurisdiction must use.

Member StateNCANIS2 Reporting Channel
GermanyBSI (Bundesamt für Sicherheit in der Informationstechnik)MELDEPFLICHT@bsi.bund.de + Portal
FranceANSSIPortail de signalement + meldung@anssi.fr
NetherlandsNCSC-NLcert@ncsc.nl + Nationaal Meldpunt
AustriaCERT.at / DSBmeldung@cert.at
PolandCERT Polska / UODOincydent@cert.pl
SwedenNCSC-SEncsc@forsvarsmakten.se
SpainINCIBE-CERT / CCN-CERTincidencias@incibe-cert.es
ItalyACN (Agenzia per la Cybersicurezza Nazionale)segnalazioni@acn.gov.it

Important: In some Member States, you report to the sector-specific NCA (e.g., BaFin for German financial entities, EBA-coordinated for cross-border banks), not a general-purpose cybersecurity NCA. Identify your correct reporting authority before an incident occurs.


6. Python NIS2IncidentReporter Implementation

The following implementation helps your security operations team classify incidents, calculate deadlines, and generate reporting content.

from dataclasses import dataclass, field
from datetime import datetime, timedelta
from enum import Enum
from typing import Optional


class IncidentSeverity(Enum):
    LOW = "low"
    MEDIUM = "medium"
    HIGH = "high"
    CRITICAL = "critical"


class IncidentType(Enum):
    RANSOMWARE = "ransomware"
    DATA_BREACH = "data_breach"
    DDoS = "ddos"
    SUPPLY_CHAIN = "supply_chain"
    UNAUTHORIZED_ACCESS = "unauthorized_access"
    INSIDER_THREAT = "insider_threat"
    SYSTEM_FAILURE = "system_failure"
    OTHER = "other"


@dataclass
class NIS2IncidentClassification:
    is_significant: bool
    severity: IncidentSeverity
    is_malicious: bool
    cross_border_likely: bool
    is_ongoing: bool
    affected_users_estimate: int
    service_disruption_hours: float
    significance_reasons: list[str] = field(default_factory=list)


@dataclass
class NIS2ReportingDeadlines:
    awareness_timestamp: datetime
    stage1_deadline: datetime   # 24h
    stage2_deadline: datetime   # 72h
    stage3_deadline: datetime   # 1 month from stage2
    
    def hours_remaining(self, deadline: datetime) -> float:
        delta = deadline - datetime.now()
        return delta.total_seconds() / 3600
    
    def status_summary(self) -> dict:
        now = datetime.now()
        return {
            "stage1_hours_remaining": self.hours_remaining(self.stage1_deadline),
            "stage2_hours_remaining": self.hours_remaining(self.stage2_deadline),
            "stage3_days_remaining": (self.stage3_deadline - now).days,
            "stage1_overdue": now > self.stage1_deadline,
            "stage2_overdue": now > self.stage2_deadline,
        }


@dataclass
class NIS2Stage1Report:
    incident_id: str
    awareness_timestamp: str
    entity_name: str
    nca_contact: str
    is_malicious_suspected: bool
    cross_border_impact_suspected: bool
    initial_description: str
    affected_services: list[str]
    submitted_at: str = ""
    
    def to_text(self) -> str:
        return f"""
NIS2 ART.23(4)(a) EARLY WARNING NOTIFICATION
============================================
Entity: {self.entity_name}
Incident ID: {self.incident_id}
Awareness Timestamp: {self.awareness_timestamp}
Submitted To: {self.nca_contact}

MANDATORY DISCLOSURES:
- Suspected malicious/unlawful cause: {'YES' if self.is_malicious_suspected else 'NO / UNDETERMINED'}
- Cross-border impact suspected: {'YES' if self.cross_border_impact_suspected else 'NO / UNKNOWN'}

INITIAL DESCRIPTION:
{self.initial_description}

AFFECTED SERVICES:
{chr(10).join(f'  - {s}' for s in self.affected_services)}

Note: Full incident notification to follow within 72 hours of awareness.
""".strip()


@dataclass
class NIS2Stage2Report:
    incident_id: str
    entity_name: str
    nca_contact: str
    severity: str
    is_ongoing: bool
    initial_assessment: str
    indicators_of_compromise: list[str]
    affected_users_count: int
    service_disruption_hours: float
    cross_border_details: str
    mitigation_actions_taken: list[str]
    submitted_at: str = ""
    
    def to_text(self) -> str:
        ioc_section = "\n".join(f"  - {ioc}" for ioc in self.indicators_of_compromise) if self.indicators_of_compromise else "  None identified at this stage"
        mitigations = "\n".join(f"  - {m}" for m in self.mitigation_actions_taken)
        return f"""
NIS2 ART.23(4)(b) INCIDENT NOTIFICATION
========================================
Entity: {self.entity_name}
Incident ID: {self.incident_id}
Submitted To: {self.nca_contact}

ASSESSMENT:
- Severity: {self.severity.upper()}
- Status: {'ONGOING' if self.is_ongoing else 'RESOLVED'}
- Affected Users (estimate): {self.affected_users_count:,}
- Service Disruption Duration: {self.service_disruption_hours:.1f} hours

INITIAL ASSESSMENT:
{self.initial_assessment}

INDICATORS OF COMPROMISE:
{ioc_section}

CROSS-BORDER IMPACT:
{self.cross_border_details}

MITIGATION ACTIONS TAKEN:
{mitigations}

Note: Final report to follow within 1 month.
""".strip()


class NIS2IncidentReporter:
    """
    NIS2 Art.23 incident classification and reporting deadline manager.
    Helps security operations teams meet 24h/72h/1-month NIS2 obligations.
    """
    
    # Significance thresholds (organisation-specific, adjust as needed)
    SIGNIFICANT_USER_THRESHOLD = 1000
    SIGNIFICANT_DISRUPTION_HOURS = 1.0
    
    def classify_incident(
        self,
        incident_type: IncidentType,
        affected_users: int,
        disruption_hours: float,
        involves_personal_data: bool,
        critical_service_unavailable: bool,
        financial_loss_eur: Optional[float] = None,
    ) -> NIS2IncidentClassification:
        """
        Classify whether an incident is 'significant' under NIS2 Art.23(3).
        Returns classification with significance determination and reasoning.
        """
        reasons = []
        is_significant = False
        
        # Always significant: ransomware, confirmed data breach
        if incident_type in [IncidentType.RANSOMWARE, IncidentType.SUPPLY_CHAIN]:
            is_significant = True
            reasons.append(f"{incident_type.value} — presumed significant by type")
        
        # Significant if substantial users affected
        if affected_users >= self.SIGNIFICANT_USER_THRESHOLD:
            is_significant = True
            reasons.append(f"{affected_users:,} users affected (≥{self.SIGNIFICANT_USER_THRESHOLD:,} threshold)")
        
        # Significant if extended service disruption
        if disruption_hours >= self.SIGNIFICANT_DISRUPTION_HOURS:
            is_significant = True
            reasons.append(f"{disruption_hours:.1f}h service disruption (≥{self.SIGNIFICANT_DISRUPTION_HOURS}h threshold)")
        
        # Significant if personal data involved
        if involves_personal_data:
            is_significant = True
            reasons.append("Personal data involved — concurrent GDPR Art.33 obligation")
        
        # Significant if critical service unavailable
        if critical_service_unavailable:
            is_significant = True
            reasons.append("Critical service unavailable — severe operational disruption")
        
        # Significant if material financial loss
        if financial_loss_eur and financial_loss_eur >= 50_000:
            is_significant = True
            reasons.append(f"Financial loss €{financial_loss_eur:,.0f} (material threshold)")
        
        # Determine severity
        if incident_type == IncidentType.RANSOMWARE or (financial_loss_eur and financial_loss_eur >= 1_000_000):
            severity = IncidentSeverity.CRITICAL
        elif critical_service_unavailable or affected_users >= 10_000:
            severity = IncidentSeverity.HIGH
        elif is_significant:
            severity = IncidentSeverity.MEDIUM
        else:
            severity = IncidentSeverity.LOW
        
        is_malicious = incident_type not in [IncidentType.SYSTEM_FAILURE]
        
        return NIS2IncidentClassification(
            is_significant=is_significant,
            severity=severity,
            is_malicious=is_malicious,
            cross_border_likely=affected_users >= 10_000,
            is_ongoing=True,  # Conservative default — update as situation evolves
            affected_users_estimate=affected_users,
            service_disruption_hours=disruption_hours,
            significance_reasons=reasons,
        )
    
    def calculate_deadlines(self, awareness_timestamp: datetime) -> NIS2ReportingDeadlines:
        """
        Calculate Art.23 reporting deadlines from awareness timestamp.
        """
        return NIS2ReportingDeadlines(
            awareness_timestamp=awareness_timestamp,
            stage1_deadline=awareness_timestamp + timedelta(hours=24),
            stage2_deadline=awareness_timestamp + timedelta(hours=72),
            stage3_deadline=awareness_timestamp + timedelta(days=30),
        )
    
    def generate_stage1_report(
        self,
        incident_id: str,
        entity_name: str,
        nca_contact: str,
        classification: NIS2IncidentClassification,
        initial_description: str,
        affected_services: list[str],
        awareness_timestamp: datetime,
    ) -> NIS2Stage1Report:
        return NIS2Stage1Report(
            incident_id=incident_id,
            awareness_timestamp=awareness_timestamp.isoformat(),
            entity_name=entity_name,
            nca_contact=nca_contact,
            is_malicious_suspected=classification.is_malicious,
            cross_border_impact_suspected=classification.cross_border_likely,
            initial_description=initial_description,
            affected_services=affected_services,
        )


# Example: e-commerce platform ransomware incident
if __name__ == "__main__":
    reporter = NIS2IncidentReporter()
    
    # Incident discovered at 2026-06-15 09:30 UTC
    awareness = datetime(2026, 6, 15, 9, 30)
    
    classification = reporter.classify_incident(
        incident_type=IncidentType.RANSOMWARE,
        affected_users=45_000,
        disruption_hours=6.0,
        involves_personal_data=True,
        critical_service_unavailable=True,
        financial_loss_eur=120_000,
    )
    
    deadlines = reporter.calculate_deadlines(awareness)
    
    print("=== INCIDENT CLASSIFICATION ===")
    print(f"Significant: {classification.is_significant}")
    print(f"Severity: {classification.severity.value}")
    print(f"Reasons: {', '.join(classification.significance_reasons)}")
    print()
    
    print("=== REPORTING DEADLINES ===")
    status = deadlines.status_summary()
    print(f"Stage 1 (24h): {deadlines.stage1_deadline.isoformat()}")
    print(f"Stage 2 (72h): {deadlines.stage2_deadline.isoformat()}")
    print(f"Stage 3 (30d): {deadlines.stage3_deadline.isoformat()}")
    
    stage1 = reporter.generate_stage1_report(
        incident_id="INC-2026-0615-001",
        entity_name="ExamplePlatform GmbH",
        nca_contact="MELDEPFLICHT@bsi.bund.de",
        classification=classification,
        initial_description="Ransomware infection detected on production order processing servers. Checkout service unavailable. Investigation ongoing.",
        affected_services=["checkout", "order-processing", "customer-portal"],
        awareness_timestamp=awareness,
    )
    
    print()
    print("=== STAGE 1 REPORT DRAFT ===")
    print(stage1.to_text())

7. GDPR Art.33 vs. NIS2 Art.23 — Dual Reporting

If a cybersecurity incident involves personal data, you face concurrent reporting obligations under two separate regimes:

AspectNIS2 Art.23GDPR Art.33
AuthorityNational Competent Authority (NCA)Data Protection Authority (DPA)
Stage 1 deadline24h (early warning)No equivalent
Stage 2 deadline72h (incident notification)72h (breach notification)
Final report1 monthNo mandatory final report
TriggerSignificant incident (includes non-data events)Personal data breach
Who must reportNIS2-regulated entitiesAll GDPR data controllers
Content focusIncident severity, IoCs, cross-border impactNature of breach, data categories, likely consequences

Key operational decision: Do not try to submit a single document to both authorities. The content requirements differ, the recipients differ, and conflating them creates compliance risk if either submission is rejected.

Build separate parallel workflows for NIS2 and GDPR reporting. Trigger both simultaneously when a personal-data incident is detected. Many organisations use the 72-hour Stage 2 notification process as the natural trigger to also check and prepare the GDPR Art.33 notification.


8. Engineering Infrastructure for NIS2 Art.23 Compliance

Meeting the 24-hour and 72-hour deadlines under real incident conditions requires pre-built infrastructure. When your systems are compromised or malfunctioning is precisely when you cannot afford to build reporting processes from scratch.

Detection and Awareness Pipeline

The Art.23 clock starts at awareness, not at CISO notification. Your monitoring pipeline determines your effective reporting window.

Recommended architecture:

[Application Logs] ─→ [SIEM/SOAR] ─→ [Incident Classification] ─→ [Alert: Significant?]
[Infrastructure Metrics] ─→ [Anomaly Detection] ─→ [Threshold Breach] ─→ [On-call Page]
[Security Events] ─→ [Threat Intelligence] ─→ [Correlation] ─→ [Incident Ticket Created]
                                                                          ↓
                                                              [NIS2_AWARENESS_TIMESTAMP logged]
                                                              [Deadlines calculated]
                                                              [On-call notified of NIS2 obligation]

Critical requirement: Log the NIS2_AWARENESS_TIMESTAMP the moment any automated or human trigger classifies an event as a potential significant incident — even before investigation is complete. This timestamp becomes your legal record. Do not wait for confirmation.

NCA Contact Database

Maintain a current, authorised-to-report list of NCA contacts for each Member State where your services are active. Store this in a readily accessible location that remains available even when your primary systems are under attack (separate credential store, printed copy in security playbook).

Verify NCA contact details at minimum every 6 months — regulatory reporting channels change.

Incident Reporting Runbook

Your security runbook must include:

  1. Significance assessment worksheet (pre-built, fill-in fields for Art.23(3) criteria)
  2. Deadline calculator (auto-populate from awareness timestamp)
  3. Stage 1 template (pre-drafted, fill in incident specifics)
  4. Stage 2 template (structured fields for each required disclosure)
  5. NCA contact list with backup contacts for each Member State
  6. GDPR Art.33 cross-check (yes/no: personal data involved?)
  7. Legal/DPO notification trigger (auto-notify at incident classification)

Offline Availability

Your incident reporting capability must survive the incident itself. If your primary communication systems are encrypted by ransomware, you need:


9. Cloud Provider Considerations

Your choice of cloud provider affects your NIS2 Art.23 compliance in two ways.

Forensic Data Access

To produce Stage 2 and Stage 3 reports, you need forensic access to your incident timeline, logs, and indicators of compromise. Cloud providers vary significantly in:

Ensure your cloud provider contracts include provisions for log retention, forensic access during incidents, and response timelines that are compatible with your NIS2 reporting obligations.

Jurisdiction and CLOUD Act

A NIS2 Art.23 notification about a data breach at a US-incorporated cloud provider (AWS, Google, Azure) may be complicated by CLOUD Act considerations: if law enforcement requested your data covertly, you may have experienced a "breach" that Art.23 requires you to report, but that the CLOUD Act order prohibits your cloud provider from disclosing to you.

This gap between NIS2 Art.23(4)(b) "incident notification including IoCs" and CLOUD Act covert production orders creates a compliance paradox: you cannot include an IoC you do not know exists.

EU-incorporated cloud providers (OVHcloud, Hetzner, Scaleway, Exoscale, sota.io) are not subject to the CLOUD Act, eliminating this disclosure gap. NIS2 auditors in 2026 are increasingly asking about jurisdiction risk as part of Art.21(2)(d) supply chain security assessments.


10. Penalties and Supervisory Authority Powers (Art.32-33)

NIS2 distinguishes between essential and important entities in its enforcement regime:

Essential entities (Art.32):

Important entities (Art.33):

For Art.23 incident reporting specifically, NCAs can impose fines for:

NCAs may also conduct on-site inspections, request evidence of security measures, and issue binding instructions to remediate identified deficiencies.


11. Art.23 Compliance Checklist — 22 Items

Pre-Incident Preparation

During Incident

Post-Incident


Common Mistakes

1. Waiting for certainty before starting the clock. The Art.23 clock starts at awareness of a potential significant incident, not confirmed. If your alert monitoring flags a possible ransomware infection and your team investigates, Day 0 is when the alert was flagged, not when the investigation confirmed ransomware.

2. Submitting Stage 2 only when Stage 1 is complete. These are parallel deadlines with different clocks. If your Stage 1 takes 20 hours to draft and approve, you have only 52 hours left for Stage 2, not 72 hours from Stage 1 submission.

3. Reporting only to your DPA when personal data is involved. NIS2 Art.23 (NCA) and GDPR Art.33 (DPA) are separate obligations. Personal data breach → report to both.

4. Assuming your cloud provider will handle NIS2 notification. Cloud providers are not your competent authority. They may notify you of an incident on their infrastructure, but the Art.23 obligation to report to your NCA rests with your organisation, not your cloud provider.


Key Takeaways

NIS2 Art.23 is one of the most operationally demanding incident reporting regimes in any regulatory framework. The 24-hour early warning requirement is particularly aggressive — it requires pre-built processes, pre-approved templates, and out-of-band NCA contacts available before any incident occurs.

The engineering work required:

For organisations running on EU-sovereign infrastructure without CLOUD Act exposure, the forensic access and disclosure paradox issues are eliminated — you have complete visibility into your own incident data without jurisdiction conflicts.

The June 2026 NIS2 audit season means NCAs are actively reviewing Art.23 readiness. Organisations without documented incident reporting workflows are high-probability enforcement targets.


See Also