Skip to content

UniFi

The UniFi controller runs the switching fabric: VLANs on switch ports, DHCP Guard, IGMP snooping and mDNS scope. It is not the gateway and not a DHCP server here. The Firewalla routes and serves DHCPv6 on every network plus DHCPv4 on Guest and Telework; the Kea pair on the resolvers serves DHCPv4 on the other networks. A setting on the controller can still veto work on those layers: on 2026-08-24, Main’s DHCP Guard trusted only the Firewalla and silently dropped every Kea offer.

URL https://unifi.fzymgc.house (192.168.40.2, Core). Use the hostname; the certificate does not cover the IP
Network application 10.6.101
API UniFi OS login POST /api/auth/login; Network API under /proxy/network/api/s/default/
Path Account Role Used by
secret/fzymgc-house/infrastructure/unifi/readonly automation-readonly Network: View Only Agents and ad-hoc probes; the controller itself rejects writes
secret/fzymgc-house/infrastructure/unifi/admin automation-admin Network: Full Management The main-cluster-unifi workspace only (policy terraform-unifi)

Humans use their own logins (the local admin is in 1Password).

tf/unifi (workspace main-cluster-unifi, agent mode, auto-apply on merge) declares every network on the controller as a unifi_network, adopted with import {} blocks. The provider is our fork, app.terraform.io/fzymgc-house/unifi (runbook).

  • Change the fabric through a PR, not the UI. A UI change shows up as drift on the next plan.
  • DHCP Guard is dhcp_guarding = { enabled, servers }, at most three servers. Any cut that brings a new DHCP server onto a guarded VLAN adds its address first.
  • On the corporate networks the subnet, domain and DHCP fields are inert leftovers: UniFi serves DHCP nowhere. Only the VLAN, DHCP Guard and basic settings are live anywhere.
  • switch-main’s ports are unifi_device_ports.switch_main in tf/unifi/ports.tf, which holds every port definition on the switch.
    • A port that isn’t listed runs the controller’s default configuration, so a port changed in the UI shows up as drift.
    • Each port states only the settings that differ from port to port: name, VLANs, LAG topology, PoE, and a few per-port ones. Settings the UI stamps identically everywhere (port security, isolation, storm control, LLDP-MED, STP mode, keepalive, egress rate limiting, autoneg) are left out. The controller keeps them, and Terraform does not assert them, so a UI change to one of those is not drift. State one in ports.tf if it ever needs to be.
    • The resolver trunks are port 12 (sandiego) and port 13 (portland): Core native, every VLAN tagged (tagged_vlan_mgmt = "auto").
    • unifi_device is not used, and its port_override block is deprecated in our fork.
  • Not yet managed: the other switches’ ports and the port profiles. AllMainDefault is used by three other switches.
  • Where to edit: the port’s entry in ports.tf. The plan shows the keys you changed. It also shows “known after apply” for keys the controller sets as a side effect, such as lag_idx on a new aggregation lead.
  • VLANs: native_networkconf_id plus tagged_vlan_mgmt. The values are auto (every network tagged), block_all (nothing tagged), or custom together with excluded_networkconf_ids.
    • The controller derives the legacy forward field and ignores writes to it.
    • A custom port that excludes every VLAN network is stored as block_all, so declare block_all for that case.
  • Link aggregation: declare the group on its lead port only, with op_mode = "aggregate" and aggregate_members. The switch-main groups are 6→7, 23→24 and 25→26.
  • Removing a setting from ports.tf leaves its value on the controller. To change a value, state the new one.
  • Never delete the resource. Destroying it resets every port on the switch, uplinks included, and prevent_destroy blocks that plan. To stop managing the switch, use removed { from = unifi_device_ports.switch_main; lifecycle { destroy = false } }.
Network Purpose VLAN DHCP Guard
Core corporate (leftover) untagged off
Lab corporate (leftover) 1000 off
BackupNet vlan-only, disabled 2000 off
Main vlan-only 3000 on: .20.6, .20.7, .20.5
MainWireless vlan-only 3001 off
IoT vlan-only 3020 off
Guest corporate (leftover) 3100 off
Telework corporate (leftover) 3200 off
Thirdparty corporate (leftover) 3300 off
  • DHCP Guard is dhcpguard_enabled plus the trusted list in dhcpd_ip_1, dhcpd_ip_2, dhcpd_ip_3 on each rest/networkconf object. There is no dhcpguard_ip key; reading one returns nothing, whatever is configured.
  • vlan is an integer on most networks, a string on some ("1000" on Lab), and absent on the untagged one.
  • Change history: POST /proxy/network/v2/api/site/default/system-log/admin-activity with {timestampFrom, timestampTo, pageSize, pageNumber} returns who changed which field, when, from which app, with old and new values. The View Only account may run it. .planning/spikes/001-networkconf-guard-fields/probe_admin_activity.py wraps it.
  • The iOS app has written DHCP Guard fields (2026-08-30 it disabled Main’s guard); check the activity log when the fabric behaves unexpectedly.