Skip to content

Tips and tricks

These tips come from two places: the SpatiumDDI docs for release 1.0.0-beta.3, and problems real operators reported on GitHub and Discord. Every tip ends with its source, so you can read the full story before you change anything in production. The amber From the field boxes are the pitfalls that have caught people most often.

SpatiumDDI is beta software: 1.0.0-beta.3 is a pre-release on the road to 1.0.0. Tips that mention an issue number describe behavior that changed between releases, so check the release notes when you upgrade. If you’re coming from 2026.09.04-1, start with the upgrade checklist. Commands that use 127.0.0.1 -p 1053 or port 8077 refer to the Academy lab.

Press Ctrl+K (⌘K on a Mac) to search or jump anywhere. The Search or jump to… palette covers twenty resource types across IPAM, DNS, DHCP, Network and Administration, and adds “Go to” rows for pages. Results are filtered to what your role is allowed to read.
Source: IPAM features §10, Global search

Type what you have, not where it lives. Search works out what kind of query you typed. An IP finds the address, its subnet and block, the DNS records that point at it, and any reservation. A CIDR finds the subnets and blocks inside it. Any other text matches names, hostnames, FQDNs, record values, descriptions and searchable custom fields.
Source: IPAM features §10, Global search

Paste a MAC in any notation when you search. Search strips the separators from both your query and the stored value, so colon, dash and dot notations all match each other, and so does an OUI prefix. Reservations are stricter than search; see the MAC tip under DHCP.
Source: IPAM features §10, Global search

