---
title: "We built DNS. The hard part wasn't DNS."
canonical: https://enum.co/blog/we-built-dns-the-hard-part-wasnt-dns
locale: en
publishedAt: 2026-08-17
author: "Roman Zipp"
summary: "Anyone can create a DNS zone for a domain they don't own. The four ways providers handle that gap, why three of them let a stranger block or take over your domain, and how enum verifies ownership through the parent's delegation."
---

Anyone can create a DNS zone for a domain they don't own. Nothing stops them: at that moment the zone is a row in a database, and the provider has no way of knowing who the domain belongs to. What a provider does about that gap decides whether a stranger can lock you out of your own domain, or walk off with it.

At the end of July we shipped [enum DNS](https://enum.co/blog/free-dns-forever-for-a-better-european-internet), free for everyone. This post is about that gap, the four ways providers close it, and the two places where the DNS spec and a working product disagree.

## Why we built it

Hyperscalers have built up an impressive catalogue of services over the years. Europe, meanwhile, still hasn't caught on. We believe Europe needs infrastructure of its own, one that wins on innovation and simplicity.

And while other cloud providers charge per zone, per record, or even per DNS query, we think DNS, as the backbone of the internet, should be free. Switching your authoritative nameservers is the cheapest step towards European sovereignty there is, whether that is for your company or just for your side projects.

## The two moving parts

Everything at enum is driven by a reconciler called `enumd`. The pattern is the one most people know from Kubernetes: you declare what a resource should look like, and the reconciler works in the background until the real world matches. Alongside it runs the observer, which watches for drift, the actual state having moved away from the declared state, and releases a resource for another reconcile when it finds any. No callbacks, no manual intervention. The system converges on its own.

For the data plane we run an established open source authoritative nameserver, picked after a long evaluation. It came down to performance and to the API the reconciler uses to manage zones and records. Native replication on top of that keeps enum DNS highly available.

We know better than to reinvent the wheel everywhere. Open source carries all cloud infrastructure, and we believe in the power of open software. It is also why we are members of the CNCF and the Linux Foundation.

## Proving a domain is yours

Moving a domain to enum starts with [`enumctl`](https://docs.enum.co/cli/overview/) and `enumctl dns zones create example.com`. Before a single record of yours goes live, we have to be sure the domain really belongs to the person creating it. There are four ways to establish that, and three of them are worse than they look.

### 1. First to create wins

The simple rule: you create a zone, the zone is taken, nobody else may create it. And that is exactly the problem, because it hands any user a denial of service against every other user.

**Example**: Bob owns the domain `bobshop.com` and wants to move it to MyDNS24. But he has reckoned without Mallory, who creates zones for already registered domains at MyDNS24 in bulk. When Bob tries to create his `bobshop.com` zone and move his nameservers, he gets an error: "domain already taken".

This is not hypothetical. Several DNS providers we tested work exactly this way. It is trivial to implement, ownership never has to be proven at any point, and getting a wrongly blocked domain released means going through the provider's support.

### 2. First to delegate wins

The intuitive alternative is not to enforce uniqueness on zone creation at all, and to let the delegation decide instead. That one is only better at first glance.

**Example**: Bob wants to move `bobshop.com` to BettererDNS. BettererDNS tells him to change the NS records at his registrar to `ns1/ns2.betterer-dns.cloud`, and he does. BettererDNS now checks at intervals whether the NS records of `bobshop.com` point at its own nameservers.

Mallory is watching Bob's domain and notices the nameserver change. She creates a `bobshop.com` zone at BettererDNS too, which nothing prevents.

Now BettererDNS runs its check again, and this is where it gets tricky. Both Bob and Mallory have a `bobshop.com` zone. The NS records of `bobshop.com` point at `ns1/ns2.betterer-dns.cloud`. So who is the rightful owner? If BettererDNS does not distinguish between the two and instead picks, say, the most recently created zone, Mallory has just taken over Bob's domain.

The DoS is gone, but a race condition took its place.

```mermaid
sequenceDiagram
    participant Bob
    participant TLD as .com nameservers
    participant BettererDNS
    participant Mallory

    Bob->>BettererDNS: create zone bobshop.com
    Bob->>TLD: set NS to ns1/ns2.betterer-dns.cloud
    Mallory->>BettererDNS: create zone bobshop.com
    BettererDNS->>TLD: which nameservers serve bobshop.com?
    TLD-->>BettererDNS: ns1/ns2.betterer-dns.cloud
    Note over BettererDNS: the answer matches both zones
    BettererDNS-->>Mallory: zone activated
```

### People have been warning about this for years

The GitHub project "[Can I Take Over DNS?](https://github.com/indianajson/can-i-take-over-dns)" has been documenting this for years and lists plenty of large DNS providers where a DNS takeover is possible. Names like Linode, Name.com, and in edge cases even Google and Azure.

### 3. A second factor

The third option is to have the user create a TXT record at their current DNS provider, one the new provider generates uniquely for that zone. Same idea as Google Site Verification or Vercel's domain verification.

Proof of ownership is unambiguous, and we still rejected it. It is an extra step, in a second control panel, at the provider you are trying to leave, and it does not follow from anything the user is already doing.

### 4. Doing it right

When you create a DNS zone at enum, you get assigned a random pair of two nameservers, and that pair is stable for the entire enum project. `enumctl dns zones create example.com` hands them back to you to set at your registrar, in this example `curie.ns.enum.co` and `planck.ns.enum.co`. Attentive readers will have noticed that our DNS servers are named after well-known European researchers.

**Example**: Bob moves `bobshop.com` to enum and is assigned `curie` and `planck`. If Mallory now shows up wanting to take over `bobshop.com`, she is out of luck, because her enum project has the nameservers `gauss` and `bohr`.

The reconciler now checks in the background which nameservers `bobshop.com` is delegated to. To do that we don't ask the domain itself, we ask the `.com` nameservers. A regular NS query would be answered from the zone apex, and we serve that apex ourselves, so we would only read back what we published a moment earlier. The parent holds the delegation, and the parent is the one link in this chain we cannot write to. If Bob's `curie` and `planck` are both listed there, ownership is confirmed and unambiguously tied to one project.

```mermaid
sequenceDiagram
    participant Bob
    participant TLD as .com nameservers
    participant Enum as enum
    participant Mallory

    Bob->>Enum: create zone bobshop.com
    Enum-->>Bob: use curie and planck
    Mallory->>Enum: create zone bobshop.com
    Enum-->>Mallory: use gauss and bohr
    Bob->>TLD: set NS to curie and planck
    Enum->>TLD: which nameservers serve bobshop.com?
    TLD-->>Enum: curie and planck
    Note over Enum: only Bob's pair is delegated
    Enum-->>Bob: verified, records go live
    rect rgba(200, 60, 60, 0.18)
    Enum-->>Mallory: claim stays unverified, nothing served
    end
```

#### What if both create the zone?

At enum any project may create a zone for `bobshop.com`, several at the same time if it comes to that. We block nothing, because blocking was the whole problem in point 1.

In the live DNS the zone still exists only once. As long as nobody is verified, the apex NS set is the union of all claims. If Bob and Mallory both create `bobshop.com`, four nameservers are listed there: `curie`, `planck`, `gauss` and `bohr`. That is necessary for the registrar pre-check to work for both of them, since each one needs to see their own nameservers answering.

As soon as Bob has switched his NS records, the reconciler sees `curie` and `planck` at the parent. Bob is verified, the apex NS set collapses to his pair, and Mallory's claim is left sitting there.

Mallory was never able to do anything during any of this. An unverified claim publishes NS and SOA and nothing else, never a single record. Her entries sat in our database and were never served.

#### How long does that take?

That depends on your registrar and on how quickly the change reaches the TLD. The observer checks regularly in the background, but you can trigger the check yourself at any time:

```
$ enumctl dns zones verify bobshop.com
```

#### Taking no chances

The pool of european scientists is finite, so two projects can end up with the same pair. On its own that means nothing, because a pair only has to tell claims apart when two projects claim the same domain. If that does happen and the two share a pair, that one domain gets its own randomly generated pair to verify against, instead of the project's default.

#### When the delegation disappears again

The check runs continuously. If the NS records of a verified zone stop pointing at enum, we wait 48 hours. If nothing changes, we take the zone out of the live DNS and the claim drops back into verification. A brief hiccup at the registrar or a flaky lookup does not start the clock, only a delegation that genuinely points somewhere else.

That way nobody gets switched off in the middle of a migration, and a domain that moved on long ago does not stay stuck with us forever.

Claims that were never verified are cleaned up after 28 days, which also means Mallory's stockpile of created zones drains by itself.

## What happens to your records

As soon as the zone exists, you can create records (`enumctl dns records create www.example.com A 203.0.113.15`) or import them (`enumctl dns zones import example.com -f example.com.zone`), before your ownership has been confirmed. The reconciler only takes them live once it is. Until then they sit in the database, visible to you and to nobody else.

### The SOA record

There is one exception, and it is the SOA (Start of Authority) record. Some registrars do not let you simply change the NS records of a domain. They first ask the new nameservers directly whether the zone exists there at all, and refuse the change if no answer comes back.

That is a catch-22. We confirm ownership through delegation, meaning the domain's NS records have to point at enum. But to change those NS records at the registrar, the zone has to be answering at our end already.

So the zone does go live immediately, just not with any of your records. Only with the SOA and the NS set at the apex.

```
$ enumctl dns zones create bobshop.com
$ dig SOA bobshop.com @curie.ns.enum.co +short
curie.ns.enum.co. dns.enum.co. 2026080301 10800 3600 604800 3600
```

## Tricking the RFC

This is an everyday case: you run a marketing site, you don't host it yourself, and the domain itself should point at that external service via a CNAME record.

DNS does not allow a CNAME record to exist alongside other records, per RFC 1034:

> If a CNAME RR is present at a node, no other data should be present; this ensures that the data for a canonical name and its aliases cannot be different.

At the apex of the domain, the root of the zone (`example.com`), NS and SOA records have to be present regardless. So a CNAME at the apex is not allowed.

enum DNS allows it anyway. When a CNAME record is created for the domain itself, `enumctl dns records create example.com CNAME external-site.com`, the reconciler resolves the target into ordinary A and AAAA records and keeps them up to date.

```
$ dig +noall +answer external-site.com A external-site.com AAAA
external-site.com.  300  IN  A     203.0.113.42
external-site.com.  300  IN  AAAA  2001:db8:1::42
```

If the target changes its address, that update happens on its own: on its next pass the observer sees the difference between desired and actual state, and the reconciler writes the new addresses into the zone.

We resolve the target once, from our vantage point, and serve that one answer to everybody. If the target is geo-aware or load balanced, its own nameservers would hand different clients different addresses, and flattening collapses all of that into whatever we happened to see. Every provider that flattens at the apex has this limitation, ours included.

The TTL is ours, not the target's. The flattened A and AAAA records carry the TTL you set on the apex record, and whatever TTL the target published is discarded. Set it low if the target moves around.

So an end user visiting the domain never gets the CNAME record that was created, since that would violate the RFC. They get A and AAAA records back.

```
$ dig +noall +answer bobshop.com A bobshop.com AAAA
bobshop.com.  300  IN  A     203.0.113.42
bobshop.com.  300  IN  AAAA  2001:db8:1::42
```

### Why not just let the nameserver handle it

Authoritative nameservers can do apex aliases themselves, through a native ALIAS record. We deliberately do not use that.

A native ALIAS is only resolved at query time. The answer comes into existence the moment the query arrives and never sits in the zone, which means it cannot be signed. A zone with DNSSEC would serve addresses without a signature at the apex, and any validating resolver throws those away.

So we resolve one level earlier, in the reconciler. What ends up in the zone are ordinary A and AAAA records, and they get signed like everything else.

## Try it

[enum DNS](https://enum.co/dns) is free. Run `enumctl dns zones create example.com`, set the two nameservers at your registrar, and the rest happens on its own. See the [docs](https://docs.enum.co/cli/overview/) for BIND zone file import, DNSSEC and everything else `enumctl` can do.
