Hetzner DNS management fails for DKIM keys

Issue Description

I setup automatic DNS management and DKIM management using Hetzner DNS. The Perform DNS management task fails with response code 409 / 422 when trying to create a domain key.

Expected Behavior

DKIM DNS management should work with Hetzner DNS.

Actual Behavior

Failed to create DNS record for example.com.: Failed to create DNS record: API error: Invalid HTTP response code 409: Err(reqwest::Error { kind: Status(409, None), url: "https://api.hetzner.cloud/v1/zones/example.com/rrsets" }); Failed to create DNS record for v1-rsa-20260520._domainkey.example.com.: Failed to create DNS record: API error: Invalid HTTP response code 422: Err(reqwest::Error { kind: Status(422, None), url: "https://api.hetzner.cloud/v1/zones/example.com/rrsets" })

Stalwart Version

v0.16.x

Installation Method

Docker

Database Backend

PostgreSQL

Blob Storage

PostgreSQL

Search Engine

PostgreSQL

Directory Backend

Internal

I have reviewed the documentation and FAQ and confirm that my issue is NOT addressed there.

on

I have searched this support forum (open and closed topics) and confirm this is not a duplicate.

on

I understand that topics in this category are triaged by a bot first but a human reply will follow up. If I’d prefer a human-only reply, I’ll add the no-ai tag to my topic.

on

The Hetzner DNS integration is implemented inside the dns_update crate (an external dependency), not in Stalwart’s own code. Let me check the error URL and Hetzner’s API details.

