> ## Documentation Index
> Fetch the complete documentation index at: https://docs.celum.io/llms.txt
> Use this file to discover all available pages before exploring further.

# DNS

> The node DNS on every node, the central DNS that holds the policy, Global DNS for every supervisor, and the query log behind the Security DNS views.

Celum can answer every DNS query on a supervisor itself: a **node DNS** on each node caches and logs, and one **central DNS** holds the policy — where names are forwarded and which ones get a fixed answer. Every query, to any server, can be made to pass through it, which is what fills the DNS views under Security.

Two pages configure it:

* **DNS** in the sidebar's global section (`/dns`) — **Global DNS**, declared once for every supervisor: forwarders, rules, blocking of encrypted DNS, and the malicious-domain sinkhole.
* **Networking → DNS** on a supervisor — the switches that run it there, and what that supervisor adds to or overrides from Global.

Nothing changes until you turn it on: a supervisor where **Run the DNS** is off resolves exactly as its CoreDNS always did.

## How a query travels

```text theme={null}
pod / VM ── :53 to kube-dns ──▶ node DNS on the same node            (the redirect)
pod / VM ── :53 to anything else ──▶ node DNS on the same node      (capture)
node DNS ── query log ──▶ cluster agent ──▶ ClickHouse dns_queries_local
  cluster names and reverse names ──▶ the cluster's CoreDNS
  everything else ──▶ central DNS ── rules ──▶ their servers, or a fixed answer
                                 ── the rest ──▶ the forwarders
```

* The **node DNS** is one CoreDNS per node, running the same image as the cluster's own CoreDNS. It caches and sends every answered query to the cluster agent on its node.
* The **central DNS** is two CoreDNS pods that hold the rules and forwarders, and filter encrypted-DNS server names and malicious domains when those blocks are on.
* The **redirect** is a Cilium Local Redirect Policy on `kube-dns`: queries to the cluster's DNS address go to the node DNS on the same node. It is a socket-level service redirect, not a proxy, so a cilium-agent restart loses no queries.
* **Capture** covers workloads that pick their own resolver: in each pod's and VM's network namespace the cluster agent rewrites every query to port 53 to the node DNS on the same node.

## Turning it on, in order

Each switch on **Networking → DNS** takes effect on **Apply**, and each needs the one before it.

**1. Turn on Local Redirect Policies**

The Status block offers this when Cilium has them off. It restarts every cilium-agent once, node by node, which briefly interrupts DNS and the traffic Cilium proxies on that node. Nothing is redirected yet.

**2. Run the DNS**

Starts a node DNS pod on every node and two central DNS pods, plus a cluster-wide policy that lets every pod reach the node DNS. Nobody's DNS changes yet.

**3. Redirect kube-dns to the node DNS**

From here, a query to the cluster's DNS is answered by the node DNS. Needs Local Redirect Policies.

**4. Capture every query (DNAT)**

A workload with a resolver of its own is answered — and logged — by the node DNS all the same. Needs the redirect, and rolls the cluster agents once.

**5. Deny DNS anywhere else**

Cluster-wide policies deny UDP and TCP 53 and 853 (DNS over TLS) from every pod to anything but the node DNS, the central DNS, CoreDNS and a vcluster's own DNS. Needs capture, and is applied only once every node reports full capture; it is held back while a VM with bridge binding exists.

**6. Query log**

Every answered query goes to ClickHouse through the cluster agent on its node. It can be on without capture, but then a workload with its own resolver is not in it. Turning it on or off rolls the cluster agents once.

**Result:** the tiles at the top of the tab read Node DNS, Central DNS, Capture and Query log as on and running, and the Status block lists the redirected pods and the policies that are in force.

> [!WARNING]
>
> **Not captured:** hostNetwork pods, the DNS pods themselves, and VMs with **bridge binding** — the nodes of guest clusters. A bridged VM's pod has no address to rewrite to, so its queries are left alone; guest nodes are pointed at the supervisor's DNS through their Talos configuration instead. The Status block lists bridged VMs per node.

## Forwarders, cache and rules

**Forwarders** are where the central DNS sends every name no rule takes, tried in order. Empty means the node's own resolvers, the ones the cluster's CoreDNS forwards to. Names under the cluster domain and every reverse name always go to the cluster's CoreDNS.

**Cache (seconds)** is the longest an answer is kept, on the node DNS and the central DNS alike. Answers about missing cluster names are kept 5 seconds at most.

A **rule** matches one of:

* a **zone** — the name and everything under it;
* an **exact name**;
* a **pattern**, where `*` is any characters;

optionally only for some record types. It either **forwards** to servers of its own, or **answers** with fixed values of one type (A, AAAA, MX or TXT; no CNAME). A fixed answer makes the name local: every other record type of it is answered with no record, so a client asking AAAA for a name with a fixed A does not conclude the name does not exist.

Rules are applied most specific first, whatever order you declared them in: exact names, then patterns, then zones from the deepest, and a rule limited to record types before one that is not.

> [!NOTE]
>
> A zone that a rule forwards to its own servers is never retried against the cluster's resolver. If those servers are down, clients get an uncached SERVFAIL and retry, rather than the public view of an internal zone answering NXDOMAIN and being cached.

## Global DNS and what a supervisor overrides

Global DNS holds the same forwarders and rules, for every supervisor at once:

| What | On a supervisor |
| - | - |
| **Forwarders** | Used where the supervisor declares none. A supervisor's forwarders replace Global's. |
| **Rules** | Added to the supervisor's own. A supervisor rule with the same zone, name or pattern replaces every Global rule of that match, whatever record types either names. A Global rule inside the supervisor's own cluster domain is skipped there. |
| **Malicious-domain sinkhole** | Global's addresses, unless the supervisor sets its own; an address set on the supervisor replaces both Global ones. |
| **DNS honeypot** | **As Global**, **On** or **Off** per supervisor. |

