Upgrading to Version 4.5.0¶
Warning: Not yet released. Version 4.5.0 is complete on the
masterbranch and awaiting its tag. This guide is prepared in advance and the steps below will only work once v4.5.0 is tagged and published. The latest release is v4.4.1; 4.3.x remains the stable line recommended for production.
Overview¶
Version 4.5.0 centers on serial handling and delegation. SOA-EDIT and SOA-EDIT-API serial policies can be managed from the UI, zone lists can show the signed serial PowerDNS actually serves, and zone templates understand serial placeholders. Access control gets finer-grained: dedicated permissions for viewing logs, zone metadata, and zone ownership, a DNSSEC key-management permission separate from zone editing, delegation NS editing below the apex, and granular API keys (read-only, operation-restricted, zone-scoped).
Also new: a structured record change log with before/after snapshots, per-record-type default TTLs, trusted proxy support, LDAP user-info sync and auto-provisioning, OIDC form_post response mode, per-request API logging, health and ping endpoints for external monitoring, and a headless mode that serves the API without the web interface.
Database Schema Update¶
The 4.5.0 migration is larger than usual. It adds six tables:
| Table | Purpose |
|---|---|
log_changesets |
Groups the record changes made in one edit, with the user, client IP and an optional comment |
log_record_changes |
Structured before/after snapshots of record and zone changes, backing the diff-style change log |
app_settings |
Key/value store for the settings an administrator can change from the UI |
record_type_defaults |
Per-record-type default TTLs managed in the admin UI |
api_key_zones |
Zone scoping for granular API keys |
records_zone_templ_api |
Template-record mapping for API-backend zones with encoded record IDs |
Column changes:
| Change | Purpose |
|---|---|
api_keys.is_readonly, api_keys.allowed_operations |
Granular API key restrictions; existing keys stay unrestricted |
login_attempts.attempt_type |
Separate throttling for password and MFA stages |
password_reset_tokens.token widened |
Hashed (sha256) token storage |
zones.zone_templ_id explicit DEFAULT 0 |
Consistent "no template" sentinel |
oidc_user_links / saml_user_links binary collation |
Byte-exact subject matching (already shipped in 4.2.5/4.3.4; included for upgrades from 4.4.0) |
zone_templ_records.type widened to varchar(10) |
Record types longer than six characters (NSEC3PARAM, OPENPGPKEY, IPSECKEY, CDNSKEY, RESINFO) can now be stored in zone templates |
The migration also registers eleven new permissions (see below) and auto-grants the four metadata/ownership view permissions to templates that already hold the matching zone_content_view_* permission, so nothing users could see before the upgrade disappears.
The migration scripts are:
sql/poweradmin-mysql-update-to-4.5.0.sqlsql/poweradmin-pgsql-update-to-4.5.0.sqlsql/poweradmin-sqlite-update-to-4.5.0.sql
Upgrade Instructions¶
Prerequisites¶
- Poweradmin v4.4.x
- PHP 8.2 or later
- Database backup
- Any integration still on API v1 migrated to v2 (see Breaking Changes)
If you are upgrading from an older release, run the intermediate migration scripts first (4.3.0, then 4.4.0, then 4.5.0). Do not skip versions.
MySQL/MariaDB with pdns_db_name (PowerDNS tables in a separate database): the 4.3.0 script shipped before 4.3.6 stopped at the domains backfill and left the rest of that update unapplied. The 4.5.0 script detects this and adds the missing users.perm_templ_source column, the zones.zone_name index and the log_api table itself. Only the zone backfill is not repeated; run it as shown in the 4.3.0 upgrade guide if you plan to switch to the API backend.
Step 1: Backup Your Data¶
# MySQL/MariaDB
mysqldump -u username -p poweradmin_db > poweradmin_backup_$(date +%Y%m%d).sql
# PostgreSQL
pg_dump -h localhost -U username poweradmin_db > poweradmin_backup_$(date +%Y%m%d).sql
# SQLite
cp /path/to/poweradmin.db /path/to/poweradmin_backup_$(date +%Y%m%d).db
Step 2: Download and Extract¶
cd /tmp
wget https://github.com/poweradmin/poweradmin/archive/refs/tags/v4.5.0.tar.gz
tar -xzf v4.5.0.tar.gz
rsync -av poweradmin-4.5.0/ /var/www/poweradmin/ --exclude=config/settings.php
Step 3: Run Database Update¶
MySQL/MariaDB:
PostgreSQL:
SQLite:
Step 4: Clear Cache and Restart¶
New Features¶
Serial Policies (SOA-EDIT / SOA-EDIT-API)¶
PowerDNS rewrites zone serials according to the SOA-EDIT and SOA-EDIT-API metadata kinds. Poweradmin can now manage both from the metadata editor and preset them for new zones:
return [
'dns' => [
'soa_edit' => '', // e.g. 'INCEPTION-INCREMENT'; empty = not set
'soa_edit_api' => '', // e.g. 'EPOCH'; 'OFF' disables, empty = server default
'soa_edit_options' => null, // values offered in the metadata editor; null = all, [] hides the kind
'soa_edit_api_options' => null, // values offered in the add-zone selector and editor
],
];
Related serial features:
- Signed serial in zone lists - with
interface.display_signed_serial_in_zone_listenabled (API backend required), zone lists show the serial PowerDNS actually serves after SOA-EDIT, next to the stored one when they differ. - Serial placeholders in templates - zone template SOA records understand
[UNIXTIME]and[COUNTER]placeholders for the serial field. - Presigned zones - zones with the
PRESIGNEDmetadata are recognized, and DNSSEC operations that don't apply to them are gated off.
Record Change Log¶
Record and zone mutations are now captured with structured before/after snapshots in the new log_record_changes table, powering a diff-style change log view. The existing activity feed (log_zones) is unchanged and continues to work as before.
Granular API Keys¶
API keys can be restricted along three independent axes (#795):
- Read-only - the key can only perform read operations.
- Allowed operations - an explicit list of permitted operation groups.
- Zone scoping - the key only works against an explicit set of zones.
Existing keys are unrestricted after the upgrade - identical to their previous behavior. Restrictions are edited on the API key pages.
New Permissions¶
Eleven permissions were added; except for the auto-granted view split (see Breaking Changes), none are granted automatically - admins opt in per permission template:
zone_dnssec_manage_own- manage DNSSEC keys for owned zones. Before 4.5.0 zone editing included this; see Breaking Changes.zone_logs_view_own/zone_logs_view_others- access zone activity logs. Before 4.5.0 zone viewing included this; see Breaking Changes.user_logs_view/group_logs_view- access user and group activity logs.zone_content_edit_ns_subzone- edit delegation NS records below the zone apex while apex NS and SOA stay protected.server_status_view- view the PowerDNS server status page andGET /api/v2/server/statuswithout administrator rights.zone_metadata_view_own/zone_metadata_view_othersandzone_ownership_view_own/zone_ownership_view_others- split out ofzone_content_view_*(auto-granted on upgrade).
Per-Record-Type Default TTLs¶
Administrators can define a default TTL per record type in the UI (stored in record_type_defaults). When a record is created without an explicit TTL, the type-specific default applies first, then the legacy chain (dns.ttl_reverse for PTR, then dns.ttl).
Trusted Proxies¶
Deployments behind a CDN or load balancer with a public peer address can now declare it:
Forwarded IP headers (X-Forwarded-For, X-Real-IP) are honored only from these peers; private and loopback peers are always trusted, matching the previous behavior for same-host reverse proxies.
LDAP: User-Info Sync, Auto-Provisioning, Group Mapping¶
ldap.sync_user_info- sync fullname/email from the directory on every login (fullname_attribute,email_attributeconfigurable). When enabled, those fields become read-only in the user editor, like OIDC/SAML accounts.ldap.auto_provision- create missing users on first successful LDAP login, withdefault_permission_templateas the fallback template.- LDAP group memberships (
groups_attribute, defaultmemberOf) can map to permission templates and to Poweradmin groups, with the same 1:n mapping semantics as OIDC/SAML.
Authentication and MFA¶
- OIDC
form_postresponse mode - per-provider opt-in (response_mode => 'form_post', HTTPS only) for providers or proxy setups where query-mode callbacks are problematic. - MFA verification is now rate limited - a six-digit second factor is small enough to guess, so wrong codes are counted and verification is refused once the limit is reached. This applies whenever MFA is enabled and does not require
security.account_lockout.enable_lockout, which ships disabled. Tune it withsecurity.mfa.max_verify_attempts(default5) andsecurity.mfa.verify_lockout_duration(default15minutes). Existing installations gain the limit on upgrade with no configuration change. - MFA stage throttling - failed MFA codes are counted separately from password failures, so a wrong code never blocks a later password login and a fresh password success no longer resets the MFA counter. Unlike password lockout, MFA failures are counted per account across all source addresses and are not exempted by
whitelist_ip_addresses. security.mfa.skip_for_external_auth- skip Poweradmin's MFA enforcement for LDAP/OIDC/SAML logins when the IdP already enforces it.
API Request Logging¶
With logging.api_request_logging enabled, every public API request is logged to the API log; permission violations (401/403) are logged regardless of the setting. logging.api_log_retention_days prunes old rows (0 keeps everything).
Zone List and UI¶
- Owner, group, and record-count columns in zone lists can be toggled (
interface.display_owner_in_zone_list,display_group_in_zone_list,show_zone_record_count). - Zone lists in API backend mode are considerably faster on installations with many zones: record counts are now fetched only for the zones on the current page instead of for every zone, and repeated reads of the same PowerDNS data are reused within a request. This changes how the Records column sorts, see Other changes.
- Record comments show author and last-modified time in a tooltip.
- API-backend zone lists indicate zones with a pending NOTIFY.
- The "Permission templates" menu item is now labeled "Permissions".
dns.parent_zone_ownership_check(default on) blocks creating a zone that overlaps an existing zone owned by another user.- Each user can switch the interface to the full browser width under Account > Preferences > Display Settings;
interface.wide_layoutsets the site-wide default.
Health and Ping Endpoints¶
Two unauthenticated endpoints for external monitoring, both disabled by default and answering 404 while off:
return [
'health' => [
'enabled' => true, // GET /api/health
'ping_enabled' => true, // GET /ping
],
];
/ping is a liveness check. It touches nothing else, so it still answers while the
database is down. /api/health is a readiness check: it reports whether the database and
the PowerDNS API can be reached, and returns 503 when either is down. Neither response
carries a version, hostname or error text.
Failure detail goes through the normal logger, so it honors logging.type. That ships as
null, meaning a failing check writes nothing until you set a real log type. Tune the
probe timeouts with health.db_timeout and health.pdns_timeout (2 seconds each).
Expose these deliberately. See Health Checks for how to restrict access at the web server.
Headless Mode (API-Only)¶
Installations driven entirely by scripts, Terraform or ExternalDNS can turn the web interface off and serve only the API:
/api/v2/*, /api/docs, /api/health and /ping stay routable, along with /api/v1/*
so retired v1 clients keep receiving their 410. Everything else answers a JSON 404,
including the login page, the OIDC and SAML callbacks and the internal endpoints the
interface uses for its own JavaScript. No session is started at all.
API keys are still issued in the web interface, so this is a post-installation setting.
Install with the interface enabled, create your admin and issue a key, then turn it off.
Setting it back to true and restarting gets the interface back whenever you need to
rotate a key.
install/ and dynamic_update.php are files rather than routes, so this setting does not
affect them. Delete the installer after installing, as usual.
Breaking Changes¶
API v1 is removed¶
Public API v1 was deprecated in 4.3.0 and is gone in 4.5.0. Every /api/v1 path now answers 410 Gone for every HTTP verb, with a Link: </api/v2/>; rel="successor-version" header and a JSON error body. Migrate your integrations to /api/v2 before upgrading, or they will stop working the moment the new files are in place.
The original sunset notice named 2026-09-01 and version 4.4.0. Version 4.4.0 shipped with v1 still present, so the removal lands here instead. Only the version label was wrong; the intent is unchanged.
Two differences catch most v1 clients out:
- Responses are wrapped. v2 returns
{success, data, message}, and list payloads nest one level deeper:data.zones,data.users,data.templates,data.records, where v1 returned a bare array. Zone list items exposeid; user records exposeuser_id. - One path was renamed.
/api/v1/permission_templatesis/api/v2/permission-templates, with a hyphen.
Your API keys keep working as they are. Authentication is unchanged, and the new read-only and operation-scope restrictions are opt-in, so an existing key stays unrestricted until an administrator narrows it.
The API documentation is generated from v2 only. /api/docs/v1/json is removed, and the Swagger UI no longer shows a version selector. The unversioned /api/docs/json serves the v2 spec, so existing bookmarks keep working.
The 4.2.x, 4.3.x and 4.4.x lines still ship v1. If you are not ready to migrate, stay on one of those.
See the API Overview for the full v1-to-v2 comparison.
interface.application_url is now required for OIDC and SAML¶
Poweradmin no longer derives a host from the request when building URLs that leave the application. Under the official Docker image (FrankenPHP/Caddy) and under Apache's default UseCanonicalName Off, the web server takes SERVER_NAME from the client's Host header, so the previous fallback let a forged header steer the OAuth redirect_uri and the SAML entityID/ACS to another host.
If OIDC or SAML is enabled and interface.application_url is empty, single sign-on stops working until you set it. Set it to the full public URL of the install before upgrading:
The value must match the redirect URI already registered with your identity provider. For deployments that were working before the upgrade this is the host users already reach Poweradmin on, so no provider-side change is needed.
What changes per feature when application_url is empty:
| Feature | Behaviour |
|---|---|
| OIDC login | Fails with interface.application_url must be configured before OIDC can be used |
SAML login and /saml/metadata |
Fail unless sp.entity_id, sp.assertion_consumer_service_url and sp.single_logout_service_url are all set explicitly, or the legacy interface.base_url is set (see below) |
| OIDC logout | Still redirects to the provider, but without post_logout_redirect_uri, so users are not returned to the login page |
| Zone-access notification email | Still sent, with the "Manage Zone" link omitted |
| API docs page | Still works; the embedded spec URL becomes relative |
This also ships in the 4.2.x, 4.3.x and 4.4.x patch releases. See OIDC and SAML.
A note on interface.base_url. SAML has always consulted this undocumented setting before falling back to the request. It still works, and it is still checked, but only after application_url. If you set it manually at some point, your SAML setup keeps working after the upgrade without any change. Prefer application_url going forward: it is the documented setting and the one OIDC, password-reset emails and the API docs page read. This is not the same setting as interface.base_url_prefix, which is unrelated and still current - that one handles subfolder deployments.
Credentials are hashed at rest¶
Password reset tokens. Reset tokens are now stored as sha256 hashes. Plaintext tokens issued before the upgrade cannot be validated afterwards - affected users simply request a new reset link; old rows expire within the token lifetime (1 hour by default).
API keys. Keys are stored as sha256$<hex> instead of plaintext. No action is needed: an existing key is migrated to its hash the first time it successfully authenticates, so integrations keep working with the key they already hold. Keys that are never used stay plaintext in the table until they are used or rotated.
Two things worth knowing:
- A database backup taken before the upgrade contains plaintext keys. Restoring one after the upgrade has been running for a while gives you a table with some rows hashed and some plaintext, which still works but undoes the migration for anything restored.
- Poweradmin can no longer show you an existing key's secret, because it no longer has it. This was already true of the interface before 4.5.0 - the secret is only ever displayed once, on the page shown right after creating or regenerating a key - but it is now true of the database as well. If you have lost a key, regenerate it.
Session keys written by the web installer should be rotated¶
The installer used to build security.session_key with mt_rand(), which is seeded from a 32-bit value, so the key carried far less entropy than its 46 characters suggest. 4.5.0 generates it with random_int() instead, but that only applies to keys created from now on. An existing key in config/settings.php is left exactly as it is, so upgrading on its own does not fix this.
If your key came from the web installer, replace it:
Everyone is logged out when you change it, since sessions encrypted under the old key can no longer be read. Nothing else is affected. Installations set up through Docker already used openssl rand and need no action.
The header warning that flags a default key now also fires when the key is missing or shorter than 32 characters. It cannot tell a weak generator from a strong one, so a full-length installer key will not be flagged - rotate it if you are unsure where yours came from. See Security Policies.
Metadata and ownership view permissions are split out of content view¶
zone_content_view_own/zone_content_view_others no longer imply seeing zone metadata or zone owners. The migration auto-grants the new zone_metadata_view_* and zone_ownership_view_* permissions to every template holding the matching content-view permission, so nothing changes visibly on upgrade - but new templates need the permissions granted explicitly, and admins can now revoke them to hide those sections.
DNSSEC key management and zone logs need the new permissions¶
Before 4.5.0 a user who could edit their own zones (zone_content_edit_own) could also manage DNSSEC keys on them, zone_content_edit_others covered every zone, and anyone who could view zones could open their activity logs. From 4.5.0 DNSSEC key management needs zone_dnssec_manage_own (plus ownership) and the zone logs need zone_logs_view_own or zone_logs_view_others. Fresh installs grant these to the default Zone Manager, Editor and Viewer templates; the upgrade does not, so after it only administrators can manage DNSSEC keys or read the zone logs. There is no _others DNSSEC permission: editors of other users' zones keep DNSSEC access only to zones they own.
To keep what your users could do before, run this once after the update script. It works on MySQL/MariaDB, PostgreSQL and SQLite, and running it again changes nothing:
-- DNSSEC key management for templates that can edit zones
INSERT INTO perm_templ_items (templ_id, perm_id)
SELECT DISTINCT pti.templ_id, np.id
FROM perm_templ_items pti
JOIN perm_items cp ON cp.id = pti.perm_id
JOIN perm_items np ON np.name = 'zone_dnssec_manage_own'
WHERE cp.name IN ('zone_content_edit_own', 'zone_content_edit_others')
AND NOT EXISTS (SELECT 1 FROM perm_templ_items x WHERE x.templ_id = pti.templ_id AND x.perm_id = np.id);
-- Zone logs for templates that can view zones
INSERT INTO perm_templ_items (templ_id, perm_id)
SELECT DISTINCT pti.templ_id, np.id
FROM perm_templ_items pti
JOIN perm_items cp ON cp.id = pti.perm_id
JOIN perm_items np ON np.name = CASE cp.name WHEN 'zone_content_view_own' THEN 'zone_logs_view_own' ELSE 'zone_logs_view_others' END
WHERE cp.name IN ('zone_content_view_own', 'zone_content_view_others')
AND NOT EXISTS (SELECT 1 FROM perm_templ_items x WHERE x.templ_id = pti.templ_id AND x.perm_id = np.id);
The zone logs page also needs logging.database_enabled, as before.
LDAP identity fields become read-only when sync is enabled¶
With ldap.sync_user_info enabled, fullname and email of LDAP accounts are managed by the directory and read-only in the user editor. With sync disabled (the default), they remain editable as before.
Lua-enabling zone metadata is restricted to administrators¶
ENABLE-LUA-RECORDS and LUA-AXFR-SCRIPT make PowerDNS evaluate Lua, which reaches
every zone the server hosts, so they can now only be set by a user holding
user_is_ueberuser. Zone-level editors keep full access to every other metadata kind.
Values an administrator has already set are left alone: the editor compares what was
submitted against what is stored and only rejects an actual change, so a zone editor
is not locked out of saving unrelated metadata on a zone that has these kinds set.
LUA records are restricted to editors above client level¶
LUA joins SOA and NS as a record type that zone_content_edit_own_as_client may
not create, edit or delete. PowerDNS executes the script content of a LUA record, so it
is not something a client-level tenant should be able to introduce. Owners and
administrators (zone_content_edit_own / zone_content_edit_others) are unaffected, and
existing LUA records keep resolving - only editing them through Poweradmin is gated.
Note this only ever mattered where the server has LUA enabled, either globally with
enable-lua-records=yes or per zone via ENABLE-LUA-RECORDS - which the same release
restricts to administrators.
The same restriction ships in 4.4.1 and 4.3.5, so it is not exclusive to this release.
It also covers zone templates: a template record is written straight to the backend when
the template is applied, so it never passes the record-level checks. Storing a LUA
record in a template therefore requires zone_content_edit_own or
zone_content_edit_others, a stricter bar than the client rule alone. Saving an existing
zone as a template skips record types the caller may not author and names them in the
resulting message rather than failing the whole save.
Identity providers can no longer grant administrator rights¶
oidc/saml/ldap.allow_superuser_provisioning defaults to false. With it unset,
Poweradmin refuses to provision superuser from an IdP assertion in either of the two ways
that previously worked:
- a
permission_template_mappingentry resolving to a template that grantsuser_is_ueberuser, and - a
group_mappingentry resolving to a group whose template grants it - note the installer ships anAdministratorsgroup bound to exactly such a template.
If your deployment relies on the IdP deciding who is an administrator, those logins will
stop being administrators after this upgrade. Both refusals are logged, so a login that
silently loses rights can be traced. Either set the flag to true, or grant administrator
rights inside Poweradmin, where an existing administrator has to act.
user_edit_templ_perm no longer hands out administrator rights¶
The permission delegates template management, not the granting of administrator rights. A
holder who is not themselves an administrator can no longer assign a template that grants
user_is_ueberuser - on the user editor, the user create form, or the REST API user
endpoints - and can only change their own template if they also hold user_edit_others.
Assigning any ordinary template is unaffected, so a delegated user administrator keeps
working as before unless they were relying on handing out the Administrator template.
This ships across the supported lines, not only here: 4.4.1, 4.3.5, 4.2.6 and 3.9.12 carry the same rule.
Zone ids in API backend mode¶
This applies only to installs running the API backend that were migrated from SQL mode.
- The API v2 zone list,
GET /zones/{id}and the internal zone list now report the canonical zone id asid, the same value every other zone endpoint takes. Before, the list reported the zones table row id, which could open a different zone. Clients that stored the old ids (for example Terraform state or scripts) see them change once, and requests with an old id get403or404. - On the first request after the upgrade, stored API key zone restrictions are moved from row ids to canonical ids. This runs once. A restriction that cannot be moved is kept but matches no zone, and the log names the key. Open each restricted key afterwards: the edit form shows a warning on a key that lost its zone, and the zone has to be selected again. Saving the key without a zone keeps it restricted; it does not become unrestricted. Until the move has run, a restricted key reaches no zone and new restrictions cannot be saved.
- A new zone no longer takes a zone id that a migrated zone, a group grant or an API key restriction already uses. Before, it could take over a migrated zone's id and hide that zone.
- A zone created in API mode before 4.5.0 may already share its id with a migrated zone. See Zone IDs Shared by Two Zones for how Poweradmin handles it.
Other changes¶
- The Records column in zone lists is no longer sortable in API backend mode. Record counts are now resolved only for the zones on the current page, so ordering by them would sort just that page rather than the whole list. The column header is plain text instead of a sort link, and a stale
count_recordssort carried over in a session falls back to sorting by name. SQL backend mode is unaffected and still sorts by record count. - The MFA failure counter is tracked per authentication stage; repeated wrong MFA codes can lock an account even after a correct password.
- Two authentication paths that were previously exempt now count towards account lockout: the dynamic DNS endpoint, and HTTP Basic Auth on the public API. This only matters if you have turned lockout on -
security.account_lockout.enable_lockoutships disabled - but if you have, an automated client retrying with stale credentials can now lock the account it belongs to. Check any DDNS client or service account whose credentials may be out of date before upgrading. See Security Policies. DELETE /api/v2/groups/{id}now returns 409 when the group still owns zones, unless the request carriesconfirm=true. Deleting such a group leaves those zones without an owner, so the confirmation is required. Groups that only have members are unaffected and delete as before. API clients that destroy zone-owning groups need the extra parameter.interface.rows_per_pagenow accepts any value from 5 to 500 rather than silently falling back to 10 for anything outside 10/20/50/100. If you had set a value like 25 it was previously ignored, and will now take effect. A value outside 5 to 500 is clamped to the nearest limit and a warning is logged; it does not stop the application from starting.- PHP's session garbage collection is raised to match
interface.session_timeout. Sessions previously expired after PHP's 1440-second default rather than the configured 1800, which returned users to the login page without an explanation. - An unrecognised
logging.levelnow falls back toinforather than silently suppressing everything belowemergency. If you have a typo such aswarninstead ofwarningandlogging.typeisnative, log volume will increase noticeably until the value is corrected. The logger records one warning naming the rejected value. - All other new features are additive or opt-in: new tables start empty, new settings default to prior behavior, and new permissions (except the auto-granted view split) are not assigned to any template.
Rollback¶
To roll back to v4.4.0:
- Restore your database backup.
- Restore your file backup.
- Restart the web server.
The new tables and columns are harmless in earlier versions - they are simply unused. Note that password reset tokens issued by 4.5.0 (hashed) cannot be validated by 4.4.0; users would need to request a new link after a rollback.