e360546de9f87c6401f136f10ac53c198c354c40
UniFi Firewall Updater
Automatically updates UniFi Dream Machine firewall rules with your current public IPv4 and IPv6 addresses via the UniFi Network API.
Overview
This tool:
- Fetches your public IPv4 and IPv6 addresses
- Reads firewall rule definitions from a YAML config file
- Creates or updates policies on your UniFi controller to use those addresses
- Optionally sends notifications via NTFY when rules change
Designed to run as a one-shot script (e.g., via cron or systemd timer) whenever your public IP changes.
Quick Start
- Copy
.env.exampleto.envand fill in your UniFi credentials - Edit
config/rules.yamlto define your firewall rules - Install dependencies and run:
uv sync
uv run main.py
Configuration
Environment Variables
| Variable | Required | Description |
|---|---|---|
UNIFI_HOST |
Yes | UniFi controller URL (e.g., https://10.1.0.1) |
UNIFI_SITE_ID |
Yes | UniFi site ID |
UNIFI_API_TOKEN |
Yes | UniFi Network API token |
UNIFI_VERIFY_SSL |
No | Verify SSL certificates (default: false) |
CONFIG_FILE |
No | Path to rules YAML (default: config/rules.yaml) |
LOG_LEVEL |
No | Log level: DEBUG, INFO, WARNING, ERROR (default: INFO) |
DEBUG |
No | Attach debugpy on port 5678 (default: false) |
NTFY_URL |
No | NTFY server URL for notifications |
NTFY_TOPIC |
No | NTFY topic to publish to |
NTFY_API_KEY |
No | NTFY API key for authentication |
Rules YAML
Each rule defines a firewall policy that will use your public IP as the source:
rules:
- name: "Allow External to Gateway HTTP(S)"
source_zone: "External"
dest_zone: "DMZ"
ip_version: "IPV6"
action: "ALLOW"
allow_return_traffic: true
protocol: "tcp"
dest_ports: [80, 443]
dest_port_ranges:
- start: 8000
stop: 8080
logging_enabled: false
enabled: true
| Field | Required | Description |
|---|---|---|
name |
Yes | Unique policy name |
source_zone |
Yes | Source zone name (e.g., WAN, External) |
dest_zone |
Yes | Destination zone name (e.g., LAN, DMZ) |
ip_version |
No | IPV4 or IPV6 (default: IPV4) |
action |
No | ALLOW, BLOCK, or REJECT (default: ALLOW) |
allow_return_traffic |
No | Allow return traffic (default: true) |
protocol |
No | Protocol filter (e.g., tcp, udp) |
dest_ports |
No | List of destination ports |
dest_port_ranges |
No | List of {start, stop} port ranges |
logging_enabled |
No | Enable policy logging (default: false) |
enabled |
No | Enable the policy (default: true) |
Development
uv run pytest -v # Run tests (all mocked, no live API calls)
uv run ruff check # Lint
uv run pyright # Type check (strict mode)
Run a single test:
uv run pytest tests/test_unifi_firewall.py::TestBuildPolicyPayload::test_with_dest_ports -v
Container
Build and run with Podman:
podman build -t unifi-firewall .
podman run --env-file .env -v $(pwd)/config:/app/config unifi-firewall
Config is mounted at runtime via volume. Environment variables are passed at runtime.
How It Works
- Loads rules from
config/rules.yaml - Fetches public IPv4/IPv6 via
curl ifconfig.me - Lists zones from UniFi to resolve zone names to IDs
- For each rule:
- Finds existing policy by name
- Skips if IP already matches
- Updates if policy exists with different IP
- Creates if policy does not exist
- Sends NTFY notification if rules were created, updated, or failed
API Details
Uses UniFi Network API v1 at:
{UNIFI_HOST}/proxy/network/integration/v1/sites/{UNIFI_SITE_ID}
Authentication via X-API-Key header. See network_v10.4.57_openapi.json for the full schema.
Languages
Python
98.8%
Dockerfile
1.2%