-
Notifications
You must be signed in to change notification settings - Fork 756
Feature/hetzner dns provider #5091
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Feature/hetzner dns provider #5091
Conversation
Add native support for Hetzner Cloud DNS API (api.hetzner.cloud). Hetzner is migrating from dns.hetzner.com to Cloud Console, with the old API shutting down in May 2026. Features: - Bearer token authentication - A and AAAA record support - Multiple hostnames (comma-separated) - Configurable TTL
## Features - Multi-account support (multiple Hetzner API tokens) - Multi-zone DNS management - Dynamic DNS with automatic failover between WAN interfaces - IPv4 and IPv6 (Dual-Stack) support - Direct DNS management (view/edit/delete records) - Change history with undo functionality - Notifications (Email, Webhook, Ntfy) - Configuration backup/restore Supports both Hetzner Cloud API and legacy DNS Console API.
Bugfixes: - Fix DNS record edit mode (editing TTL no longer fails with "Failed to create record") - Fix error dialog titles showing "Danger" instead of "Error" - Add detailed error messages with record info for debugging - Fix TTL dropdown not being populated Improvements: - Integrate notifications into automatic DNS update flow - Add global default TTL setting for DynDNS entries (60s default) - Add "Save & Apply TTL to All Entries" button - Move DynDNS TTL settings from Scheduled to DNS Entries tab - Simplified TTL settings UI
- Fix TTL updates and add TTL dropdown selector - Add plugin concepts for UniFi and MikroTik integration - Integrate notifications into automatic DNS update flow - Add global default TTL setting for DynDNS entries - Add "Apply TTL to All Entries" button - Fix DNS record edit mode and improve error dialogs - Move DynDNS TTL settings from Scheduled to DNS Entries tab - Fix TTL dropdown and simplify settings layout - Auto-save TTL before applying to all entries - Simplify TTL UI to single Save & Apply button - Move Import to DNS Entries, add DynDNS button to DNS Management - Add grouped record display with filter and search in DNS Management
### DNS Management Improvements - Sort zone groups alphabetically - Persist collapsed group state in localStorage - Fix zone search visibility - move to separate container - Preserve collapsed group state on refresh - Add DNS Management improvements: sorting, search, zone groups ### Import & Account Handling - Fix Import from Hetzner account dropdown ### DKIM & TXT Records - Fix DKIM wizard field sizes - Improve TXT record value display with smart formatting - Show TXT record subtypes (SPF, DKIM, DMARC, Google, MS) in DNS Management ### DynDNS Entries - Fix default TTL loading from settings API - Use default DynDNS TTL when creating entries from DNS Management - Mark A/AAAA records already configured as DynDNS with green bolt icon ### Record Edit/Delete - Add synthetic record IDs for rrsets API - Fetch record data live from API for edit/delete - Refactor edit/delete to lookup record data from cache - Fix edit/delete handlers for new TXT value display ### UI/UX - Fix gateway auto-selection to use sorted order instead of exact priority values - Add grouped record display with filter and search in DNS Management - Move Import to DNS Entries, add DynDNS button to DNS Management - Simplify TTL UI to single Save & Apply button
|
@ArcanConsulting We're not really sure yet if this fits our plugin scope, reviewing these large amounts of code takes a lot of time and we're not sure about the number of users interested in it. Keeping a project like this alive, also requires a time investment in the long run from your end. Are there already people using this? In some cases it's better to offer a package from your own infrastructure to avoid maintenance issues in the long run from our end which also makes clearer for the user that this isn't a part of our distribution. |
I built this primarily for my own infrastructure - I manage ~30 domains on Hetzner and needed proper multi-zone DynDNS with failover support. The existing solutions didn't cover my requirements. I understand the review burden for a plugin this size. I'm happy to maintain it as a community package from my own repo - that removes the long-term maintenance concern from your side. If it gains traction and proves stable, we can revisit official inclusion. Could you point me to docs on setting up a community package repo? |
|
building the index is part of the package manager |
|
The rough sequence is as seen in the tools repo: From a project perspective it's not useful to document how all of this works. Other documentation about it exists in FreeBSD. Cheers, |
| """Get existing record by name and type""" | ||
| url = f"{self._api_base}/zones/{zone_id}/rrsets/{record_name}/{record_type}" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note that the API support using the zone name as path parameter, so you would have the following:
| """Get existing record by name and type""" | |
| url = f"{self._api_base}/zones/{zone_name}/rrsets/{record_name}/{record_type}" |
| response = requests.put(url, headers=headers, json=data) | ||
| NOTE: Hetzner Cloud API has a bug where PUT returns 200 but doesn't update. | ||
| Workaround: DELETE old record, then POST new record. | ||
| """ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Updating records cannot be done with PUT /.../rrsets/..., you must use one of the following endpoint:
- https://docs.hetzner.cloud/reference/cloud#zone-rrset-actions-set-records-of-an-rrset
- https://docs.hetzner.cloud/reference/cloud#zone-rrset-actions-add-records-to-an-rrset
- https://docs.hetzner.cloud/reference/cloud#zone-rrset-actions-remove-records-from-an-rrset
Also note, that those endpoint return "Actions", which describe async tasks that should be waited upon.
| 'ttl': int(self.settings.get('ttl', 300)) | ||
| } | ||
|
|
||
| response = requests.post(url, headers=headers, json=data) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The response holds an "Action", that should be waited upon.
See https://docs.hetzner.cloud/reference/cloud#zone-rrsets-create-an-rrset
| """Update existing record with new address | ||
| NOTE: Hetzner Cloud API has a bug where PUT returns 200 but doesn't update. | ||
| Workaround: DELETE old record, then POST new record. | ||
| """ | ||
| # DELETE old record first | ||
| delete_url = f"{self._api_base}/zones/{zone_id}/rrsets/{record_name}/{record_type}" | ||
| delete_response = requests.delete(delete_url, headers=headers) | ||
|
|
||
| if delete_response.status_code not in [200, 201, 204]: | ||
| syslog.syslog( | ||
| syslog.LOG_ERR, | ||
| "Account %s error deleting record for update: HTTP %d - %s" % ( | ||
| self.description, delete_response.status_code, delete_response.text | ||
| ) | ||
| ) | ||
| return False | ||
|
|
||
| # CREATE new record | ||
| return self._create_record(headers, zone_id, record_name, record_type, address) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Updating records cannot be done with PUT /.../rrsets/..., you must use one of the following endpoint:
- https://docs.hetzner.cloud/reference/cloud#zone-rrset-actions-set-records-of-an-rrset
- https://docs.hetzner.cloud/reference/cloud#zone-rrset-actions-add-records-to-an-rrset
- https://docs.hetzner.cloud/reference/cloud#zone-rrset-actions-remove-records-from-an-rrset
Also note, that those endpoint return "Actions", which describe async tasks that should be waited upon.
| class HetznerCloudAPI: | ||
| """ | ||
| Hetzner Cloud DNS API (api.hetzner.cloud) | ||
| Uses Bearer token authentication and rrsets endpoints | ||
| """ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There is already a https://pypi.org/project/hcloud/ API client that can speak to the DNS API.
API Changes (based on Hetzner feedback):
- Migrate to proper rrset-actions endpoints for record updates
- Use POST /zones/{zone_id}/rrsets/{name}/{type}/actions/set_records
- Add async action polling - wait for success/error status before continuing
Performance:
- Switch from sequential to parallel DNS update processing (ThreadPoolExecutor)
- Deduplicate entries by (zone_id, record_name, record_type) before processing
- Thread-safe state access with locks
Notifications:
- Single batch notification per update run instead of per-entry
- Clean title format with gateway names:
"HCloudDNS: Failover WAN_Primary → WAN_Backup"
"HCloudDNS: Failback WAN_Backup → WAN_Primary"
"HCloudDNS: DynIP Update on WAN_Primary"
- Records listed once in body (no duplication)
- Grouped by domain with proper spacing
| if delete_response.status_code not in [200, 201, 204]: | ||
| data = { | ||
| 'records': [{'value': str(address)}], | ||
| 'ttl': int(self.settings.get('ttl', 300)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
TTL should be updated using another endpoint:
| import requests | ||
| from . import BaseAccount | ||
|
|
||
| ACTION_POLL_INTERVAL = 0.5 # seconds between action status polls |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is rather short, what about 1 second ? Ideally you would use an exponential backoff function.
Summary
New plugin for comprehensive Hetzner DNS management in OPNsense.
Features
Supports both Hetzner Cloud API and legacy DNS Console API.
Screenshots









Technical Details
Testing
Checklist