Skip to main content
RunBook Academy

OPNsenseIV · OPNsense ArchitectureOPNsense architecture

The web UI and REST API surface

Intermediate⏱ ~14 mincurljqopenssl

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

Not yet marked complete on this device.

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/.

Read-only / Safeservice status (3 daemons)
$ service nginx status; service php-fpm status; service configd status
nginx 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.

  1. 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 containing key= and secret=. The secret is stored hashed on the firewall, so that download is the only copy — lose it and the pair has to be regenerated.
  2. 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 Python requests it is auth=(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.

Read-only / Safecurl /api/core/firmware/status
$ 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.

PrivilegeSection
page-access.firewall.ruleRead and write firewall rules
page-status-servicesRead service status
page-system-usermanagerManage users and groups
page-diagnostics-logsRead logs
page-vpn-wireguardManage 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 root account 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:

Read-only / Safecurl with jq
$ curl -sk -u "$KEY:$SECRET" https://localhost/api/core/firmware/status | jq .product_version, .product_latest
"25.7.4"
"25.7.4"

Illustrative output

EndpointPurpose
GET /api/core/firmware/statusCurrent version, latest available
GET /api/core/firmware/changelog/<version>Release notes
POST /api/core/firmware/upgradeTrigger upgrade (after explicit confirmation)
GET /api/core/system/statusCPU, memory, disk, uptime
GET /api/diagnostics/firewall/pf_statesPF state table (text dump)
GET /api/diagnostics/firewall/pf_rulesLoaded PF ruleset
GET /api/diagnostics/firewall/logRecent firewall log lines
GET /api/interfaces/overviewInterface list and state
GET /api/firewall/filter/searchRuleSearch automation firewall rules
POST /api/firewall/filter/addRuleAdd 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/applyLoad the staged ruleset into pf
GET /api/diagnostics/interface/statsPer-interface counters
GET /api/diagnostics/netstatNetstat-style output
POST /api/diagnostics/activity/downloadSave 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:

  1. 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.
  2. 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.
  3. Authenticate strongly. Strong passwords (or external authentication — LDAP, RADIUS), MFA via plugin, certificate- based API access where the automation supports it. The root account 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

  1. 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?

  2. Q2. An API POST that adds a firewall rule follows the same validation, XML write, and template resolver path as the GUI Apply button.

  3. 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.