News

Denmark Population Registry Breach: 8.8 Million CPR Entries Exposed (October 2026)

9 min read

The Back Room Tech is reader-supported. We may earn a commission when you buy through links on our site. Learn more.

On October 5, 2026, Danish authorities disclosed a breach of the Central Person Register (CPR). The CPR is Denmark’s national population registry. It issues the CPR number used across government, banking, and healthcare. BleepingComputer and Euronews report that names, addresses, and CPR numbers tied to about 8.8 million registered entries were accessed. The suspected route was a private company’s legitimate access to the register. That access was used to run a huge number of automated lookups.

For IT teams, the short version is simple. Officials describe abuse of a company’s lawful access, not a break-in through the registry’s perimeter. A valid credential was used to pull far more data than it should ever have reached. The 8.8 million figure is provisional, and it counts register entries, including people who’ve died or moved away. Sources were retrieved October 6, 2026. The police case is still open, so recheck the numbers before you quote them.

What Happened

What the CPR is and why the number matters

The CPR gives every person registered in Denmark a personal ID number. That CPR number works as a primary key across Danish public and private services: tax, healthcare, banking, and more. Pair a CPR number with a name and a home address, and an attacker has a ready-made identity profile. That’s why this story matters beyond the headline count.

Timeline

Here’s the timeline as reported by The Hacker News, The Record, and Euronews:

Date (2026)Event
September (about 10 days)A very large number of automated lookups ran through the company’s access
October 2 (Friday)Register staff noticed the unusual activity
October 3–4 (weekend)Authorities mapped the extent of the incident
October 4 (Sunday)Denmark’s Data Protection Agency (Datatilsynet) was notified
October 5 (Monday)Public disclosure, with Euronews quoting officials calling it “extremely serious”

Note the gap. The lookups ran in September, but nobody flagged them until October 2. Once spotted, the incident went from detection to public disclosure in three days. The slow part was noticing, not announcing. Security Affairs carries the same sequence.

What data was exposed

Reported exposed fields (officials have not said whether every field was pulled for every entry):

  • Names
  • Addresses
  • CPR numbers

People in Denmark’s name-and-address protection program were reportedly left out of the exposed set. According to The Hacker News, the access did not include the names and addresses of people registered with that protection. If that holds up once the investigation ends, the access control behind it worked even while the bulk lookups kept running.

Who did it and how

Reporting ties the access to a private Danish company’s legitimate credentials, used for mass automated lookups. Officials say unauthorized parties abused that company’s lawful access, and Euronews describes hackers breaking into the company. The sourced reporting does not name the company or explain how the attackers got hold of its access. Nor does it say what tech stack or hosting provider runs the CPR. Danish police and the Data Protection Agency are investigating. Anyone claiming to know those details right now is ahead of the evidence.

The register administration has since stopped the company’s access, according to The Hacker News and the Copenhagen Post. An early Euronews report said the access had not been revoked, so check the date on anything you read about the access status.

BleepingComputer article page headlined Denmark population registry data breach affects 8.8 million people, with the Bill Toulas byline

How It Stacks Up

No vendor or product has been implicated, so there’s no platform comparison to make. The useful comparison is between the reported numbers. That’s where the confusion is.

8.8 million vs. Denmark’s population

Denmark has about 6 million residents. So how did 8.8 million people get hit? The answer is in what the register holds:

FigureWhat it representsSource
~11 millionTotal records in the CPR, including deceased and emigrated peopleEuronews, The Hacker News
~8.8 millionRegistered entries reportedly accessed (provisional)BleepingComputer
~6 millionDenmark’s current resident populationWorldometer, Euronews

The accurate phrase is “8.8 million register entries.” Some of those entries belong to people who’ve died or moved abroad. Those records still carry risk: a valid CPR number paired with a name and address can be misused whether or not the person still lives in Denmark.

Is 8.8 million final?

No. Reporting calls the figure provisional until the police and Data Protection Agency finish their work. Treat it as the October 5, 2026 working number. Wider coverage from DW, Anadolu Agency, and The Record carries the same provisional figure.

Classic hack vs. credential misuse

This is the comparison that matters for admins:

Perimeter breachLegitimate-access misuse (suspected here)
Entry pointExploited vulnerability or stolen admin credsValid third-party API or system credential
What stops itPatching, WAF, network segmentationScoped permissions, per-client rate limits, volume alerting
Why detection lagsIntrusion alerts may fire earlyEach lookup looks authorized, so only the volume is abnormal

Most patching and firewall budgets go to the left column. This incident sits in the right one.

The Reaction

The official reaction came fast. Digital Affairs Minister Christina Egelund called it “an extremely serious incident” (Euronews) and ordered a full security review of the register. The government also extended the hours of its digital security hotline, and The Record notes this is the most significant CPR incident since 2015. Security outlets amplified the story too, including BleepingComputer’s post on X.

Our read of the practical risk is fraud and social engineering. A caller who knows your CPR number, name, and address proves much less than they used to. There’s also the bigger design question: any mandatory national ID store is a high-value target, and GDPR can punish a leak but can’t undo one. From what’s reported, the registry wasn’t cracked. A legitimate pipe was opened too wide for too long.

