OPNsenseIV · OPNsense ArchitectureOPNsense architecture
The web UI and REST API surface
What you'll learn
- Describe the components that serve the OPNsense web UI and API
- Authenticate to the REST API and identify the supported methods
- List the API endpoints an operator uses most often
- Recognise the security implications of exposing the GUI/API beyond the management network
Prerequisites
Verified against OPNsense 25.x · FreeBSD 14.x · PF (FreeBSD packet filter) FreeBSD 14.x · Unbound 1.20+ · Kea DHCP OPNsense 25.x plugin · WireGuard in-kernel + OPNsense plugin · strongSwan (IPsec plugin) OPNsense 25.x plugin · OpenVPN 2.6.x · Suricata 7.x · 2026-08-14
The web UI is the operator’s primary interface. The REST API is the automation’s primary interface. Both are served by the same backend — a PHP application framework running on FreeBSD’s nginx + PHP-FPM stack. They share authentication, authorisation, the configuration XML, and the template resolvers. The course treats them as one surface with two interfaces; an operator who knows both can drive OPNsense from a browser, from a curl shell, or from an Ansible playbook without surprises.
This lesson covers the components that serve the UI and API, the authentication model, the endpoints an operator uses most, and the security implications of exposing the management surface.
What serves the UI and the API
Three processes cooperate to serve every UI page and every API request.
nginx (port 443) → PHP-FPM (/var/run/php-fpm.socket) →
opnsense MVC framework → /conf/config.xml + configd →
template resolvers + FreeBSD services
- nginx. Listens on TCP 443 (HTTPS) and TCP 80 (HTTP, which redirects to HTTPS by default). Serves static assets (CSS, JS, images) directly. Forwards dynamic requests to PHP-FPM.
- PHP-FPM. A pool of PHP workers that execute OPNsense’s PHP framework. The framework reads the config XML, applies business logic, validates changes, and writes back the XML.
- OPNsense MVC framework. Lives under
/usr/local/opnsense/mvc/. Implements the routes that map URLs to controllers, the controllers that process requests, and the views that render HTML or JSON responses.
The framework is open source (Apache 2.0) and is the same code
that ships in OPNsense. An operator who wants to understand a
specific UI page reads the corresponding controller under
/usr/local/opnsense/mvc/app/controllers/.
$ service nginx status; service php-fpm status; service configd statusnginx is running as pid 1138.
php_fpm is running as pid 1137.
configd is running as pid 1139.Illustrative output
Authentication
Both the GUI and the API authenticate against the same backend (user accounts in the config XML, or an external backend like LDAP, RADIUS, or — via plugin — TOTP). The same credentials work in both. The same group memberships determine the same authorisation.
GUI authentication. Browser submits the login form over HTTPS; the server validates the credentials, sets a session cookie (HTTP-only, Secure, SameSite=Lax), and the framework consults the cookie on every subsequent request.
API authentication. The API accepts one credential type: an API key/secret pair, presented over HTTP Basic.
- Generate the pair. In the user manager
(
System → Access → Users), edit the account and add an entry under API keys. The browser downloads a single ini-formatted text file containingkey=andsecret=. The secret is stored hashed on the firewall, so that download is the only copy — lose it and the pair has to be regenerated. - Present it as Basic auth. The key goes in the Basic-auth
username field and the secret in the password field, so the
wire format is
Authorization: Basic <base64(key:secret)>. With curl that is-u "$KEY:$SECRET"; with Pythonrequestsit isauth=(key, secret). The credentials are sent on every request, so TLS is mandatory.
The pair belongs to a user account and carries exactly that account’s group privileges. There is no separate per-key scope: to narrow what an automation can reach, create a dedicated user with narrow group membership and issue the key against it.
$ curl -sk -u "$KEY:$SECRET" https://localhost/api/core/firmware/status{
"status": "ok",
"product_version": "25.7.4",
"product_latest": "25.7.4",
"product_name": "OPNsense",
"package_name": "OPNsense",
"repository": "OPNsense",
"os_version": "14.2-RELEASE-p3",
"product_brand": "OPNsense",
"firmware_version": "25.7.4"
}Illustrative output
Authorisation: groups and privileges
Authorisation is by group membership. Each account has one or more groups; each group has a set of privileges; each privilege corresponds to a section of the API.
| Privilege | Section |
|---|---|
page-access.firewall.rule | Read and write firewall rules |
page-status-services | Read service status |
page-system-usermanager | Manage users and groups |
page-diagnostics-logs | Read logs |
page-vpn-wireguard | Manage WireGuard |
The GUI enforces the same model: a user without
page-access.firewall.rule cannot reach the firewall rules page,
and their API calls to the corresponding endpoints return
403 Forbidden.
The course’s management-plane-security lessons (Part VIII) cover the full privilege matrix; for now, the rule of thumb is:
- Automation accounts get an API key with the minimum privileges the script needs.
- Operator accounts get the privileges the operator needs to do their job — not more.
- The
rootaccount is for break-glass only, behind a strong password (and ideally MFA via a plugin), and never embedded in scripts.
The API endpoints an operator uses most
The API surface is large (every GUI page is an endpoint). The subset the course uses repeatedly:
$ curl -sk -u "$KEY:$SECRET" https://localhost/api/core/firmware/status | jq .product_version, .product_latest"25.7.4"
"25.7.4"Illustrative output
| Endpoint | Purpose |
|---|---|
GET /api/core/firmware/status | Current version, latest available |
GET /api/core/firmware/changelog/<version> | Release notes |
POST /api/core/firmware/upgrade | Trigger upgrade (after explicit confirmation) |
GET /api/core/system/status | CPU, memory, disk, uptime |
GET /api/diagnostics/firewall/pf_states | PF state table (text dump) |
GET /api/diagnostics/firewall/pf_rules | Loaded PF ruleset |
GET /api/diagnostics/firewall/log | Recent firewall log lines |
GET /api/interfaces/overview | Interface list and state |
GET /api/firewall/filter/searchRule | Search automation firewall rules |
POST /api/firewall/filter/addRule | Add an automation firewall rule |
POST /api/firewall/filter/toggleRule/{uuid}/{enabled} | Enable/disable a rule |
POST /api/firewall/filter/delRule/{uuid} | Delete a rule |
POST /api/firewall/filter/apply | Load the staged ruleset into pf |
GET /api/diagnostics/interface/stats | Per-interface counters |
GET /api/diagnostics/netstat | Netstat-style output |
POST /api/diagnostics/activity/download | Save a packet capture |
The firewall/filter controller drives the automation ruleset
(Firewall → Automation → Filter), which is a separate rule set
from the per-interface rules an operator edits by hand. Rules
written through the API are staged in config.xml and do not
reach pf until POST /api/firewall/filter/apply runs.
The course brings each endpoint up in context. The lesson on automation has a worked example of using the API to add a rule and verify it compiled into the running ruleset.
Security implications
The management surface — HTTPS, the REST API, and any SSH access the operator enables — is the most sensitive part of the firewall. Compromise of the management surface is compromise of the firewall. Three disciplines follow:
- Isolate the management network. The GUI/API should be reachable only from a dedicated management network or VLAN. The course’s lesson on management network design covers this in detail.
- Source-restrict at the firewall. OPNsense lets the operator configure which source IPs/networks can reach the GUI/API. The lesson on management-plane security (Part VIII) covers the rule set.
- Authenticate strongly. Strong passwords (or external
authentication — LDAP, RADIUS), MFA via plugin, certificate-
based API access where the automation supports it. The
rootaccount should not be the daily operator credential.
Production patterns
Three patterns make the API tractable in a production estate.
Configuration as code
The API is the supported writer. An operator with a configuration-as-code pipeline drives the firewall through the API rather than the XML directly. The pipeline’s git history is the audit trail; the API is the validation gate.
Read-only API keys for monitoring
A monitoring tool that scrapes interface counters, gateway state, and PF state table size does not need write access. The operator issues an API key with the minimum read privileges and scopes the key to the monitoring endpoints.
API-key rotation on a schedule
API keys are long-lived. The operator rotates them on a quarterly schedule (more often if compliance requires), and rotates immediately on personnel changes.
Summary
- nginx + PHP-FPM + OPNsense’s PHP MVC framework serve both the GUI and the API. The same authentication, authorisation, and configuration apply path serve both surfaces.
- API authentication is HTTP Basic, with the API key as the username and the API secret as the password. Authorisation is by the group privileges of the user the key belongs to.
- The API surface is comprehensive; the operator learns the endpoints that match the work they do, not the entire surface.
- The management surface is the most sensitive part of the firewall. Isolate it on a dedicated management network, source- restrict it, and authenticate strongly.
Knowledge check · 3 questions
Q1. You need to drive OPNsense from an Ansible playbook that adds firewall rules. Which credential is the most appropriate for the playbook to use?
Q2. An API POST that adds a firewall rule follows the same validation, XML write, and template resolver path as the GUI Apply button.
Q3. Which of the following statements about exposing the OPNsense management surface are correct? Select all that apply.
Passing score: 75%. Answers are checked in this browser.