Press ? to see every keyboard shortcut. The overlay lists every key binding in your version. It stays closed while you are typing in a field or another dialog is open.
Source: CHANGELOG, keyboard shortcut overlay (#81)

Shift+Click to select a run of addresses. In a subnet’s IP address table, tick one checkbox and then Shift+Click another to select every row in between. You can then edit them all at once instead of opening each IP.
Source: CHANGELOG, shift-click range select on IPAM IP checkboxes

Climb the tree with the colored breadcrumb pills. Above every IPAM detail panel, a blue pill is the IP space, violet pills are blocks, and the emerald pill is the subnet you are in. Click a pill to jump straight up to that level.
Source: IPAM features §14.5

Save the filters you rebuild every morning. On pages that have a Views menu, Save current view as… keeps your filters and sort order. Star a view to make it your default, and it is applied when you open the page. The menu is part of the ui.saved_views feature module.
Source: REST API §5, feature-module gating

Put the environment name on every screen. The environment banner in Settings → Branding & URL shows a colored strip of text at the top, the bottom or both, including on the login page, so nobody mistakes the lab for production. Only a superadmin can change branding.
Source: System administration §6

A menu the docs describe may be switched off. A fresh install turns on 16 of the 57 feature modules, and Features & Integrations near the bottom of the sidebar shows “16 of 57 enabled” and switches the rest. A disabled module disappears from the sidebar, the REST API and the copilot. Conformity, Windows cutover, nmap, packet capture, scheduled Wake-on-LAN, NAT mappings, approvals and every integration ship off; IPAM, DNS, DHCP, sites, VLANs and VRFs ship on. An upgraded install keeps the modules it had.
Source: REST API §5, feature-module gating (#1069); verified in the Academy lab, 1.0.0-beta.3

Keep the API reference one click away. The API documentation icon in the header opens ReDoc (/api/redoc), and Administration → Reference in the sidebar has API Docs (ReDoc) and API Docs (interactive) (Swagger, /api/docs). Both are generated from the build you run and work through the web port (http://localhost:8077/api/docs in the lab). Sign in to the console first: the pages read the schema with your session, and signed out they only say “Sign in to SpatiumDDI to view the API documentation.” From a script, fetch /api/openapi.json with an API token; anonymous requests get 401.
Source: REST API §2 (#1157, #2935); verified in the Academy lab, 1.0.0-beta.3

Pin the DNS and DHCP groups on the block, not on every subnet. Subnets inherit their DNS server group, zone and DHCP server group from the block or space above them. Ridgeline pins both groups once, on 10.20.0.0/16, so every per-VLAN subnet created under it is wired up automatically.
Source: Getting Started §7

Inheritance for DNS and for DHCP is set separately. A subnet can inherit DNS from its block and still use its own DHCP group. Leave DNS inheritance on, turn DHCP inheritance off, and pin the DHCP group on that subnet.
Source: Getting Started §8

Subnets get their reverse zone automatically, inherited DNS included. When you save a subnet whose DNS group or zone is set on it or inherited from the block, SpatiumDDI creates the matching in-addr.arpa (or ip6.arpa) zone in that group: one zone per /24 the subnet spans, never one enclosing zone, so a /23 gets two. Ridgeline’s Staff VLAN 21 gets 21.20.10.in-addr.arpa as you save it. The API’s skip_reverse_zone opts a subnet out; the UI has no such switch.
Source: DNS features §4 (#1149, #1840); verified in the Academy lab, 1.0.0-beta.3

Expect .1 to be reserved already. When you create a subnet, SpatiumDDI reserves the first usable address as the gateway (status reserved, description “Gateway”) and adds rows for the network and broadcast addresses. The gateway’s PTR is published at once, but on purpose no forward gateway.<zone> A record, so its DNS column reads “forward DNS skipped”. Move or delete the gateway row for a transit link. /31 and /32 subnets get no network or broadcast rows (RFC 3021).
Source: IPAM features §11 and §14.1 and GitHub issue #1150; verified in the Academy lab, 1.0.0-beta.3

Next available never hands out a dynamic-pool address. Allocate IP → Next available, and bulk allocation, skip the network and broadcast rows and every IP inside a dynamic DHCP pool. Allocation is atomic, so two people clicking at the same moment don’t get the same address.
Source: IPAM features §5 and §16

A static IP typed inside a dynamic pool also needs a reservation. Choosing a specific IP inside an active dynamic pool is allowed once you confirm a warning. The IPAM row on its own doesn’t stop the DHCP server from leasing that address to another client. Create a matching static_dhcp reservation on the scope as well.
Source: IPAM features §16, IP allocation (#631)

Collision warnings are questions, not errors. If the hostname and zone already exist on another IP, or the MAC is already recorded somewhere, the save stops with an amber warning and the button changes to Allocate anyway. Read the warning first: a duplicate MAC usually means an old row should be retired, while a duplicate hostname is deliberate for round-robin DNS (see the DNS tips).
Source: IPAM features §15.11

Mark as Orphan is the delete you can undo. IP addresses don’t go to Trash. When you delete one, Mark as Orphan keeps the row, takes its DNS records and aliases out of the zone, and puts them back when you restore it from the orphans view. Delete Permanently removes the row with its DNS records and DHCP static assignments, and has no undo.
Source: IPAM features §14.8 and §15.16 (#2604)

Bulk-allocate with a name template, and always preview. Use Tools ▾ → Bulk allocate… on a subnet to fill a range in one pass. The name template takes the tokens {n}, {n:03d} (zero-padded), {n:x} (hex) and {oct1}–{oct4} (address octets). The preview flags addresses already in use, dynamic-pool overlaps and FQDN collisions before anything is written. One run is capped at 1024 IPs.
Source: CHANGELOG, IPAM bulk allocate

Let Find Free locate space for you. Find Free… on a space or block lists unallocated CIDRs of the prefix length you ask for, and Create subnet here passes the one you pick straight to the create form.
Source: IPAM features §15.15

Split, merge and resize make you type the CIDR. All three change the network definition that everything else depends on. Preview shows the impact on addresses, DHCP scopes, DNS records and reverse zones. Resize can only grow a subnet; to shrink one, delete it and create it again.
Source: IPAM features §15.10 and §15.15

Put overlapping address space in separate IP spaces, each with its own server groups. Subnets in different spaces can overlap, as they do in separate VRFs. However, one Kea server can’t serve two subnets with the same prefix, and one DNS group can’t hold two reverse zones with the same name. Give each overlapping space its own DHCP and DNS server groups.
Source: IPAM features §1 (#844)

Know what the utilization numbers mean. The IPAM tree colors utilization green below 70%, amber from 70% to 90% and red above 90%. The utilization thresholds in Settings default to a warning at 80% and critical at 95%, and IPv4 subnets longer than /29 (such as /30 and /31 links) are left out of utilization reporting so point-to-point links don’t raise alarms.
Source: IPAM features §2 and §9 and System administration §6

Turn on discovery only where it is safe. Ping/ARP discovery is opt-in per subnet and off by default, because unsolicited probes can trip an IDS; subnets larger than /20 and IPv6 subnets are skipped. Mark medical or OT segments Do not probe: the flag applies to everything below the space, block or subnet you set it on, and a child can’t switch it off.
Source: IPAM features §8

Find reclaim candidates with the Stale-IP report. It lists allocated IPs that haven’t been seen for a number of days (90 by default). Reserved and static_dhcp rows are left out, and so are IPs that were never seen unless you ask for them. Deprecating doesn’t free an address: a deprecated row still counts as allocated and Next available never offers it, so delete the row to give the address back. Bulk-deprecating from the report can be undone from the normal edit form.
Source: IPAM features §8, Stale-IP report (#2421)

Let reverse DNS name the rows nobody named. Settings → IPAM → Reverse DNS (PTR) runs a sweep that looks up PTR records for IPs with no hostname and fills in the short name. It never overwrites a hostname, and it only fills the description when the description is empty.
Source: IPAM features §8, reverse-DNS auto-population

Read the Seen dot before you reclaim an address. The Seen column is green if the IP was seen in the last 24 hours, amber for 24 hours to 7 days, red for longer than that, and gray if it has never been seen. It is separate from the IP’s status, so an allocated IP can still be showing red.
Source: CHANGELOG, Seen recency column

Fix DNS drift from the subnet, with a preview. An amber “out of sync” dot in the DNS column means the IP’s records are missing or different. Sync ▾ → DNS on the subnet opens a report of what is missing and what doesn’t match; untick anything you want to leave alone, then apply. A clean subnet reads “✓ In sync — no drift detected.” Sync DNS leaves each address in the zone it was published in, so changing a subnet’s zone doesn’t move existing addresses: use Bulk edit → DNS zone for that.
Source: Getting Started §10 and DNS features §14 (#2104); verified in the Academy lab, 1.0.0-beta.3

A Required custom field is enforced on blocks and subnets. A create that leaves a Required field empty is refused (422) and names the field; a value inherited from the block above counts. An update is only checked for the fields it sends or the row already holds, so making a field Required later doesn’t lock existing rows.
Source: IPAM features §6 (#2478)

The engine is chosen for each server, and a group runs only one engine. You pick BIND9, PowerDNS, Technitium or Windows when you add a server, and every server in a group must run the same driver. If you need more than one engine, create a group for each.
Source: DNS features §0

Choose the engine for the feature you can’t do without. Views and RPZ blocklists need BIND9. ALIAS and LUA records need PowerDNS. DNS over QUIC, and encrypted upstream forwarding over DoH or DoQ, need Technitium. Online DNSSEC signing works on BIND9 and PowerDNS; Technitium signing is refused in 1.0. Catalog zones: BIND9 produces and consumes them, PowerDNS only produces them, and Technitium can’t use them in 1.0.
Source: DNS features §0 (#1782, #1783) and Getting Started §3

A CNAME must be alone at its name, and never at the apex. The record API refuses a CNAME beside other records at the same name, and a CNAME at the zone apex, and every importer applies the same rule: the offending row is skipped and listed as a warning instead of producing a zone the server refuses to load. For a CNAME-like record at the apex, use ALIAS on a PowerDNS group.
Source: Migration, CNAME beside other data (#1381, #2089)

Check your work in the lab with dig @127.0.0.1 -p 1053. The lab’s BIND9 container answers on host port 1053, because 53 would clash with the host’s own resolver and 5353 with mDNS. Run dig @127.0.0.1 -p 1053 print-01.corp.example.edu +short for the A record and dig @127.0.0.1 -p 1053 -x 10.20.21.10 +short for the PTR.
Source: README, running the built-in containers and Docker Compose deployment §10

Record edits don’t restart anything. Record changes reach BIND9 as RFC 2136 dynamic updates, usually in under a second, with no daemon restart (in a group without views; with views, the agent re-renders the zone instead). Zone, ACL and view changes trigger a config reload.
Source: README, running the built-in containers

Sync with Servers adds; it never deletes. It imports zones and records it finds on a server and pushes records that exist only in SpatiumDDI back out. On agent-based BIND9 and Technitium groups, only the per-zone record step runs; PowerDNS groups don’t take part (see the next tip).
Source: DNS features §12

PowerDNS groups have no record-level drift report. The PowerDNS driver doesn’t read a zone’s records back from the server, so record sync and the zone’s Drift tab can’t work on a PowerDNS group. Plan your drift checks around that.
Source: DNS features §12

A clean zone’s Drift tab says so in words. Drift compares what each server serves with the database, and leaves the zone’s own apparatus (the apex SOA and NS, and the BIND9 agent’s placeholder glue) out of the comparison. An untouched lab zone reads “All 1 comparable server match the database” and “Every record in SpatiumDDI is being served, and nothing extra is present on the host.”
Source: DNS features §12 and Release notes 2026.10.02-1; verified in the Academy lab, 1.0.0-beta.3

Zone transfers from agent-managed groups need the group’s TSIG key. AXFR from BIND9 and Technitium agents is signed with the group’s TSIG key. A group with no key can’t be transferred from at all, and it shows as unsupported rather than as an empty “in sync” result.
Source: DNS features §12 (#734)

SRV records need a priority, a weight and a port. Fill in all three in the record form; an SRV record with any of them missing is rejected. Older releases had no weight and port fields.
Source: GitHub issue #424

Allow dynamic updates by TSIG key rather than by IP address. A zone’s Dynamic Updates dialog accepts writers by TSIG key or by source IP/CIDR. Source IPs can be spoofed over UDP, so prefer a key. The cloud DNS drivers don’t accept RFC 2136 updates at all.
Source: DNS features §19 (#641)

Moving a signed zone to another group means a new DS record. A zone Move asks you to acknowledge three things separately: view widening, DNSSEC re-signing (a key rollover, so you publish a new DS record at the registrar) and lost dynamic-update grants. You then type the zone name to confirm, and the target group must have exactly one agent DNS server. Plan the registrar change before you start.
Source: GitHub issue #935 and DNS features §0 (#1762)

A record with no view answers in every view. Leaving a record’s View as “All views (default)” publishes it in every view. In a split-horizon group that can expose an internal name to external clients, so set the view on anything internal.
Source: GitHub issue #935 and DNS features §2

Once a group has one view, every client needs a view. When any view exists, BIND serves every client from a view, so add a catch-all view that matches any with a higher order number, or clients outside your views get no answer at all. Views are checked lowest order first, and a view name can be at most 45 characters.
Source: DNS features §8, scoping and constraints

A group with blocklists can’t validate DNSSEC. RPZ rewriting is answer tampering by definition, so the agent turns DNSSEC validation off on any group that has a blocklist. Filtering can also be bypassed (a browser with DoH, a hard-coded resolver, a VPN); block outbound 53 and 853 at the firewall to anything but your resolvers.
Source: DNS features §8.1, honest limits

Fill in Primary NS and Admin Email on real zones. If you leave them blank, the zone is served with an SOA naming ns1.<zone> as the primary name server and admin.<zone> as the contact, which is rarely what the rest of the world should see. The values you enter are what BIND9 serves, and editing them bumps the zone’s serial so secondaries transfer the new apex.
Source: DNS features §4 and GitHub issue #1153

Turn recursion off on authoritative-only groups. An authoritative-only server should have recursion turned off and its allowed-recursion list set to none. New groups have recursion on, but answer recursive queries only from loopback, the server’s own networks and the private ranges (10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, fc00::/7, fe80::/10). Clients on public, CGNAT or Tailscale (100.64.0.0/10) addresses are refused until you add their networks in the group’s Options → Recursion card; any opens it deliberately.
Source: DNS features §3.2 (#1817) and System administration §6

Start rate limiting in log-only mode. For an internet-facing BIND9 group, the docs suggest RRL at about 15 responses per second, a window of 15 and slip 2, with your own secondaries exempt. Run it log-only first and watch the drop counters before you enforce it.
Source: DNS features §3.8

Treat LUA record authors as privileged. LUA scripts run inside the PowerDNS daemon, so anyone who can write one has code execution in your DNS server. Scope DNS write permission accordingly.
Source: README, PowerDNS-only record types

A DNS pool is not a load balancer. Clients cache answers, so a member that drops out keeps receiving traffic until the pool’s TTL expires. Keep that TTL short (the default is 30 seconds).
Source: DNS features §17

Sign zones on a group with one agent server, then give the DS records to the registrar. Each BIND9, PowerDNS or Technitium server signs with keys it generated, so signing is refused (422) on a group with two or more agent DNS servers, disabled ones included. A zone signed before that rule shows DNSSEC: multiple signers and raises the critical dnssec_multi_signer alert. Once a zone is signed, its DNSSEC card lists the DS records with Copy buttons; it’s only protected once the parent publishes them. Manual key rollover is BIND9-only.
Source: DNS features §0 and §3.3a (#1762)

Turn on query logging before you need it. The DNS Queries tab on the Logs page is only filled once query logging is on in the group’s options. Rows are kept for 24 hours by default; Settings → Data Retention → DNS query & DHCP activity logs keeps them for up to a week. Technitium query logs don’t reach this tab yet.
Source: Observability §3 and §4½ and System administration §2.7

On Docker Compose, encrypted DNS needs the overlay file. You enable DoT and DoH on a group under Options → Encrypted transports, but Compose only publishes the ports when you add -f docker-compose.dns-encrypted.yml. On an appliance, pick port 8443 for DoH, because the web UI already uses 443.
Source: README, encrypted DNS

Check the suggested pool in a new scope. Create Scope on the subnet’s DHCP Pools tab pre-fills Routers (option 3) with the subnet’s gateway, along with the defaults from Settings. On an IPv4 subnet of /28 or larger it also suggests an Initial pool from the tenth host to the last usable address (.10–.254 on a /24). Ridgeline replaces it with 10.20.21.100–10.20.21.199: kept as suggested, the pool would take in print-01 at .10. An IPv6 subnet gets no Routers value, because option 3 is DHCPv4-only.
Source: Getting Started §9 and GitHub issue #1154; verified in the Academy lab, 1.0.0-beta.3

Edit, bulk edit and the importer keep reservations in step. Flipping an existing row to static_dhcp in Edit, bulk edit or the address importer creates its reservation on the subnet’s scope; changing its MAC or hostname updates the reservation, and flipping it away removes it. If a reservation seems to be missing, look in the group’s Static Assignments tab and read the save’s warning. A static_dhcp row that never got its reservation gets one the next time you save it.
Source: DHCP features §3, static assignments (#1628)

Only dynamic pools reach Kea. Excluded and reserved pools are bookkeeping in IPAM; only dynamic ranges are rendered into Kea. A scope with no pool is still valid and still renders the subnet, which is what a subnet with reservations only needs.
Source: DHCP drivers §3 and DHCP features §3

You can put a reservation inside a dynamic pool. Since 2026.07.21-1, a reservation whose IP falls inside a dynamic range is accepted. Kea won’t give the reserved address to another client.
Source: DHCP features §16 (#631)

A MAC can be reserved only once in a server group. Every server in a group serves the same reservations, so the same MAC in two scopes of one group is refused with a 409. Keep the reserved IP inside the scope’s subnet; that is refused with a 422 otherwise.
Source: DHCP features §16

An inactive scope disappears from Kea. Only active scopes, and the reservations inside them, are sent to Kea. Disabling a scope silently drops every reservation it holds.
Source: DHCP features §3, troubleshooting

Set Client reachability to where the clients really are. On the DHCP server group, Directly attached/mixed (raw sockets, the default) receives broadcast DISCOVERs and relayed traffic. Relay-only (UDP sockets) cannot hear clients on the same LAN at all.
Source: DHCP features §1

A green Kea chip doesn’t prove Kea will answer your clients. “Healthy” only means the process is up. If no active IPv4 scope in the group contains the IP of the interface the client is on, Kea ignores the DISCOVER and sends nothing. Check the rendered subnet4 list first.
Source: Troubleshooting, Kea doesn’t respond to DISCOVER

Kea HA runs between the Kea members that have an HA Peer URL. Set the group to Hot Standby (one active, one passive) or Load Balancing (both active) and give each Kea server its own HA Peer URL (http://<its address>:8000/); appliance-hosted pairs get theirs filled in. Until two members have one, the group page says “HA is not running: no HA Peer URL on …”. A third Kea member with a URL joins as a backup peer; one without a URL is sidelined and serves no scopes. HA covers DHCPv4 only, so serve DHCPv6 from a group with one Kea member.
Source: DHCP features §14, Kea HA (#332, #1238, #3151)

Run a Kea pair on two VMs from the same agent compose file. Give each VM a different DHCP_HOSTNAME and the same AGENT_GROUP, so both register into one group, then set each server’s HA Peer URL to its own address.
Source: Docker Compose deployment §10 and DHCP features §14

Leave Kea’s lease cache off unless you accept stale data. Kea 3.0 caches leases by default, but SpatiumDDI ships the setting at 0.0 (off). A cached renewal skips the lease write that drives DDNS and the IPAM mirror, so turning caching on can leave DNS records and last-seen times quietly out of date.
Source: DHCP features §4b (#637)

Pick the DHCPv6 mode on purpose. An IPv6 scope is stateful (DHCPv6 hands out addresses and options), stateless (clients use SLAAC for the address and ask DHCPv6 only for options) or slaac (DHCPv6 isn’t involved). A slaac scope renders no pools, options or reservations.
Source: DHCP features §2, DHCPv6 operating mode

DHCP Lease Sync is for agentless servers. Turn on DHCP Lease Sync in Settings once you register a Windows DHCP server. It polls leases every 15 seconds by default (10 seconds minimum). Kea doesn’t need it, because its agent streams leases continuously.
Source: Getting Started §5

Waking hosts across a router needs directed broadcast. A Wake-on-LAN packet sent from the server to another subnet only arrives if the router forwards directed broadcasts, which is usually off by default. The alternative is to send it from a Fleet appliance on that segment. The IPAM docs include router configuration examples.
Source: GitHub issue #586 and IPAM features §5

Nothing gets installed on the Windows server. SpatiumDDI manages Windows DNS and DHCP remotely, over WinRM and over RFC 2136 and AXFR on port 53.
Source: Windows Server setup

Set each zone’s dynamic updates to “Nonsecure and secure”. AD-integrated zones default to “Secure only”, which rejects SpatiumDDI’s unsigned RFC 2136 updates, and GSS-TSIG isn’t implemented yet. If you don’t change it, record writes fail without any visible error.
Source: Windows Server setup §2

Path B still writes records over RFC 2136. WinRM credentials add zone create and delete, and zone reads that don’t need AXFR. Record writes still use RFC 2136 in both paths, so the dynamic-updates setting matters with or without credentials.
Source: DNS features §13

Use separate service accounts with the least rights that work. The DNS account goes in DnsAdmins. The DHCP account goes in DHCP Administrators if SpatiumDDI should write scopes, reservations and exclusions, or in DHCP Users to mirror leases and scopes read-only. Don’t use Domain Admins. Prefer WinRM over HTTPS on port 5986 with ntlm for domain-joined servers; credssp is only needed to manage Windows failover relationships, and Kerberos isn’t offered (422).
Source: Windows Server setup §1 (#1128)

Click Test Connection before you save. It runs a small PowerShell probe over WinRM. A 401 points at the username, password or transport; “Access is denied” means the account authenticated but isn’t in the right group.
Source: Windows Server setup §2 and §4

Keep WinRM TLS verification on in production. Turning off TLS verification is a per-server credential setting, so in a lab you can accept one host’s self-signed certificate without weakening the others. In production, give each WinRM listener a certificate from your internal CA and leave verification on.
Source: Windows Server setup §1 and §5

Test RFC 2136 with nsupdate when record writes fail. Run it by hand from the SpatiumDDI host. REFUSED points at the zone’s dynamic-updates setting; NOTAUTH means the zone’s primary NS in SpatiumDDI doesn’t match the server.
Source: Windows Server setup §4

With Windows DHCP Path A, the account decides who changes scopes. SpatiumDDI mirrors leases into IPAM as dhcp rows and imports scopes on the next lease sync. With a DHCP Users account that’s all it does, so change scopes in the Windows DHCP console. With a DHCP Administrators account, creating or editing a scope, pool or reservation in SpatiumDDI runs the matching cmdlet on the server first, and a WinRM failure rolls the change back.
Source: Windows Server setup §3 and DHCP features §15

Two Windows DHCP servers in one group need a failover relationship. SpatiumDDI writes a scope that a failover relationship covers to both partners. Outside a relationship it writes to one member only, refuses to activate a scope several members hold (422), and raises the critical DHCP scope served uncoordinated alert. The group’s Windows DHCP failover panel shows and manages the relationships, which needs the CredSSP transport and DHCP Administrators on both partners. The upstream docs say this hasn’t been tested against a live failover pair yet.
Source: Windows Server setup, more than one Windows DHCP server in a group (#1110)

Give access to groups, not to individual users. Users belong to groups, groups carry roles, and roles carry permission entries. Any single matching entry grants the action.
Source: Permissions model

Clone a built-in role to customize it. Built-in roles such as Viewer or DNS Editor can be cloned but not edited or deleted. Clone one, rename it, and change the copy.
Source: Permissions model, built-in roles

A custom role needs a read grant on each type it should see. Each IPAM and DNS route checks its own resource type, so a role must list read on ip_space, ip_block, subnet and ip_address to browse IPAM, and on dns_group, dns_zone and dns_record for DNS (the DNS page starts at the server groups). Writing a DNS record needs write on dns_record, and deleting an address delete on subnet. The built-in roles already carry what they need.
Source: Permissions model (#3058, #3073)

A permission on one object doesn’t cover the whole type. A grant scoped to one subnet (it has a resource_id) never satisfies an unscoped check. “Write on this subnet” doesn’t mean “write on subnets”.
Source: Permissions model, evaluation rule 4

Delegated admins can’t give out more than they hold. A non-superadmin who edits a role or a group’s roles can only add permissions they already have, so an IPAM editor can’t create a */* role for themselves.
Source: Permissions model, evaluation rule 5

Test a role by signing in with it. Controls the role can’t use stay visible but disabled, with the missing permission as a tooltip: as a Viewer, Allocate IP reads “Requires write permission on this subnet or on an address set in it”. The server still does the enforcing. A refused API call returns 403 (“Permission denied: need …”), and the audit log records a denied event, including a refused superadmin-only page (access on superadmin).
Source: Permissions model (#1155); verified in the Academy lab, 1.0.0-beta.3

An SSO user whose groups match no mapping can’t sign in. There is no fallback default group. When a test login fails, check the provider’s group mappings before you check anything else.
Source: Auth & permissions, rules & constraints

Set the External URL before you configure OIDC or SAML. Both build their redirect URLs from Settings → Branding & URL → External URL. The OIDC callback is https://<External URL>/api/v1/auth/{provider_id}/callback, and SAML SP metadata is served at /api/v1/auth/{provider_id}/metadata. Behind your own TLS-terminating proxy, set it to the public https:// address and list the proxy in FRONTEND_TRUSTED_PROXY_CIDRS; otherwise requests look like plain HTTP, and the api logs sso_external_url_not_set when it falls back.
Source: Getting Started §1 and §2, Auth & permissions, SAML and Running behind a reverse proxy

An SSO account may need linking once after an upgrade. An external account is tied to the provider it signs in through, and is never adopted by username alone. An account the upgrade couldn’t attribute to one provider, or one whose identifier changed at the IdP (an LDAP DN after an OU move), is refused with account_link_required and shows an unlinked chip on the Users page. Link it under Users → Edit → Sign-in provider. A sign-in whose username belongs to another account is refused with username_collision; the failed login row in the audit log names the reason.
Source: Auth & permissions, provider linking (#1235)

SSO admins must enroll TOTP before they can reveal secrets or mint tokens. Revealing agent keys, pairing codes, the appliance kubeconfig or the SNMP community, creating an API token, and making someone a superadmin all ask you to confirm who you are. Local users enter their password; SSO users enter a TOTP code. Set it up under Account → Set up authenticator.
Source: Auth & permissions, TOTP MFA and step-up (#408, #1355)

Keep the built-in local admin working. Local credentials are checked before any external provider, and local accounts work with no SSO configured. They are your way back in when the IdP is down. If you forget the password, reset it from the CLI (see the Troubleshooting playbook).
Source: Auth & permissions, provider priority

Know the default password policy. A fresh install asks for at least 12 characters with an uppercase letter, a lowercase letter and a digit, and refuses your last 5 passwords; the Change Password page shows the checklist as you type. Symbols and forced rotation are off. A fresh install holds even the first admin password change to this policy. Change it under Settings → Security → Password Policy.
Source: System administration §6 (#3203); verified in the Academy lab, 1.0.0-beta.3

Sign-in throttles can refuse a name, or everyone, for 15 minutes. Before it checks a password, /auth/login checks three budgets: 30 attempts a minute per source address, 20 failed passwords per username in 15 minutes (LOGIN_FAIL_MAX_PER_USERNAME) and 500 failed passwords across the install (LOGIN_FAIL_MAX_GLOBAL). Past one, sign-in answers 429 “Too many login attempts. Try again shortly.” until the window ends, for a superadmin too; sessions, API tokens and OIDC or SAML sign-in carry on. Behind your own reverse proxy, set FRONTEND_TRUSTED_PROXY_CIDRS, or every user shares the proxy’s per-source budget.
Source: Auth & permissions, sign-in throttles and Running behind a reverse proxy

RADIUS and TACACS+ fail over on errors, not on rejections. A definite reject from the primary server ends the login. Only network, timeout or protocol errors move on to a backup server, and all backups share the primary’s shared secret.
Source: Auth & permissions, backup server failover

Treat packet capture as a sensitive permission. Captures can contain plaintext credentials and personal data. Network Editor is the role that can start and delete them, and every download is audited. Listing and downloading a finished capture needs only read on manage_packet_capture, though, which Viewer’s read-everything entry includes. If you turn the Packet capture module on (it ships off), don’t give Viewer to people who mustn’t see captured traffic.
Source: Permissions model, resource types and built-in roles, REST API §5 (#1069) and module 2.1

Account lockout stays off until you set a threshold. In Settings → Security → Account Lockout, a threshold of 0 (the default) disables lockout. It applies to local users only. A superadmin can unlock an account from the Users page.
Source: GitHub issue #71

Keep passwords to 72 bytes or fewer. From 2026.08.22-1, passwords are capped at 72 bytes. That is a breaking change for anyone who pastes very long passphrases.
Source: Release notes 2026.08.22-1

Idle sessions end after 60 minutes. Settings → Security → Session & Security → Session Timeout (60 by default, 0 turns it off) is enforced by the API, so an abandoned browser session stops working even with its tab left open. API tokens aren’t sessions and aren’t affected. Auto-Logout Warning shows a warning that many minutes before the timeout (off by default).
Source: System administration §6 (#3203)

Revoke a session instead of waiting for its token to expire. Each access token is tied to its session. Revoking the session from Administration → Sessions makes every token minted from it fail on the next request.
Source: REST API §3.2

Everything you do in the UI is a REST call. Anything you click can be scripted with curl, Terraform or your own client. Find the endpoint in Swagger at /api/docs (http://localhost:8077/api/docs in the lab, signed in to the console in the same browser).
Source: README, API & interactive docs; verified in the Academy lab, 1.0.0-beta.3

Give scripts an API token, not a login. A session access token lasts 15 minutes, and its refresh token is an HttpOnly cookie that scripts shouldn’t handle. An sddi_ API token is long-lived, is shown exactly once when you create it, and is stored only as a hash, so a lost token means creating a new one. Creating one asks for your password again (SSO users: an authenticator code), in the dialog or as stepup_password or stepup_totp_code in the API body.
Source: REST API §3 and Auth & permissions, API tokens (#1355); verified in the Academy lab, 1.0.0-beta.3

Narrow every token. Scopes (read, ipam:write, dns:write, dhcp:write, agent) and per-resource grants restrict what a token can do. A token can never do more than its owner, and leaving scopes empty means “whatever the owner can do”. In this release, a call outside a token’s scopes is refused with 401 “Token scope insufficient for this request”.
Source: REST API §3.3; verified in the Academy lab, 1.0.0-beta.3

Last Used is a fair staleness signal. A token’s Last Used is updated on reads as well as writes, at most once a minute per token, so a read-only automation token shows when it last ran. A token that shows “—” hasn’t made a call since it was created.
Source: Auth & permissions, API tokens (#1158); verified in the Academy lab, 1.0.0-beta.3

Change the forced password before you script against a fresh install. An account that still has to change its password can only reach the change-password, logout, me and password-policy endpoints. Every other call is refused.
Source: REST API §3.1

List endpoints come in three shapes. Core IPAM, DNS and DHCP lists return a bare JSON array. Newer routers wrap results in items, total, limit and offset. DNS records and DHCP leases answer items, total, page and page_size, and the audit list total and items. A subnet’s address list sets an X-Total-Count header when you page it. Check the route’s schema in /api/docs rather than assuming.
Source: REST API §6

A 404 can mean the feature module is off. A disabled module’s endpoints return 404, not 403, as if the feature weren’t installed, with a body such as Feature 'network.circuit' is disabled. A fresh install turns on only 16 of 57 modules, so check Features & Integrations before you debug the URL.
Source: REST API §5 (#1069)

Handle collision warnings on purpose. An IP create or update that collides returns 409 with requires_confirmation: true and a list of warnings. Only re-send with force=true after your code has looked at them.
Source: REST API §8 and IPAM features §15.11

Log the X-Request-ID. Every response carries it, and it appears in the server’s structured logs, so you can match a failed call to the exact log lines.
Source: REST API §8

Generate clients from the release’s openapi.json. Every release attaches a pinned openapi.json, which needs no credential. The live /api/openapi.json and the version details of GET /api/v1/version need a session or an API token: anonymous callers get 401 from the schema and "version": "restricted" from the version call. Regenerate your clients when the release notes say so.
Source: REST API §2.1 and §7.2 (#2935); verified in the Academy lab, 1.0.0-beta.3

Deduplicate webhook events on event_id. Typed-event webhooks are delivered at least once, except for a crash in the short window after the audit row commits. A receiver should ignore an event_id it has already processed.
Source: Observability §5b

Scripts must expect 503 during maintenance. When maintenance mode is on, every write returns 503 with Retry-After: 120, while reads keep working. Build back-off into your automation.
Source: System administration §5

Deleted objects wait in Trash for 30 days. IP spaces, blocks, subnets, zones, records and DHCP scopes (with their pools and reservations) are soft-deleted. Restore them from Administration → Trash. The purge window is under Settings → Data Retention → Trash (0–365 days; 0 keeps deleted rows forever).
Source: IPAM features §15.16 and System administration §2.7

A restore brings the whole batch back. A cascade delete stamps the parent and all its children with one deletion batch. Restoring a subnet brings back its DHCP scopes, its reverse zone and its DNS records; restoring a zone brings back its records, and the confirmation says how many rows return. A row only comes back under a live parent: a subnet whose block is still in Trash is refused (409), and the page offers to restore the block first.
Source: IPAM features §15.16, System administration, Trash and Data model, soft delete

Trash takes a subnet’s names out of DNS, and Restore puts them back. Moving a subnet, block or space to Trash withdraws the A, AAAA and PTR records SpatiumDDI published for its addresses, the gateway’s PTR included; restoring republishes them. In the lab, scratch-01 answered NXDOMAIN within two seconds of Move to Trash and resolved again within two seconds of Restore. Records you created by hand are left alone, and a restore is refused (409, naming the record) if someone created an identical record by hand meanwhile.
Source: IPAM features §15.16 (#1906); verified in the Academy lab, 1.0.0-beta.3

A permanent subnet delete refuses a subnet that isn’t empty. A delete with ?permanent=true (superadmin only) returns 409 while IPs or scopes remain, unless you add ?force=true. The network and broadcast rows, the untouched gateway row and lease-mirrored rows don’t count. The normal delete always goes to Trash.
Source: Troubleshooting, subnet delete is refused and Permissions model

The audit log can’t be edited, and it proves it. Every change writes an audit row in the same transaction. Database triggers refuse deletes and updates, and each row is hash-chained to the one before it. A nightly job checks the rows added since its last run, and a full pass re-checks the whole chain about once a week.
Source: System administration §2.7 and Data model, audit log

Pause a server for maintenance; don’t delete it. Per-server maintenance mode (pause) makes the agent stop applying config while it keeps heartbeating, and it masks offline alerts during the work. Deleting the row loses the server’s state instead.
Source: GitHub issue #182

Deleting a server row doesn’t stop the container. A deleted BIND9 or Kea agent registers itself again within about 30 seconds. Settings you had edited only in the GUI, such as notes, Path B credentials or rotated TSIG keys, don’t come back.
Source: Troubleshooting, recovering a deleted server

Maintenance mode freezes changes, not service. Turning on Maintenance Mode in Settings → Security makes every write return 503 for everyone except superadmins. Reads, and the agents’ config channel, keep working, so DNS and DHCP keep serving.
Source: System administration §5

Use two-person approval for risky changes. With the governance.approvals module on (it ships off), matching changes are queued as change requests. A Change Approver also needs the permission for the change itself.
Source: Permissions model, built-in roles

Know what each health endpoint checks. /health/live checks that the process is up, and /health/ready checks the database, Redis and that the schema is at head; neither needs a login. /health/platform always returns 200: an anonymous caller gets only the overall status, and a signed-in caller or an API token gets the per-component rollup.
Source: REST API §1 (#2935) and Observability §7

A red celery-beat doesn’t mean beat is broken. Beat only schedules the heartbeat task; a worker runs it. A missing tick can mean every worker slot is stuck, even while celery-workers still shows green.
Source: Troubleshooting, celery-beat unhealthy

Control-plane logs go to stdout, not the Logs page. Read them with docker compose logs -f api worker. The Logs page shows DNS and DHCP logs pushed by the agents, and Windows logs read over WinRM. No log store ships with SpatiumDDI, so for retention point your own collector (ELK, Loki, Splunk and so on) at the container output.
Source: Observability, overview and §3

Audit forwarding is best-effort. Forwarding runs outside the database transaction, so a dead SIEM never blocks or rolls back a change, and one failing target doesn’t affect the others. If you need guaranteed delivery, point a webhook at a durable queue.
Source: Observability §5.1

An agent turns “unreachable” after 90 seconds of silence. Agents send a heartbeat every 30 seconds, and a sweep that runs every minute marks a server unreachable after three missed heartbeats.
Source: DNS agent §4

Watch for the config-rejected chip. An agent whose new config the daemon rejects goes back to its last good config and keeps serving and heartbeating. The server row shows a chip, and the agent_config_rejected alert rule can notify you.
Source: DNS agent §3, last-known-good revert (#882)

Agents keep what they collect while the control plane is down. DNS and DHCP agents spool the logs, metrics and lease events the control plane couldn’t accept, and replay them on reconnect with their original timestamps. While an agent drains a backlog, its server row shows Replaying … backlog. If the spool hit its cap (AGENT_SPOOL_MAX_BYTES, 256 MiB by default) and dropped its oldest batches, the row shows Spool trimmed and the agent_spool_trimmed alert fires.
Source: Observability §4½.3 and §9.1 (#1077)

Alerts are evaluated every 60 seconds. Evaluate now on the Alerts page forces a pass. Alerts are delivered through the targets under Settings → Security → Audit Event Forwarding, the same syslog, webhook and SMTP targets that audit forwarding uses.
Source: Observability §9.1

Minimum Severity on a forwarding target filters alerts by rank. Critical alerts rank level with denied, so even the strictest threshold passes them. A target that received no alerts on 2026.09.04-1 because of its threshold starts receiving them after the upgrade; raise the threshold if the volume is unwanted. Test sends an audit-shaped payload, so confirm alert delivery with a real alert event.
Source: Observability §5.1 (#1031)

Expect alert rules you didn’t create, on by default. Recent releases seed rules that page with no setup: backup_failed and backup_stale for scheduled backups, agent_daemon_degraded when a DNS or DHCP daemon stops serving for 5 minutes, agent_spool_trimmed, duplicate-agent rules for a cloned agent, the critical dnssec_multi_signer and DHCP scope served uncoordinated, and disk, heartbeat, storage, CoreDNS and Postgres checks on appliances. Disable a seeded rule rather than delete it: rules seeded at startup come back after a restart. A manual Resolve stays in force while the subject keeps matching.
Source: Observability §9.1 (#1067, #1262, #2044, #2498)

For Microsoft Teams, create the webhook with the Workflows app. The old Office 365 “Incoming Webhook” connector is retired and no longer delivers. A Workflows webhook answers 202 Accepted before its flow runs, so Test reports success even when the card never posts; check the flow’s run history in Power Automate. Chat webhook URLs are write-only secrets: once saved, only the scheme and host are shown.
Source: Observability §5.1 (#1502, #1504)

Use Platform Insights before you set up Prometheus. Superadmins get Postgres, Redis and per-container views there. The containers tab needs the Docker socket mounted into the api container.
Source: System administration §0

/metrics needs a bearer token. It’s on by default and serves the API request family plus Python process metrics; the IPAM, DHCP, DNS, Celery and database families are still planned. Scrape it with the scrape token (PROMETHEUS_METRICS_TOKEN in .env) or any API token as Authorization: Bearer <token>; without one it answers 401. In the lab it answers on the web port too.
Source: Observability §6 (#1159) and REST API §1; verified in the Academy lab, 1.0.0-beta.3

Deployment (Docker Compose, Kubernetes, appliance)

Section titled “Deployment (Docker Compose, Kubernetes, appliance)”

Choose the DNS and DHCP containers with Compose profiles. Set COMPOSE_PROFILES=dns-bind9,dhcp in .env, as the Academy lab does, and a plain docker compose up -d starts both. --profile dns still works as an alias for dns-bind9.
Source: README, running the built-in containers

Built-in agents register into their AGENT_GROUP. A new BIND9 container joins the group its AGENT_GROUP names, or default if it names none. The PowerDNS and Technitium containers use default-powerdns and default-technitium. A group that doesn’t exist yet is created for you, with the description “Auto-created by agent registration”.
Source: DNS features §1, Getting Started §3 and DNS agent §8; label verified in the Academy lab, 1.0.0-beta.3

For real DHCP on Docker, use host networking. The shipped Compose file publishes Kea on host port 6767, which is fine for a lab. To serve a real LAN, run the Kea container with network_mode: host so it receives layer-2 broadcasts.
Source: Docker Compose deployment §10, host vs bridge networking

Keep Postgres, Redis and the API off the network. Never expose 5432 or 6379. Compose publishes the API port on 127.0.0.1 only (API_BIND), so browsers and remote agents go through the web port. If another host must reach the API directly, set API_BIND=0.0.0.0 and narrow TRUSTED_PROXY_CIDRS to the frontend’s address, because the API believes X-Real-IP from those peers.
Source: Docker Compose deployment §1 and §3 (#1221)

Size a single host realistically. Docker Compose needs 2 vCPU and 4 GiB of RAM at minimum (4 vCPU and 8 GiB recommended); the Academy lab’s containers use about 1.4 GB at rest. A single VM with 4 vCPU, 8 GB and a 50 GB SSD handles about 5,000 IPs, 50 subnets and 50 zones; beyond that, move the agents to their own hosts.
Source: Docker Compose deployment, prerequisites (#198), Sizing and Deployment topologies, Topology 1; verified in the Academy lab, 1.0.0-beta.3

Run exactly one beat. Celery beat is a singleton scheduler. Never run more than one, on Compose or on Kubernetes.
Source: Kubernetes deployment §1

On Kubernetes, reuse the same Helm release name. helm template, or an install under a new release name, generates a new SECRET_KEY. Always upgrade the same release, or pre-create the secret and set auth.existingSecret.
Source: Kubernetes deployment §2

The api autoscaler needs metrics-server. The api Deployment’s HPA is on by default and scales on CPU and memory; with the current chart defaults it stays at 2 replicas at rest. Install metrics-server, or set api.autoscaling.enabled: false and manage the replica count yourself. Upgrade an existing release once with --reset-then-reuse-values, because --reuse-values keeps the old chart’s api and worker sizing.
Source: Kubernetes deployment §5 and §8 (#3110)

On Helm, expose the DNS and DHCP agent Services on purpose. The agent Services default to ClusterIP, reachable in-cluster only. To answer clients on your LAN, set service: { type: LoadBalancer } on each server, with a MetalLB pool or loadBalancerIP. On k3s, disable servicelb first (--disable servicelb): it would take the node’s port 53 and break the node’s own DNS. A server upgraded from a chart that defaulted to LoadBalancer releases its external IP unless you set the type before upgrading.
Source: Kubernetes deployment §3, serving DNS on :53 (#3229)

DHCPv4 on Kubernetes needs hostNetwork or a relay. A Kea pod only hears client broadcasts with hostNetwork: true, or when a DHCP relay forwards them to it.
Source: Kubernetes deployment §3

On CloudNativePG, never delete the primary’s PVC. When a CNPG instance is stuck Pending, only delete a replica’s PVC. Confirm the role with the cnpg.io/instanceRole=primary label first, because deleting the primary’s PVC destroys the database.
Source: Kubernetes deployment §6

Pairing codes expire; the bootstrap key doesn’t. A single-use pairing code expires after 15 minutes by default, and a persistent one after 30 days. For re-installs, replacement hardware or air-gapped sites, paste the bootstrap key from Settings → Security → Agent bootstrap keys instead.
Source: OS appliance §9, pairing code

On a multi-node appliance, point everything at the control-plane VIP. Set a MetalLB pool and the VIP in Appliance → Network & Host; the frontend moves onto it, and the self-signed certificate grows to cover it. Point browsers and every off-cluster DNS and DHCP agent at the VIP rather than one node’s address, or an agent loses its control plane whenever that node is down.
Source: Deployment topologies, Topology 7 (#1103)

Size appliances by role. The docs recommend 4 vCPU, 8 GiB and a 40 GiB SSD for a one-node control plane, 48 GiB SSDs on every node of a three-node control plane, and 2 vCPU, 4 GiB and a 32 GiB SSD for a DNS/DHCP appliance. The installer refuses disks smaller than 32 GiB for any role, and pods don’t schedule below about 4 GiB of RAM on a control plane or 3 GiB on a DNS/DHCP appliance. For about a million devices, give every control-plane node 64 GiB of disk.
Source: OS appliance, recommended sizing and Sizing (#198)

Control-plane HA is not serving HA. DNS and DHCP roles are assigned per appliance, and only nodes with a role run DNS or DHCP. In a failover test with the roles on one node only, the survivors answered about 5% of queries while that node was down. Assign the roles on every node you expect to keep serving.
Source: OS appliance, control-plane high availability

Make lasting network changes in Appliance → Network & Host. SpatiumDDI regenerates its own NetworkManager profile from its saved state at every boot. The console (F4) warns before it opens nmtui, and offers when you leave to save mode, interface, address, prefix, gateway, DNS and MTU changes back (spatium-network-adopt --adopt does the same). Static routes, bonds and bridges can’t be adopted and still revert.
Source: OS appliance, reconfiguring the network after install (#1016)

Grow the appliance control plane in odd numbers. The embedded etcd needs a quorum, so the cluster grows 1 → 3 → 5 → 7, and the API refuses a promotion that would leave an even number of members.
Source: Deployment topologies, Topology 7

Take a backup before every upgrade. Go to Administration → Backup → Manual → Build + download, enter a passphrase of at least 8 characters that you will remember, and store the archive off the host. It is your only reliable way back if the upgrade goes wrong. The passphrase encrypts only the archive’s secrets.enc; the database dump inside the zip isn’t encrypted, so store the archive as carefully as the database itself.
Source: Docker Compose deployment §8 and System administration §2.9; verified in the Academy lab, 1.0.0-beta.3

Coming from 2026.09.04-1, go through the bridge release. Upgrade to 2026.10.02-1 first: it’s the last CalVer release, and the only one that knows 1.0.0 comes after 2026.x. Then upgrade to 1.0.0-beta.3. Take a backup before each hop, and never go back afterwards: an older release can’t run on the migrated database. The upgrade checklist walks through both hops.
Source: README, reaching 1.0.0 from a CalVer release and Release notes 1.0.0-beta.1

On Compose, an upgrade is pull, migrate, up. Outside the lab, run git pull first to refresh docker-compose.yml and .env.example. Then run docker compose pull, docker compose run --rm migrate and docker compose up -d; the api and worker wait for migrate to finish. The migrate step is safe to repeat, and each revision commits on its own, so if it stops (for example with deadlock detected while the old release is still running), run it again and it resumes. Containers you start with --profile instead of COMPOSE_PROFILES need the same flags on pull and on up -d.
Source: Docker Compose deployment §8 and README, upgrading; verified in the Academy lab, 1.0.0-beta.3

Pin SPATIUMDDI_VERSION in production. A pinned tag in .env gives you reproducible installs and no surprise upgrades. Change it on purpose and run the same commands. A pre-release such as 1.0.0-beta.3 is never what latest points to (that stays on 2026.10.02-1 until 1.0.0), so the pin is the only way onto it; the Academy lab pins 1.0.0-beta.3. A compose file from 2026.09.04-1 or older pulls from ghcr.io/spatiumddi/, which now answers denied; the current file pulls from ghcr.io/spatiumnorth/.
Source: README, upgrading and Release notes 1.0.0-beta.3

Downgrades aren’t supported anywhere. Database migrations only go forward, so going back to a release from before a schema change means restoring a backup. Read the target release’s changelog entry, especially its Breaking and Migrations sections, before you upgrade.
Source: README, upgrading

Coming from 2026.07.21-1 or earlier, don’t upload the slot image. On those releases the Web UI returns 413 on a slot-image upload before the API sees it. Use the URL source, or spatium-upgrade-slot apply over SSH.
Source: OS appliance, upgrade images (#787)

Use versioned asset names for air-gapped upgrades. The unversioned “latest” slot-image name only exists on the newest release and is pruned once a newer one ships, so a URL that pins a tag with the unversioned name returns 404. Download the file whose name includes the version, together with its .sha256.
Source: OS appliance, air-gap workflow (#789)

Upgrade both members of a Kea HA pair in the same window. A pair part-way through an upgrade keeps its current HA configuration, and the two switch together to authenticating each other with a per-group certificate when the last one upgrades. Crossing to Kea 3.0 is stricter: its HA protocol can’t talk to peers older than 2.7.0, so the pair loses lease sync while its members run different versions, and each server serves from its own lease store meanwhile.
Source: DHCP features §14 and DHCP drivers, Kea 3.0 (#637)

Rolling back PowerDNS takes two steps. PowerDNS 5.0 migrates its LMDB database on first open, and rolling back the image doesn’t roll back the database. Redeploy the older image, then run spatium-pdns-lmdb-guard restore latest in the DNS container (with the daemon stopped). The image takes a snapshot before the migration.
Source: DNS drivers §4.9 (#638)

Prove your backups with restore drills. The Restore Drills tab replays a destination’s newest archive into a throwaway database on a schedule and runs seven checks against it; your live database is never touched. A “Failed” verdict is a finding about the archive, while “Could not run” (error in the docs) means the drill was blocked (for example, no CREATEDB grant, or a database over 20 GiB).
Source: System administration §2.9, restore drills

A raw pg_dump doesn’t back up your secrets. The in-app archive includes the encrypted master key and the schema version; a plain SQL dump doesn’t include SECRET_KEY. Only use pg_dump for diagnostics or migration scripts.
Source: Docker Compose deployment §9

A backup from a newer release won’t restore onto an older one. A restore refuses an archive whose schema isn’t in this build’s migration chain, before any data is written; upgrade the target first. The allow_newer_schema override exists for the A/B-rollback case, and it can leave the database ahead of the running code, which the API’s readiness check then rejects.
Source: System administration §2.9, restore

Restoring onto a new install re-encrypts your secrets. The archive carries the source install’s keys, wrapped with your passphrase, and the restore re-encrypts every stored credential with the new install’s key; if that stops part-way, the restore result says so. You don’t copy SECRET_KEY across by hand.
Source: System administration §2.9, cross-install rewrap; verified in the Academy lab, 1.0.0-beta.3

On a multi-node appliance, upload slot images after you promote. An image uploaded while the box was still single-node sits on that node’s local disk, not on the shared mirror, so the other nodes can’t fetch it. Upload it again once the cluster has formed.
Source: OS appliance, air-gap workflow (#787)

Every restore saves a safety copy first. Before a restore overwrites anything, the current database is dumped to /var/lib/spatiumddi/backups/pre-restore-{ts}.zip (passphrase pre-restore-safety). That copy doesn’t protect an upgrade you ran without a restore, so the pre-upgrade backup is still essential.
Source: Docker Compose deployment §8

Start with IPAM only. The lowest-risk trial makes SpatiumDDI the source of truth for your address plan while your existing DNS and DHCP servers keep serving. Register Windows DHCP read-only to see live leases, then turn on write paths one subnet or one zone at a time.
Source: Getting Started, project status

Importers are one-shot, and they preview before they commit. The DNS, DHCP and NetBox importers show you exactly what they would create, then commit it. After that, SpatiumDDI owns the imported objects; there is no ongoing sync back.
Source: Migration

Running an import twice is safe. A second commit of the same source finds the existing objects and skips them by default. Choose overwrite (or rename for DNS) only when you mean to refresh something.
Source: Migration, re-running an import

DNSSEC doesn’t come across; sign again afterwards. Imports strip DNSKEY, RRSIG, NSEC and DS records with a warning. Sign the zone again from its DNSSEC card after you commit.
Source: Migration, DNS importer

The Technitium importer only takes primary zones. Secondary, stub, forwarder and catalog zones are reported as warnings, and disabled zones and records are skipped rather than turned back on.
Source: Migration, DNS importer

DHCP imports don’t bring live leases. Leases fill in again from the running daemon once a real Kea server joins the target group. When you move off Windows, the cutover’s lease handover turns live leases into reservations so clients keep their addresses.
Source: Migration, DHCP importer and DHCP features §15.7

Prepare an ISC dhcpd.conf before you upload it. Inline any include files first. Class declarations are shown for manual review and never created automatically. Failover and key blocks appear in the “didn’t import” panel.
Source: Migration, DHCP importer

AD “Secure only” zones block a Windows cutover. SpatiumDDI doesn’t implement GSS-TSIG, so once such a zone is served from SpatiumDDI, domain members silently stop registering. The cutover refuses these zones, and no flag overrides it. Change the zone on Windows, or leave it there. The cutover itself is the Windows cutover feature module, which ships off: turn it on under Features & Integrations first.
Source: Migration, Windows cutover (#756, #1069)

Lower TTLs on both sides, well before the switch. A zone left on a 3600-second TTL keeps a bad cutover visible for an hour after you revert it; at 300 seconds, it clears in five minutes. The cutover’s TTL pre-flight lowers SpatiumDDI’s copy and can put it back, but it deliberately doesn’t touch the live Windows zone: it gives you PowerShell to run there. A TTL drop on only one side buys nothing.
Source: Migration, Phase 3a TTL pre-flight

In a DHCP cutover, the Windows scope goes off before the managed one goes on. The switch deactivates the Windows scope before it activates the managed one, so two servers never answer the same subnet. The lease handover turns live Windows leases into reservations first, so renewing clients keep their addresses.
Source: DHCP features §15.7 and Migration, Phase 3b

Choose how NetBox VRFs become IP spaces. The NetBox importer’s default, per_vrf, creates one IP space per NetBox VRF plus a Global space for prefixes with no VRF. Choose single to put everything into one space you pick.
Source: Migration, NetBox importer

Import DHCP scopes in link-only mode first. Leave the IP space and block blank, and each imported scope links to an existing IPAM subnet with the same CIDR; scopes with no match report an error instead of creating a subnet somewhere you didn’t intend.
Source: Migration, DHCP importer

Change the default secrets before first start. POSTGRES_PASSWORD and SECRET_KEY must be changed (openssl rand -hex 32 for the key). The api refuses to start on the .env.example placeholder, on anything under 32 characters, or on anything that reads like a placeholder. An install already running on one must rotate it with python -m app.core.rotate_secret_key, which re-encrypts every stored credential (Rotating SECRET_KEY in the Docker docs). The Academy lab’s up.sh generates fresh random secrets into lab/.env for you.
Source: Docker Compose deployment §2, §3 and §8 (#1222)

Never turn on DEMO_MODE for a real install. Demo mode is built for throwaway public demos: it locks down features a stranger could abuse and forces a set of feature modules off. The shipped .env.example says to leave it false for real deployments.
Source: Docker Compose .env.example, section 9

Keep TLS verification on for agents. Remote agents verify the control plane’s certificate by default. With a private CA or a self-signed certificate, mount the CA into the agent and set TLS_CA_PATH. SPATIUM_INSECURE_SKIP_TLS_VERIFY=1 is for labs only, since anyone on the network path could then read the agent key and serve the agent its configuration. A remote agent that relied on the old skip default stops connecting after an upgrade from 2026.09.04-1 until you do this.
Source: Docker Compose deployment §10, prerequisites (#1220)

Pin CORS_ORIGINS and TRUSTED_HOSTS. Both default to *. Set TRUSTED_HOSTS to your real hostnames to guard against Host-header injection and DNS rebinding.
Source: REST API §9

Stored credentials are encrypted and never shown again. Provider secrets, agent keys and cloud tokens are Fernet-encrypted at rest. Leave a secret field blank when you edit to keep the stored value. Revealing agent bootstrap keys requires signing in again and is audited.
Source: Auth & permissions, secrets at rest and Docker Compose deployment §10

SpatiumDDI sends no telemetry, and makes one outbound call by default. That call is a daily, anonymous check of GitHub for a newer release. Turn it off under Settings → Application → Updates; every feature works air-gapped.
Source: Privacy and System administration §7

Think before you turn on DHCP fingerprinting. It sniffs every DHCP transaction, which is sensitive on guest and BYOD networks. With a Fingerbank key, it also sends observed signatures to Fingerbank for lookup. It is off by default.
Source: DHCP features §17 and Privacy

The Copilot sends prompts to whichever provider you choose. Prompts and tool results, which can include hostnames, subnets and records, go to the configured AI provider. An on-premises model served through the OpenAI-compatible driver keeps everything on your network.
Source: Privacy

Support bundles are scrubbed, not secret. Diagnostics → Support bundle always leaves out secrets and replaces IPs, hostnames, MACs and usernames with stable fake values. The scrubbing is best-effort, so read the preview before you attach the bundle to a public issue.
Source: System administration §9

Give technitium_api a token from a dedicated user, and expect limits. The agentless Technitium driver acts with the permissions of the user who owns its API token. Make that user a member of Technitium’s DNS Administrators group rather than using the admin account; “Zones: Modify” alone can’t delete zones or see older ones, so the connection test reports 0 zones. The driver needs Technitium 15.0 or later and is experimental in 1.0: one server per group, zones and records only, and DNS pools, zone ACLs, group options, catalog zones and blocklists are refused (422).
Source: DNS features §0 (#1784)

Point the appliance at your own NTP server. Leaving the installer’s Time source blank means no time sync at all. For a box that mustn’t reach the internet, give it your internal NTP server rather than nothing: nodes that disagree about the time show up, among other things, as celery-beat clock-skew warnings.
Source: GitHub issue #1002 and Troubleshooting, celery-beat unhealthy