The error URL in the logs (https://api.hetzner.cloud/v1/zones/...) is noteworthy – Hetzner actually has two separate DNS APIs: the Hetzner Cloud API (api.hetzner.cloud) and the Hetzner DNS API (dns.hetzner.com). The DNS management records API is at dns.hetzner.com, not api.hetzner.cloud. The Hetzner Cloud API endpoint is for virtual machine/cloud resources and will reject DNS record creation requests (hence the 409/422 responses).

The error logs show that Stalwart is hitting https://api.hetzner.cloud/v1/zones/... - but that’s the Hetzner Cloud API endpoint. The correct Hetzner DNS API base URL is https://dns.hetzner.com/api/v1/. This suggests the wrong API token/endpoint is being used.

Additionally, the error messages are worth noting:

  • HTTP 409 on the zone itself - a “conflict”, meaning a record already exists and Stalwart is trying to create rather than update it.
  • HTTP 422 on the DKIM _domainkey TXT record - “unprocessable entity”, which could indicate malformed data or an API schema mismatch.

Here are the things to check:

  1. Wrong API endpoint being used. Stalwart’s Hetzner DNS integration is built against the Hetzner DNS API (dns.hetzner.com), not the Hetzner Cloud API (api.hetzner.cloud). The error URL shows api.hetzner.cloud being called. Make sure you are using a Hetzner DNS API token (created at dns.hetzner.com), not a Hetzner Cloud project API token. These are separate services with separate tokens.

  2. Pre-existing records causing 409. If DNS records for this zone already exist in Hetzner DNS (e.g., you had previously configured them manually), the create call conflicts with existing entries. You may need to remove any pre-existing records for the affected names before letting Stalwart manage them automatically.

  3. DKIM TXT record length causing 422. RSA DKIM public keys are long TXT strings. Some DNS providers require long TXT values to be split into multiple quoted 255-byte chunks. If Stalwart is sending the full key as a single string, Hetzner may reject it with 422. This would be a compatibility issue between Stalwart’s Hetzner DNS implementation and Hetzner’s API requirements.

The list of natively supported hosted DNS providers in the current docs is: Cloudflare, AWS Route 53, Google Cloud DNS, OVH, deSEC, DigitalOcean, Bunny DNS, Porkbun, DNSimple, and Spaceship. Hetzner is present in the server source code as an integrated provider, but the errors you’re seeing suggest an API endpoint mismatch or a bug in the Hetzner integration’s handling of long TXT records. This looks like a bug that will need a human to investigate further.

As a workaround in the meantime, you can switch DNS management to Manual mode and publish the records from the dnsZoneFile field yourself via the Hetzner DNS console.

Relevant docs: DNS Providers

This is an automated reply from the Stalwart Help Bot. Other community members may follow up if this answer is incomplete or wrong. If you found it useful, mention @helpbot in any reply to ask follow-up questions and the bot will respond again.

@helpbot , your information is outdated. The Hetzner DNS service and API is discontinued. DNS records now live on Hetzner Cloud API. So, Stalwart using the Hetzner Cloud API is correct.

Let me look at the actual Hetzner DNS implementation in the server source to understand what’s happening with the 409/422 errors.

The Hetzner DNS integration in Stalwart is provided by the external dns-update crate (version 0.4.0). The actual API endpoint logic (what URL is called for Hetzner) lives in that upstream crate, not in Stalwart itself. The errors you’re seeing - 409 and 422 - are coming back from the Hetzner Cloud API, which means Stalwart is reaching the right endpoint.

Given that Hetzner consolidated DNS into their Cloud API, the issues are likely:

  • HTTP 409 (Conflict): Stalwart is trying to create a record that already exists. The Hetzner Cloud API returns 409 when you POST a record that conflicts with an existing one. You may need to manually delete any pre-existing records for that zone/name in the Hetzner Cloud DNS console so Stalwart can create them fresh.
  • HTTP 422 (Unprocessable Entity): This is most likely a problem with how the DKIM TXT record value is being submitted - either a data formatting issue or a length/chunking issue with the RSA public key. This points to a potential bug in the dns-update crate’s Hetzner implementation regarding long TXT records.

Since the actual Hetzner API client code is in the upstream dns-update crate rather than Stalwart’s own source, this isn’t something that can be diagnosed or fixed purely within Stalwart. A human maintainer will need to look at this to determine whether it’s a bug in Stalwart’s handling or in the upstream dependency.

As a workaround, you can switch to Manual DNS management and publish the records shown in the dnsZoneFile field yourself via the Hetzner Cloud DNS console.

This is an automated reply from the Stalwart Help Bot. Other community members may follow up if this answer is incomplete or wrong. If you found it useful, mention @helpbot in any reply to ask follow-up questions and the bot will respond again.

I see the same error in my logs:

(most records cut for brevity, all successful)

2026-05-21T07:01:07Z INFO DNS record created (dns.record-created) hostname = “_submissions._tcp.example.com.”, details = “example.com”, type = “SRV”, value = “0 1 465 mail.example.com.”

2026-05-21T07:01:07Z INFO DNS record created (dns.record-created) hostname = “_smtp._tls.example.com.”, details = “example.com”, type = “TXT”, value = “v=TLSRPTv1; rua=mailto:[email protected]”

2026-05-21T07:01:07Z WARN Task failed during processing (task-manager.task-failed) id = 30…0992, details = “DnsManagement”, reason = “Failed to create DNS record for example.com.: Failed to create DNS record: API error: Invalid HTTP response code 409: Err(reqwest::Error { kind: Status(409, None), url: “https://api.hetzner.cloud/v1/zones/example.com/rrsets” })”

So, there are a many successes reported, and then this one failure. I don’t know what this failed request tried to do.

I do see the DNS records being modified. The notifications in Hetzner Console show changes made by Stalwart on that domain.

more information shared here: Support Hetzner DNS for automatic DNS management - #5 by P4sca1

This has been fixed but could not be tested as we do not have an account with your DNS provider. If you are familiar with Rust (or have an LLM to help you) we suggest you try running a quick test using the dns-update crate (with the latest code from main).

I seem to have the same problem (Stalwart 0.16.6):

details = “DnsManagement”, reason = “Failed to create DNS record for 202503r._domainkey.ocrs.at.: Failed to create DNS record: API error: Invalid HTTP response code 422: Err(reqwest::Error { kind: Status(422, None), url: “https://api.hetzner.cloud/v1/zones/ocrs.at/rrsets” }); Failed to create DNS record for _443._tcp.ua-auto-config.ocrs.at.: Failed to create DNS record: API error: Invalid HTTP response code 409: Err(reqwest::Error { kind: Status(409, None), url: “https://api.hetzner.cloud/v1/zones/ocrs.at/rrsets” }); Failed to create DNS record for _443._tcp.autodiscover.ocrs.at.: Failed to create DNS record: API error: Invalid HTTP response code 409: Err(reqwest::Error { kind: Status(409, None), url: “https://api.hetzner.cloud/v1/zones/ocrs.at/rrsets” }); Failed to create DNS record for _443._tcp.mta-sts.ocrs.at.: Failed to create DNS record: API error: Invalid HTTP response code 409: Err(reqwest::Error { kind: Status(409, None), url: “https://api.hetzner.cloud/v1/zones/ocrs.at/rrsets” }); Failed to create DNS record for _443._tcp.autoconfig.ocrs.at.: Failed to create DNS record: API error: Invalid HTTP response code 409: Err(reqwest::Error { kind: Status(409, None), url: “https://api.hetzner.cloud/v1/zones/ocrs.at/rrsets” })”

However the records seem to be created when I’m looking in the Hetzner console.