OPNsense Unbound Host Overrides
This role manages DNS host overrides (local DNS records) in OPNsense Unbound resolver via the REST API.
OPNsense Unbound Host Overrides Role
Overview
This role manages DNS host overrides (local DNS records) in OPNsense Unbound resolver via the REST API. It provides full lifecycle management: creating new overrides, updating existing overrides when configuration changes, and deleting orphaned overrides that exist on OPNsense but are no longer defined in the vars files.
Purpose
- Local DNS Management: Define custom DNS names that resolve to specific IP addresses
- Code as Configuration: Define DNS overrides in YAML
- API-Based: Reliable automation via OPNsense REST API
- Full Lifecycle Management: Create, update, and delete overrides
- Idempotent: Safe to run multiple times using hostname+domain as unique key
- Automatic Cleanup: Removes orphaned overrides not defined in vars
- Domain Blocking: Block malicious TLDs by pointing them to null addresses
Requirements
- Ansible 2.9 or higher
- OPNsense firewall with API access enabled
- API key and secret stored in Ansible Vault
- Network connectivity to OPNsense (VLAN10)
- OPNsense user with Unbound permissions
What are Host Overrides?
Host overrides are local DNS records that:
- Override public DNS for specific hostnames
- Provide internal name resolution for local services
- Block domains by pointing them to invalid addresses
- Support A, AAAA, and MX record types
Example: server.homelab → 192.168.x.x
Role Variables
Required Variables
| Variable | Required | Description |
|---|---|---|
vault_opnsense_bjoffrey_user_api_key | Yes | OPNsense API key (in vault) |
vault_opnsense_bjoffrey_user_api_secret | Yes | OPNsense API secret (in vault) |
opnsense_unbound_host_overrides_list | Yes | List of host overrides |
Optional Variables
| Variable | Default | Description |
|---|---|---|
opnsense_unbound_host_overrides_validate_certs | true | Validate SSL certificates |
Override Structure
Each override in the list has these fields:
Required fields:
- hostname: "grafana" # Hostname portion (use "*" for wildcard)
domain: "localdomain" # Domain portion
server: "192.168.x.x" # IP address to resolve to
Optional fields:
enabled: "1" # Enable (1) or disable (0)
rr: "A" # Record type: A, AAAA, or MX
ttl: "" # Time to live (empty = default)
mxprio: "" # MX priority (only for MX records)
mx: "" # Mail server (only for MX records)
description: "Grafana" # Human-readable description
Complete example:
opnsense_unbound_host_overrides_list:
# Block malicious TLD
- hostname: "*"
domain: "zip"
server: "192.168.x.x"
description: "Block zip domain"
# Internal service
- hostname: "grafana"
domain: "localdomain"
server: "192.168.x.x"
description: "Grafana monitoring"
# Management interface
- hostname: "grafana-mgmt"
domain: "localdomain"
server: "192.168.x.x"
description: "Grafana mgmt interface"
Idempotency
The role uses hostname+domain as the unique identifier:
server.homelabmaps to a specific UUID on OPNsense- Create: New hostname+domain combination → creates override
- Update: Existing hostname+domain with changed fields → updates override
- Delete: hostname+domain on OPNsense but not in vars → deletes override
Important: Removing an override from your vars file will delete it from OPNsense on the next run.
Dependencies
This role has no dependencies on other Ansible roles, but requires:
- OPNsense firewall with API enabled
- Unbound DNS service enabled
- API key with Unbound management permissions
- Ansible Vault for storing API credentials
Example Playbook
Basic Usage
---
- name: Configure OPNsense Unbound Host Overrides
hosts: mint-vm
gather_facts: false
vars_files:
- ../../roles/opnsense_unbound_host_overrides/vars/host_overrides.yml
tasks:
- name: Configure host overrides
ansible.builtin.include_role:
name: opnsense_unbound_host_overrides
Inline Variables
---
- name: Configure Unbound Overrides
hosts: mint-vm
gather_facts: false
vars:
opnsense_unbound_host_overrides_list:
- hostname: "myservice"
domain: "localdomain"
server: "192.168.x.x"
description: "My custom service"
roles:
- opnsense_unbound_host_overrides
What This Role Does
- Fetches existing overrides via
/api/unbound/settings/searchHostOverride - Builds hostname+domain → UUID mapping for idempotency
- Creates new overrides via
/api/unbound/settings/addHostOverride - Updates existing overrides via
/api/unbound/settings/setHostOverride/{uuid} - Deletes orphaned overrides via
/api/unbound/settings/delHostOverride/{uuid} - Reconfigures Unbound via
/api/unbound/service/reconfigureto apply changes - Displays summary of configured overrides
OPNsense API Endpoints
Search Host Overrides
GET /api/unbound/settings/searchHostOverride
Authorization: Basic (API key:secret)
Returns all host overrides with their UUIDs.
Add Host Override
POST /api/unbound/settings/addHostOverride
Authorization: Basic (API key:secret)
Content-Type: application/json
Request body:
{
"host": {
"enabled": "1",
"hostname": "grafana",
"domain": "localdomain",
"rr": "A",
"server": "192.168.x.x",
"description": "Grafana"
}
}
Update Host Override
POST /api/unbound/settings/setHostOverride/{uuid}
Authorization: Basic (API key:secret)
Content-Type: application/json
Delete Host Override
POST /api/unbound/settings/delHostOverride/{uuid}
Authorization: Basic (API key:secret)
Reconfigure Unbound
POST /api/unbound/service/reconfigure
Authorization: Basic (API key:secret)
Applies pending DNS configuration changes.
Use Cases
Internal Service Resolution
- hostname: "nextcloud"
domain: "localdomain"
server: "192.168.x.x"
description: "Nextcloud file sharing"
Management Interface Separation
# Service VLAN
- hostname: "grafana"
domain: "localdomain"
server: "192.168.x.x"
description: "Grafana (service)"
# Management VLAN
- hostname: "grafana-mgmt"
domain: "localdomain"
server: "192.168.x.x"
description: "Grafana (management)"
Block Malicious TLDs
- hostname: "*"
domain: "zip"
server: "192.168.x.x"
description: "Block .zip TLD (phishing)"
- hostname: "*"
domain: "mov"
server: "192.168.x.x"
description: "Block .mov TLD (phishing)"
MX Record
- hostname: "mail"
domain: "localdomain"
rr: "MX"
mxprio: "10"
mx: "server.homelab"
description: "Internal mail server"
Security Considerations
- API Credentials: Stored in Ansible Vault
- HTTPS: Uses SSL/TLS for API calls
- Basic Auth: API key/secret authentication
- Certificate Validation: Enabled by default
- Domain Blocking: Can block phishing TLDs
Troubleshooting
”Authentication failed” errors
Cause: Invalid API key/secret
Solution: Verify credentials in vault and test API manually.
Overrides not resolving
Cause: Unbound not reconfigured
Solution: Check role output for reconfigure task execution.
DNS not updating
Cause: Client DNS cache
Solution: Flush DNS cache on client (ipconfig /flushdns or systemd-resolve --flush-caches)
Best Practices
- Consistent naming: Use clear hostname conventions
- Separate management: Create
-mgmtentries for management VLANs - Document overrides: Use descriptive descriptions
- Block malicious TLDs: Add wildcard blocks for known-bad TLDs
- Version control: Store overrides in git
- Review before running: Removing overrides from vars deletes them
Related Roles
This role works well with:
- opnsense_firewall: Configure firewall rules for DNS traffic
- opnsense_aliases: Create aliases for DNS servers
License
MIT
Author
Created for homelab infrastructure management.