TL;DR#
A Python script that identifies every device on your network in PAN-OS traffic logs, without Active Directory. Combines Pi-hole DNS, UniFi Controller, and DHCP leases into one priority merge. 124 devices named on my PA-440.
Before:
| |
After:
| |
Here’s what the traffic logs look like on my PA-440 with User-ID populated:

Quick Start (5 minutes)#
What you need:
- A PAN-OS firewall acting as DHCP server (any model, any version with XML API)
- An admin API key for the firewall
- Python 3.6+ on any machine that can reach the firewall over HTTPS
Step 1: Generate a PAN-OS API key (run this once):
| |
Copy the key from the <key> element in the response. See the PAN-OS API key generation docs for details.
Step 2: Create the script and config file:
| |
Then create sync_userid_dhcp.py with the full source at the bottom of this post.
Step 3: Run it:
| |
That’s it for the basic DHCP setup. Your traffic logs will now show hostnames for every device that sends a DHCP hostname (option 12). Read on to add UniFi and static DNS sources for full coverage.
Prerequisites (Things You Must Configure First)#
The script pushes mappings via API, but PAN-OS needs to be told to use those mappings in logs and policies. If you skip this, the mappings exist but don’t appear anywhere.
1. Enable User-ID on Your Zones#
In the PAN-OS web UI: Device > User Identification > User Mapping
Then for each internal zone (Network > Zones > click zone name):
- Check Enable User Identification
Or via CLI:
| |
Enable it on every zone where you want to see device names in traffic logs. Don’t enable it on your WAN/untrust zone.

