# Connect your own nameserver (PowerDNS)

A **self-hosted** provider is a nameserver you run yourself, on premises or
on a VPS, next to the cloud providers of a zone. RedundantDNS writes the
zone to it through the **PowerDNS Authoritative HTTP API**, exactly as it
writes to Route 53 or Cloudflare: same canonical zone, same diff, same
drift checks, and your nameserver's names join the zone's NS set.

The quickest way to get a suitable server is the open-source
[`selfhost-ns`](https://github.com/redundantdns/selfhost-ns) bundle
(Docker Compose or systemd, hardened defaults). Any PowerDNS Authoritative
4.x with the API enabled works too.

The API key is encrypted with your organization's key as soon as it
reaches us, is never shown again, and is only used to read and write the
zones you attach.

| Access level | What RedundantDNS can do |
|---|---|
| **Create and manage zones** | Create zones on your server, write their records, and delete a zone when you detach it (only after you type the zone name). |
| **Manage an existing zone** | Read and edit the records of one zone you created on your server. It never creates or deletes zones. |
| **Mirror source (read-only)** | Only read one zone you keep editing on your server. RedundantDNS mirrors it to your other providers and never writes to it. |

PowerDNS API keys are server-wide: every level uses the same key, and the
level only limits what RedundantDNS does with it. Create a dedicated server
(or at least a dedicated key, see below) for RedundantDNS. The API is
enabled in `pdns.conf`:


```
api=yes
api-key=<api key>
webserver=yes
webserver-address=127.0.0.1
webserver-port=8081
webserver-allow-from=127.0.0.1
```

Put the TLS reverse proxy (see below) in front of `127.0.0.1:8081`.

## Read this first: what your server must offer

- **The API, reachable by RedundantDNS over https.** The PowerDNS
  webserver speaks plain http; put a TLS reverse proxy in front of it
  (Caddy, nginx) and allow only RedundantDNS's addresses, or reach it over
  a VPN. RedundantDNS refuses `http://` API URLs and API URLs that resolve
  to private, loopback or link-local addresses; a self-installed platform
  on the same private network as its nameservers can list them in
  `RDNS_SELFHOST_PRIVATE_ALLOWLIST` (CIDRs, IPs or `IP:port` pairs; plain
  http only for a URL whose host is a literal IP in the list).
- **DNS on port 53, UDP and TCP, to the whole internet**, on the addresses
  your nameserver names resolve to.
- **Nameserver names** (`ns1.example.net`) with A/AAAA records pointing to
  the server. These are the names you enter in the connection; they are
  added to the NS set of every zone attached to it. If the names live
  inside the zone they serve, your registrar also needs glue records.
- **No recursion** and a **hidden version**: an authoritative-only server
  (the `selfhost-ns` bundle does this for you).

Record types: A, AAAA, CNAME, MX, TXT, SRV, CAA, NS and PTR, with any TTL
from 1 second, wildcards, and no limit on values per record set. PowerDNS
also serves TLSA, HTTPS, SVCB and more; RedundantDNS will write them once
its canonical model carries them. A CNAME at the zone apex is refused (it
cannot coexist with the SOA and NS records).

## 1. Collect the connection values

| Field | Where it comes from |
|---|---|
| **PowerDNS API URL** | The https URL of the API, e.g. `https://pdns-api.example.net`. A trailing `/api/v1` is accepted. |
| **API key** | The `api-key` of `pdns.conf` (`scripts/init.sh` of `selfhost-ns` generates one and prints it). |
| **Nameservers** | The NS names this server answers as, comma separated: `ns1.example.net, ns2.example.net`. One at least. |
| **Server ID** (optional) | PowerDNS's server id, `localhost` unless you renamed it. |

## 2a. Create and manage zones

In RedundantDNS, **Providers → Add connection**: provider *Self-hosted
(PowerDNS)*, access level *Create and manage zones*, fill the fields above,
then click **Test and save**. We check the key by reading the server
(`GET /api/v1/servers/localhost`) and that it is an authoritative server.

When you attach the connection to a zone, RedundantDNS creates it on your
server as a **Native** zone (no AXFR between PowerDNS servers) with
`soa_edit_api: DEFAULT`, so PowerDNS increases the SOA serial on every
change.

## 2b. Manage an existing zone

Create the zone on your server yourself, for example:

```sh
pdnsutil create-zone example.com ns1.example.net
pdnsutil set-meta example.com SOA-EDIT-API DEFAULT
pdns_control rediscover
```

`SOA-EDIT-API DEFAULT` makes PowerDNS increase the SOA serial on every
change RedundantDNS makes (a zone created with `pdnsutil` has no such
setting, and its serial would stay where it is). `pdns_control rediscover`
makes the running server load the new zone at once: until then, or until
its zone cache refreshes (`zone-cache-refresh-interval`, 300 seconds by
default), it answers `REFUSED` for the zone, and the zone's health shows
degraded even though the records are in sync.

Add the connection with access level *Manage an existing zone*. The key is
checked when you attach the connection to a zone: open the zone,
**Providers → Attach**, pick the connection and give the **zone name**
(`example.com.`; the trailing dot is optional) as the zone ID. We verify
that the zone exists and holds the same domain.

## 2c. Mirror source (read-only)

Use this when the zone stays edited on your server (with `pdnsutil`, your
own tooling or a hidden primary) and RedundantDNS only copies it to your
other providers. RedundantDNS reads the zone with `GET` requests and
**never writes to it**. Add the connection with access level *Mirror source
(read-only)*, or use **Zones → New zone → Mirror an existing zone** and
give the **zone name** (`example.com.`) as the zone ID. The NS plan starts
as the zone's own apex NS set.

## What RedundantDNS writes

- Every record set you manage in the dashboard. A change goes to PowerDNS
  as **one** `PATCH /api/v1/servers/<id>/zones/<zone>` request: `REPLACE`
  for created and updated record sets and `DELETE` for removed ones,
  applied by PowerDNS in a single transaction. Both are idempotent, so a
  retry after a network error converges.
- The apex NS set: the nameservers of **every** active provider of the
  zone, including the names of this connection. A standby attachment
  receives the records only: its apex NS set is left as it is.
- Nothing in the SOA (PowerDNS maintains the serial).

A record you change directly on the server (with `pdnsutil` or the API) is
reported as drift, never overwritten silently. Disabled records are
ignored, and record types RedundantDNS does not model yet (DNSSEC records,
TLSA, ...) are left alone.

## Limits in this version

- **Query metrics** are not read (PowerDNS statistics are server-wide, not
  per zone).
- **AXFR mode** (RedundantDNS as a hidden primary with TSIG, for servers
  whose API cannot be exposed) and several nameserver identities per host
  come next; see the `selfhost-ns` roadmap.
- **DNSSEC** is not managed: if you sign zones on your server, the other
  providers of the zone do not serve the same signatures.

## Troubleshooting

| Message | Meaning |
|---|---|
| `HTTP 401: the API key was rejected` | The key does not match `api-key` in `pdns.conf`. |
| `HTTP 404` on the connection test | The server ID is not `localhost` (check `GET /api/v1/servers`), or the URL points to something that is not the PowerDNS API. |
| `apiUrl must use https` | Put a TLS proxy in front of the API (see above). |
| `the API address is not public` | The API URL resolves to a private or loopback address. |
| `HTTP 422: ... Conflicts with pre-existing RRset` | A CNAME and another record share a name on the server (often a record created by hand). |
| `HTTP 409: Domain 'example.com.' already exists` | The zone already exists on the server: adopt it when the dashboard offers to. |