Saving Global DNS rewrites every supervisor's central DNS at once; a supervisor that was unreachable catches up within five minutes. CoreDNS takes a minute or two to reload.

> [!NOTE]
>
> A supervisor registered in two Celum installations (production and a test bench, say) is reconciled only by the one that last saved its DNS, so the two do not overwrite each other's policy every few minutes. The other one says the supervisor is managed elsewhere.

### Blocking encrypted DNS (DoH, DoT, DoQ)

Keeps DNS from leaving a supervisor uninspected over an encrypted channel. With **Block DNS over HTTPS, TLS and QUIC** on, the central DNS answers NXDOMAIN for the listed server names and every name under them, so a client that looks its server up gets no address.

* The list is Celum's shipped list of public resolvers (Cloudflare, Google, Quad9 and others), edited under **Manage servers and lists**, plus downloaded lists of names.
* **Nothing is blocked by address.** A client with its server's address written in is not stopped here; the [network policies](https://docs.celum.io/security/policies.md) are where an address is denied.
* A name a supervisor's own rule forwards elsewhere does not pass the filter.

### Answering malicious domains with a sinkhole

With **Answer malicious domains with the sinkhole** on, every name on the lists — exactly that name, not the names under it, because lists name hosts on shared platforms — is answered with the sinkhole address: A with the IPv4 one, AAAA with the IPv6 one, and every other record type with nothing.

* `0.0.0.0` refuses at once; an internal block page shows the user why.
* **Manage lists** holds the downloaded lists (fetched once a day while the block is on, or with **Download now**), names of your own, and names that are never sinkholed whatever a list says.
* Who asked for a sinkholed name shows in **Security → Blocked DNS**.

**Honeypot instead** answers listed names with a honeypot rather than a sinkhole: one pod per supervisor on the first address of its egress band, reachable from every project, isolated ones included. It accepts every TCP and UDP port and ICMP, and records who connected and what they tried — TLS server name, HTTP request, first bytes — in **Security → DNS Honeypot**.

The lists reach every supervisor as ConfigMaps, so filtering keeps working while Celum is out of reach.

## The query log

Every answered query is written to `dns_queries_local` in the supervisor's ClickHouse, with the client named — namespace, pod, workload, project, guest cluster — the upstream that answered, the latency, and whether it came from the cache. Rows are kept 14 days, and per-minute rollups 30 days.

It is read on the Security pages:

| View | What it answers |
| - | - |
| **DNS Queries** | Which names were asked, by whom, with what outcome, record type and cache result |
| **Blocked DNS** | Who asked for an encrypted-DNS server or a sinkholed name |
| **DNS Honeypot** | Who connected to the honeypot, and what they sent |

An empty DNS view usually means the query log is off on that supervisor, or the node DNS is not running — the tiles on **Networking → DNS** say which.

## API and MCP

| Task | Route | MCP | Permission |
| - | - | - | - |
| Read / change a supervisor's DNS | `GET` / `PUT /api/supervisors/{name}/networking/dns` | `get_node_dns`, `set_node_dns` | `cilium:GetState` / `cilium:Apply` |
| Read / change Global DNS | `GET` / `PUT /api/dns` | `get_global_dns`, `set_global_dns` | `settings:Read` / `settings:Update` |
| Download the lists now | `POST /api/dns/feeds/refresh` | `refresh_dns_feeds` | `settings:Update` |
| Read the query log | `GET /api/supervisors/{name}/security/flows/dns-queries` and its `summary`, `top`, `resolution`, `block-source` | `list_dns_queries`, `get_dns_query_summary`, `get_dns_query_top`, `get_dns_query_resolution`, `get_dns_block_source` | `security:Read` |
| Blocked attempts, honeypot | `GET .../security/flows/dns-blocked`, `.../dns-honeypot` | `get_dns_blocked_attempts`, `list_dns_honeypot_events` | `security:Read` |

Turning on Local Redirect Policies is `POST /api/supervisors/{name}/networking/cilium-feature` with `{"feature": "localRedirectPolicies", "enabled": true}`, authorised by `cilium:Apply`.

## What commonly goes wrong

| Symptom | Cause |
| - | - |
| **Redirect kube-dns** cannot be turned on | Local Redirect Policies are off in Cilium. Turn them on from the Status block first; it restarts the cilium-agents once |
| **Deny DNS anywhere else** is on but not in force | Not every node reports full capture yet, or a VM with bridge binding exists. The Status block says which |
| A workload's own resolver still answers it | Capture is off, or the workload runs on the host network, which is never captured |
| An internal name returns SERVFAIL for a while | Its zone is forwarded by a rule to servers that are down. That is deliberate: the alternative was the public view caching NXDOMAIN |
| The DNS views under Security are empty | The query log is off, or the node DNS is not running on that supervisor |
| A Global change has not reached a supervisor | It was unreachable; the reconciler catches up within five minutes, and CoreDNS reloads a minute or two after that |
| A client still reaches an encrypted DNS server | It has the server's address written in. Encrypted DNS is blocked by name only |

## Related

> [card] [Networking overview](https://docs.celum.io/networking/overview.md)
>
> The Cilium datapath the redirect and capture sit on.

> [card] [Network policies](https://docs.celum.io/security/policies.md)
>
> Denying an address, which DNS blocking does not do.

> [card] [Security overview](https://docs.celum.io/security/overview.md)
>
> Where the DNS views sit among the Security tabs.

> [card] [Pools & IPAM](https://docs.celum.io/networking/pools-and-ipam.md)
>
> The egress band whose first address the honeypot takes.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.