2. Verify the API Key Works#
Test your API key can push User-ID entries:
| |
You should see <response status="success">. The test mapping expires in 5 minutes.
3. No Firewall Configuration Changes Needed#
Unlike the syslog loopback approach, this script requires zero configuration on the firewall itself. No syslog profiles, no parse profiles, no server monitors. Just API calls from an external host.
How the Script Works#
The Core Idea#
No single source knows every device:
- DHCP only sees dynamic clients (and some don’t send hostnames)
- DNS records only cover devices you’ve manually documented
- UniFi only sees devices on its switches/APs (not VMs behind virtual bridges)
The script queries all three, merges them by IP with a priority order, and pushes the combined list to PAN-OS via the User-ID XML API.
The Priority Merge (This Is the Key Concept)#
The priority order determines which source wins when multiple sources know the same IP:
| Priority | Source | What It Covers | Example Names |
|---|---|---|---|
| 1 | Pi-hole A records | Static infrastructure (Proxmox, Caddy, NFS) | graylog, pve5, caddy |
| 2 | UniFi client names | User-assigned labels in the UniFi UI | Ring - Front Door, NAS-920 |
| 3 | DHCP hostnames | What the device reports via DHCP option 12 | iphone, mario-pc |
| 4 | UniFi OUI vendor | Manufacturer from MAC address prefix | Amazon-08568d, Ring-7781ac |
The merge is a Python dictionary where the first source to claim an IP wins:
| |
Why this order? A real example: my Proxmox host at 192.168.30.205 appears in:
- Pi-hole A records as
pve5(clean, short, I chose this name) - UniFi as
PVE5(from the controller’s device fingerprint) - DHCP: not at all (static IP, no lease)
The A record wins because it’s manually curated and the shortest/cleanest.
The API Push#
PAN-OS accepts bulk User-ID entries via a single API call using the User-ID XML API:
| |
Key fields:
name: whatever you want to appear in traffic logs (hostname, device name, etc.)timeout: minutes until the mapping expires (180 = 3 hours)- No commit needed. Mappings are dynamic and ephemeral.
Adding UniFi Controller (Optional, Recommended)#
If you run UniFi switches or APs, you already have a goldmine of device identity data. The controller fingerprints every connected client and knows:
| Field | What It Is | Example |
|---|---|---|
name | Label you set in the UniFi UI | Ring - Front Door |
hostname | DHCP hostname the device reported | LG_Smart_Fridge2_open |
oui | Manufacturer from MAC address lookup | Amazon Technologies Inc. |
mac | Device MAC address | 54:e0:19:83:d4:82 |
What UniFi reveals that DHCP alone misses:
| Device | DHCP Hostname | UniFi Knows |
|---|---|---|
| Fire TV Stick | [Unavailable] | name: HO-FTV4kUltra |
| Ring Doorbell | (empty) | name: Ring - Front Door, OUI: Ring LLC |
| TP-Link Smart Plug | (empty) | hostname: KP115, OUI: TP-Link |
| Smart Fridge | (empty) | hostname: LG_Smart_Fridge2_open |
| Amazon Fire Stick | (empty) | OUI: Amazon Technologies Inc. |
UniFi Setup#
Add UniFi credentials to your .env file:
| |
The script auto-detects the UniFi .env if it’s at ../../unifi/.env relative to the script, or reads from environment variables UNIFI_URL, UNIFI_USER, UNIFI_PASS.
UniFi API notes:
- Uses session-based auth (login with username/password, get cookie, query, logout)
- The endpoint is
GET /api/s/default/stat/sta(returns all connected clients) - Works with UniFi Network Application 7.x+ and UniFi OS consoles
- Port 8443 is the default for self-hosted; CloudKey/UDM may differ
- The script handles SSL certificate warnings automatically (self-signed certs are common)
Tip: Name Your Devices in UniFi#
Open the UniFi web UI, go to Clients, click any device, and set a friendly name. These names become Priority 2 in the merge and show up in your firewall logs. I named my Ring doorbells, NAS boxes, and KVMs this way.
Adding Static DNS Records (Optional, For Infrastructure)#
Devices with static IPs (servers, Proxmox hosts, switches, access points) never appear in DHCP. If you maintain Pi-hole A records or any hostname-to-IP file, the script can read it.
Pi-hole A Records Format#
The script parses this YAML format (used by Pi-hole’s DNS management):
| |
Don’t use Pi-hole? You can substitute any hostname-to-IP source. The script just needs a file it can parse. Adapt the get_static_mappings() function to read your format (CSV, JSON, hosts file, whatever you use).
The script auto-discovers the A records file at ../../pihole/dns-records/a-records.yaml relative to its location, or you can set the ARECORDS_YAML_PATH environment variable.
Domain suffixes are stripped automatically. graylog.mydomain.local becomes graylog in traffic logs. When an IP has multiple DNS names, the shortest one is kept.
Gotchas I Hit (Save Yourself the Debugging)#
1. The Syslog Loopback Doesn’t Work#
The commonly-referenced approach configures the firewall to forward DHCP lease logs back to its own syslog listener. I implemented all four components (syslog profile, log match filter, parse profile, server monitor).
Result: 0 messages. PAN-OS cannot deliver UDP syslog to its own interface. I tested the OOB management IP, the in-band data-plane IP, localhost, and even removed the syslog service route to force management-plane routing. None worked.
2. DHCP Reservations Were Being Skipped#
PAN-OS marks DHCP reservations as state=reserved. My first version skipped all reserved entries because most are MAC-only reservations without hostnames. But my main workstation had a DHCP reservation with a hostname, and it was invisible.
Fix: Only skip reserved entries that have no hostname. If the reservation includes a hostname, use it.
3. HTTP 414 URI Too Long#
With 124 entries in a single URL-encoded request, the PAN-OS web server rejects it. The script batches into groups of 50.
4. UniFi API Requires Session Auth, Not API Keys#
The UniFi API key (X-API-KEY header) returns 0 clients for the /stat/sta endpoint. You must use session-based auth: POST to /api/login with username/password, receive a cookie, then query with that cookie.
5. PAN-OS XML Schema Differs From CLI Syntax#
If you ever need to configure User-ID via the XML API directly, the element structure doesn’t match what the CLI set commands suggest. The action=complete endpoint is your schema explorer:
| |
This returns all valid child elements at any xpath. I discovered that syslog-parse-profile uses <entry name="..."> reference format (not text content), and event-type nests inside the profile entry. See the PAN-OS XML API config reference for more on the action=complete endpoint.
Automating It (Cron)#
The script should run every 5 minutes. Mappings timeout after 180 minutes (3 hours), so you have 36 missed runs of buffer before mappings go stale.
Option A: Simple Cron#
| |
Option B: Ansible + Semaphore (What I Use)#
I run it via a Semaphore CI/CD template with an Ansible playbook that writes inline Python to /tmp, executes it, and cleans up. This gives me a web UI for monitoring runs, failure alerts, and centralized credential management.