BleepingComputer post on X about the Denmark population registry breach affecting 8.8 million people

Our Take

Verdict: Adopt, today, the practice this breach exposes as missing: per-client scoping and volume alerting on any system that answers lookups against personal data.

Everything from here on is our recommendation, not something the incident reporting proves. There’s no product to adopt, pilot, or hold here. Ask whether your shop treats “authorized but abnormal” as a security event. The CPR lookups ran for roughly ten days in September before staff noticed them on October 2. On your own systems, you want that gap measured in minutes.

The cost is mostly staff time. Licensing barely enters into it. You need a list of every outside client holding a standing credential. You need a rate limit keyed on that credential, not the source IP. And you need an alert when one client’s daily lookup count jumps far above its baseline. In a small setup, that’s an afternoon of config work. With dozens of partner links, the inventory itself is the expensive part.

This applies to homelab identity stores too: Keycloak, LDAP, a self-hosted directory, or a contact-lookup API on a Raspberry Pi. Any endpoint that answers “who is this?” can be enumerated.

Linux: rate-limit and spot heavy lookups (nginx)

This pattern uses the standard limit_req module, so it works on nginx 1.24, the version in the Ubuntu 24.04 LTS repositories. It assumes your API clients send an X-API-Key header. Change the key variable to match how your clients actually log in. One catch: nginx doesn’t rate-limit requests with an empty key at all, so a client that simply omits the header would bypass the limit. The config below rejects those requests outright.

# /etc/nginx/conf.d/lookup-ratelimit.conf
# Key the limit on the client credential, not the IP: a misused partner key
# can come from many IPs, but it's still one key.
limit_req_zone $http_x_api_key zone=lookup_per_key:10m rate=10r/s;

server {
    listen 443 ssl;
    server_name api.example.internal;

    location /lookup/ {
        # Requests without a key are not counted by limit_req, so refuse them
        if ($http_x_api_key = "") {
            return 401;
        }
        # burst=20 absorbs short spikes; nodelay rejects anything beyond immediately
        limit_req zone=lookup_per_key burst=20 nodelay;
        limit_req_status 429;
        proxy_pass http://127.0.0.1:8080;
    }
}

Warning: Validate before reloading. A syntax error on reload can take your proxy offline.

sudo nginx -t && sudo systemctl reload nginx

Expected output:

nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful

Next, find your top requesters. The first field in the default combined log format is the client IP:

# Count requests per client IP and show the top 10
awk '{print $1}' /var/log/nginx/access.log | sort | uniq -c | sort -rn | head -n 10

Expected output (example):

48211 203.0.113.45
1302 198.51.100.12
877 192.0.2.30

One client doing 30–50x the volume of the next is exactly the pattern worth an alert. To count per API key instead of per IP, add $http_x_api_key to a custom log_format. Then ship the logs to a central collector. In a homelab, a small NAS or a spare mini PC running your log stack works fine.

Windows Server: top requesters in IIS logs

On Windows Server 2022 with IIS 10, run this in PowerShell 5.1 or 7.x:

# Find the c-ip column from each file's #Fields header instead of assuming its position.
Get-ChildItem "C:\inetpub\logs\LogFiles\W3SVC1\*.log" | ForEach-Object {
  $idx = -1
  Get-Content $_.FullName | ForEach-Object {
    if ($_ -like '#Fields:*') {
      $idx = [array]::IndexOf((($_ -replace '^#Fields: ', '') -split ' '), 'c-ip')
    } elseif ($_ -notmatch '^#' -and $idx -ge 0) {
      ($_ -split ' ')[$idx]
    }
  }
} |
  Group-Object |
  Sort-Object Count -Descending |
  Select-Object -First 10 Count, Name

Expected output (example):

Count Name
—– —-
47920 203.0.113.45
1288 198.51.100.12
860 192.0.2.30

For enforcement on IIS, the Dynamic IP Restrictions feature caps request rates per IP. Install the IP and Domain Restrictions role service first, then go to IIS Manager > your site > IP Address and Domain Restrictions > Edit Dynamic Restriction Settings in the Actions pane (Microsoft Learn). Per-credential limits usually have to live in your app or API gateway.

Whatever OS you run, protect the admin accounts that can issue or widen partner credentials. A hardware key like a YubiKey is the easy win. A rate limit doesn’t help if someone can quietly raise it.

Wrapping Up

So far, the Denmark registry breach looks like a valid credential used for bulk lookups far beyond its purpose. It reportedly exposed names, addresses, and CPR numbers for about 8.8 million register entries. That count is provisional and includes dead and emigrated people. No vendor is implicated, and the company involved hasn’t been named. Our view: firewalls were never going to catch this. The hard part is knowing what “normal” looks like for every client you’ve already trusted.

StepActionApplies To
1Inventory every third-party credential with lookup access to personal dataAll PII systems
2Scope each credential to the minimum fields and records it needsAPIs, LDAP, Keycloak, directories
3Rate-limit per credential as well as per IPnginx (Linux), app/gateway (Windows)
4Alert on per-client volume spikes against a baselineLog stack or SIEM
5Watch Denmark’s Data Protection Agency for the final countAnyone handling Danish identity data