The playbook is self-contained: it writes the full Python script inline (via ansible.builtin.copy), executes it, then cleans up the temp file. No git clone of the script needed. Credentials come from Semaphore’s encrypted environment variables.
Click to expand sync-userid-dhcp.yml (Ansible playbook)
| |
Key design choices:
- The Python script is written to
/tmpat runtime, not cloned from git (avoids SSH key management in Semaphore) changed_when: "'Pushed' in sync_result.stdout"gives accurate change tracking in the Semaphore UI- Environment variables pass credentials without them ever touching disk
- The playbook runs on
localhostwithconnection: localsince it only needs HTTPS access to the firewall and UniFi controller
Verifying It Works#
After running the script, check the firewall:
| |
Or via CLI (SSH to firewall):
| |
Check traffic logs (Monitor > Traffic): the “Source User” and “Destination User” columns now show device names.

What I Evaluated and Rejected#
Before building this, I researched every tool I could find:
| Tool | Why I Skipped It |
|---|---|
| PacketFence | Enterprise NAC. Requires 16GB RAM, MariaDB, FreeRADIUS. Wants to be your DHCP server. No native PAN-OS User-ID integration. Massive overkill for device naming. |
| p0f | Passive OS fingerprinting. Signature database last updated ~2012. Cannot identify modern iOS, Android, or Windows 11. Unmaintained. |
| Fingerbank | Cloud API for device fingerprinting (by the PacketFence team). Interesting, but UniFi already does 80% of this for free. Free tier is 300 req/hour. |
| mDNS/Bonjour sniffing | Requires a listener daemon on each VLAN. UniFi already catches most mDNS devices through its normal operation. |
| PAN-OS Device-ID | Maps device type (e.g., “IP Camera”), not hostname. Requires a paid IoT Security subscription for full coverage. 0 entries without the license. |
| NetBIOS scanning | Windows-only, active scanning, UDP 137. Marginal benefit when UniFi already has the data. |
Extending the Script#
The script is designed to be extended. Every source follows the same pattern:
| |
Then insert it at the right priority level in main(). The merge logic handles deduplication automatically (first source to claim an IP wins).
Ideas for additional sources: Wazuh agent list, Proxmox API (VM/LXC names to IPs), SNMP discovery (device sysName), or ARP table + reverse DNS.
Results#
124 devices identified across 6 VLANs, zero pip dependencies.
| Source | Devices | Examples |
|---|---|---|
| Pi-hole A records (static DNS) | 69 | graylog, sema, atlas, pve5, caddy |
| UniFi hostnames | 34 | LG_Smart_Fridge2_open, KP115, HS200 |
| UniFi user-assigned names | 5 | Ring - Front Door, NAS-920-Eth1 |
| UniFi OUI vendor fallback | 10 | Amazon-08568d, Tuya-5b4b03, Ring-7781ac |
| PAN-OS DHCP leases | 6 | tesla, Mario-s-S23-Ultra |
| Total | 124 |
The script is ~300 lines of Python using only the standard library (urllib, ssl, json, re, xml.etree). No pip install needed. Runs on any host with HTTPS access to your firewall.
Full Script#
The complete script below is the DHCP-only version (no UniFi or static DNS). Add the other sources by following the sections above.
Click to expand sync_userid_dhcp.py (~150 lines)
| |
To add UniFi and static DNS sources, follow the sections above. The merge pattern is the same: query the source, add to the all_mappings dictionary (first IP wins), push the combined result.
Official Documentation References#
- PAN-OS XML API: Get Your API Key
- PAN-OS XML API: User-ID Request Types
- PAN-OS Admin: Enable User-ID
- PAN-OS Admin: Configure User Mapping
- PAN-OS XML API: Configuration Actions (action=complete)
- PAN-OS Admin: Device-ID Overview (IoT Security)
- UniFi Controller API Reference
- Original inspiration: User-ID from DHCP (James Holland)
Built on a PA-440 running PAN-OS 11.2.10-h2. Automated via Semaphore (every 5 min). Compatible with any PAN-OS firewall that has the XML API enabled.