Zum Inhalt springen
labkompass

Guides · OPNsense

OPNsense-API nutzen: REST, API-Key, curl und Python

Die OPNsense hat eine vollständige REST-API — die Weboberfläche benutzt sie selbst. So ist sie aufgebaut, so richtest du den Zugang sicher ein, und so rufst du sie mit curl und Python auf.

Von Niclas Hennrich · Veröffentlicht: · 12 Min. Lesezeit

Getestet mit

  • OPNsense 26.1

Technisch verifiziert am


Kurz gesagt: Die OPNsense-API folgt dem Muster /api/<modul>/<controller>/<kommando>, spricht JSON und authentifiziert per HTTP Basic Auth mit einem Key-Secret-Paar aus einem Benutzerkonto. GET liest, POST schreibt oder löst aus. Der Schlüssel erbt die Rechte seines Benutzers — deshalb gehört jede Anwendung an einen eigenen, knapp berechtigten Benutzer und nicht an das Administrator-Konto. Welchen Endpunkt du brauchst, zeigt dir die Weboberfläche selbst: Sie ist nichts anderes als ein Client derselben API.

Die meisten Homelab-Automatisierungen scheitern nicht an fehlenden Funktionen, sondern an drei Fragen: Wie heißt der Endpunkt? Welche Rechte braucht er? Und warum antwortet er mit 403, obwohl der Key funktioniert? Dieser Guide beantwortet genau diese drei Fragen — mit Beispielen, die du auf jeder OPNsense nachvollziehen kannst.

Getestet wurde auf OPNsense 26.1. Ältere Versionen verhalten sich bei den Grundlagen gleich; Unterschiede benenne ich, wo sie relevant sind.

Was die OPNsense-API eigentlich ist #

Eine REST-API ist eine Schnittstelle, über die Programme mit einem System sprechen — statt Formulare auszufüllen, schicken sie HTTP-Anfragen und bekommen strukturierte Daten zurück. „REST" beschreibt dabei nur den Stil: Adressen für Ressourcen, die üblichen HTTP-Methoden, meist JSON als Format.

Bei OPNsense ist diese API keine Zusatzfunktion, sondern das Fundament. Die offizielle Dokumentation formuliert es so: Nahezu alle Endpunkte werden von der Weboberfläche selbst benutzt. Wenn du auf Speichern klickst, schickt dein Browser einen POST an einen /api/-Pfad — dieselbe Adresse, die du auch aus einem Skript ansprechen kannst.

Daraus folgt die wichtigste praktische Konsequenz dieses Guides: Du musst Endpunkte nicht raten. Die GUI führt sie dir vor.

Der Aufbau: Modul, Controller, Kommando #

Jeder Aufruf folgt demselben Muster:

https://<HOST>/api/<modul>/<controller>/<kommando>/[<param>/...]
TeilBedeutungBeispiel
ModulFunktionsbereich, oft ein Plugincore, firewall, diagnostics, auth
ControllerObjekt innerhalb des Modulsfirmware, alias, traffic, user
Kommandodie Aktionstatus, searchItem, interface, add
Parameteroptionale Pfadangaben, z. B. eine UUID.../setItem/<uuid>

Es gibt nur zwei Methoden:

  • GET — Daten lesen.
  • POST — anlegen, ändern oder eine Aktion ausführen.

Ein PUT oder DELETE wie bei anderen APIs existiert nicht; Löschen ist ein POST auf ein Kommando, das del… heißt. Request- und Response-Body sind JSON.

Eine Stolperfalle bei der Schreibweise: In der API-Referenz stehen manche Kommandos mit Unterstrich (system_resources), während die Weboberfläche denselben Endpunkt in camelCase aufruft (systemResources). Wenn ein Aufruf unerwartet ins Leere läuft, ist die Schreibweise ein guter erster Verdacht — und der zuverlässigste Weg zur richtigen Form ist der Blick in die Entwicklerwerkzeuge (siehe unten).

Zugang einrichten: eigener Benutzer, eigener Schlüssel #

Die Authentifizierung ist bewusst einfach gehalten: HTTP Basic Auth. Das heißt, jeder Request trägt ein Paar aus Benutzername und Passwort mit sich — hier: API-Key an der Stelle des Benutzernamens, API-Secret an der des Passworts. Es gibt keinen Login-Endpunkt, keine Session und kein ablaufendes Token.

Erzeugt wird das Paar im Benutzerkonto unter System → Access → Users → API keys. OPNsense lädt dabei eine Datei mit key und secret herunter. Das Secret siehst du genau einmal — auf der Firewall liegt danach nur noch ein Hash davon.

Und damit zur wichtigsten Entscheidung dieses Guides:

Ein API-Key hat exakt die Rechte des Benutzers, an dem er hängt.

Ein Schlüssel am Administrator-Konto darf also alles: Regeln ändern, VPN-Zugänge anlegen, die Konfiguration exportieren. Genau diesen Schlüssel legen die meisten Anleitungen in eine .env-Datei auf irgendeinem Container-Host.

Der saubere Weg ist Least Privilege — jede Anwendung bekommt genau die Rechte, die sie braucht:

Anwendung
   ↓  Was ruft sie wirklich auf?
benötigte Endpunkte bestimmen
   ↓  Welche GUI-Seiten gehören dazu?
nur diese Effective Privileges vergeben

eigener Benutzer  →  eigener API-Key

OPNsense kennt dafür keinen globalen „read-only"-Schalter, sondern vergibt Rechte pro GUI-Seite (Effective Privileges im Benutzerkonto). Ein Benutzer, der nur Diagnose- und Statusseiten zugewiesen bekommt, kann folglich nur lesen. Die internen IDs dieser Seiten — etwa page-diagnostics-pf-info — liefert die Firewall selbst:

curl -s -u "$OPNSENSE_API_KEY:$OPNSENSE_API_SECRET" \
  https://firewall.example.lan/api/auth/priv/search

Wie das für ein Monitoring konkret aussieht — inklusive der Privilegien-Liste und der Besonderheit, dass es für das Erzeugen eines Keys keinen eigenen Endpunkt gibt —, steht im eigenen Guide: read-only API-User fürs Monitoring.

Key und Secret behandeln wie ein Passwort #

Denn genau das sind sie. Ein paar Regeln, die im Homelab genauso gelten wie in der Firmen-IT:

  • In Umgebungsvariablen oder eine .env-Datei, nicht in den Quelltext.
  • Niemals in Git — auch nicht „kurz zum Testen", denn ein Commit bleibt in der History, selbst wenn die Datei später gelöscht wird.
  • Niemals im Frontend, also nicht in JavaScript, das im Browser läuft. Alles, was der Browser lädt, kann der Benutzer lesen.
  • Nicht als URL-Parameter anhängen — URLs landen in Logs, Proxys und im Browserverlauf.
  • Nicht in Screenshots, Pastebins oder Foren-Posts. Auch teilweise geschwärzt nicht.
  • Ein Schlüssel je Anwendung. Dann kannst du einen einzelnen zurückziehen, ohne alles andere lahmzulegen.
  • Rotieren, wenn ein Schlüssel irgendwo aufgetaucht ist, wo er nicht hingehört. Ein Key, der einmal öffentlich war, gilt als kompromittiert — auch ohne Hinweis auf Missbrauch.

In allen Beispielen hier stehen deshalb nur Platzhalter: $OPNSENSE_API_KEY, $OPNSENSE_API_SECRET und der Beispielhost firewall.example.lan.

Der erste Aufruf mit curl #

Zugangsdaten in die Shell laden (oder besser: in eine Datei, die nicht im Verlauf landet), dann den harmlosesten Endpunkt aufrufen — den Systemstatus:

export OPNSENSE_API_KEY='...'
export OPNSENSE_API_SECRET='...'
 
curl -s -u "$OPNSENSE_API_KEY:$OPNSENSE_API_SECRET" \
  https://firewall.example.lan/api/core/system/status

Kommt JSON zurück, funktioniert der Zugang. Zwei Details dazu:

-u statt eines Authorization-Headers. curl baut den Header selbst. Wer ihn von Hand setzt, hat den Base64-kodierten Schlüssel im Shell-Verlauf stehen — unnötig.

Zertifikatsfehler. Eine frisch installierte OPNsense benutzt ein selbstsigniertes Zertifikat; curl bricht dann mit einer TLS-Warnung ab. Die schnelle Lösung ist -k (Prüfung aus), die richtige ist, das Zertifikat der Firewall auf dem aufrufenden Host als vertrauenswürdig zu hinterlegen:

curl -s --cacert /pfad/zum/opnsense-ca.pem \
  -u "$OPNSENSE_API_KEY:$OPNSENSE_API_SECRET" \
  https://firewall.example.lan/api/core/system/status

-k im eigenen LAN für einen Test ist vertretbar. Dauerhaft in einem Skript ist es die Einladung für einen Man-in-the-Middle-Angriff: Der Aufruf schickt gültige Zugangsdaten an jeden, der sich als Firewall ausgibt.

Die Antworten verstehen #

Drei Antwortformen begegnen dir immer wieder.

Statusdaten — eine flache Struktur mit Werten:

{
  "product_version": "26.1",
  "status": "OK"
}

Listen kommen aus search…-Kommandos und sind für Tabellen mit Blätterfunktion gebaut:

{
  "total": 42,
  "rowCount": 7,
  "current": 1,
  "rows": [
    { "uuid": "…", "name": "BLOCKLIST", "enabled": "1" }
  ]
}

total ist die Gesamtzahl, rows die aktuelle Seite. Wer alle Einträge braucht, muss die Parameter current und rowCount mitschicken — sonst bekommt er nur die erste Seite und merkt es nicht.

Ergebnisse von Aktionen melden sich mit einem Status:

{ "status": "ok" }

Bei einem Fehler stehen dort stattdessen status_msg oder ein validations-Objekt mit den beanstandeten Feldern. Wichtig: Ein HTTP-200 bedeutet bei OPNsense nur, dass der Request angekommen ist — ob die fachliche Änderung geklappt hat, steht im Body. Ein Skript, das nur den HTTP-Code prüft, übersieht abgelehnte Eingaben.

Was die HTTP-Codes bedeuten #

CodeBedeutung in der Praxis
200Request angekommen — Ergebnis trotzdem im Body prüfen
401Key oder Secret falsch
403Key gültig, aber dem Benutzer fehlt das Privileg für diese Seite
404Dieses Kommando gibt es in dieser Version nicht (oder Tippfehler)

Eine Einschränkung dazu: Diese saubere Trennung gilt für aktuelle Versionen. Ältere OPNsense-Versionen meldeten auch fehlende Rechte als 401 mit der Meldung Authentication Failed — was zu der naheliegenden, aber falschen Diagnose „Schlüssel kaputt" führte. Wenn du auf einer älteren Installation ein 401 bekommst, obwohl derselbe Schlüssel anderswo funktioniert, ist es mit hoher Wahrscheinlichkeit trotzdem ein Rechteproblem.

Die Unterscheidung zwischen 403 und 404 spart viel Zeit. 403 heißt: richtiger Pfad, fehlendes Recht — im Benutzerkonto das passende Privileg ergänzen. 404 heißt: Der Pfad existiert nicht. Das passiert häufiger, als man denkt, weil Endpunkte zwischen Versionen umziehen oder ein Kommando, das in einem Blogpost von 2022 stand, es nie in die aktuelle Version geschafft hat.

Die API mit Python ansprechen #

Für alles, was über einen Einzelaufruf hinausgeht, ist ein Skript angenehmer. Das folgende Beispiel holt die Interface-Zähler und rechnet sie in Megabyte um — bewusst klein gehalten, aber mit den Dingen, die in echten Skripten fehlen: Zugangsdaten aus der Umgebung, ein Timeout und eine ehrliche Fehlerbehandlung.

import os
import sys
 
import requests
 
HOST = os.environ.get("OPNSENSE_HOST", "firewall.example.lan")
KEY = os.environ["OPNSENSE_API_KEY"]
SECRET = os.environ["OPNSENSE_API_SECRET"]
 
# Pfad zum CA-Zertifikat der Firewall; True = normale Prüfung.
VERIFY = os.environ.get("OPNSENSE_CA_BUNDLE", True)
 
 
def api_get(pfad: str) -> dict:
    """Ein GET auf die OPNsense-API — gibt die JSON-Antwort zurück."""
    antwort = requests.get(
        f"https://{HOST}/api/{pfad}",
        auth=(KEY, SECRET),
        verify=VERIFY,
        timeout=10,
    )
    if antwort.status_code == 403:
        sys.exit(f"403 — dem API-Benutzer fehlt das Privileg für {pfad}")
    antwort.raise_for_status()
    return antwort.json()
 
 
daten = api_get("diagnostics/traffic/interface")
 
for eintrag in daten.get("interfaces", {}).values():
    name = eintrag.get("name")
    empfangen = int(eintrag["bytes received"]) / 1024**2
    gesendet = int(eintrag["bytes transmitted"]) / 1024**2
    print(f"{name:<6}{empfangen:10.1f} MB   ↑ {gesendet:10.1f} MB")

Aufgerufen wird es mit den Zugangsdaten aus der Umgebung — sie stehen an keiner Stelle im Skript:

OPNSENSE_API_KEY='...' OPNSENSE_API_SECRET='...' python3 traffic.py

Zwei Anmerkungen zum Beispiel:

diagnostics/traffic/interface ist derselbe Endpunkt, den das Dashboard der OPNsense für seine Live-Anzeige benutzt. Er ist leichtgewichtig und liefert alle Interfaces in einer Antwort — mit Feldern wie bytes received, bytes transmitted, link state und line rate. Die Feldnamen enthalten Leerzeichen; deshalb der Zugriff über eintrag["bytes received"] statt über einen Punkt.

Die Zähler sind kumulativ, sie laufen seit dem letzten Reboot hoch. Eine Datenrate bekommst du erst, wenn du zweimal misst und die Differenz durch die verstrichene Zeit teilst — ein einzelner Abruf sagt nichts über die aktuelle Auslastung.

Endpunkte selbst finden: die Weboberfläche als Referenz #

Der offiziell empfohlene Weg, und der schnellste:

OPNsense-GUI im Browser öffnen

Entwicklerwerkzeuge (F12) → Tab „Netzwerk"

Filter auf  /api/  setzen

die gewünschte Aktion in der GUI ausführen

Request anklicken: Methode, Pfad, Payload, Antwort

Ein Rechtsklick auf den Request bietet in allen gängigen Browsern Als cURL kopieren an. Damit hast du den funktionierenden Aufruf inklusive JSON-Payload in der Zwischenablage — der Rest ist Aufräumarbeit.

Und hier eine Warnung, die selten irgendwo steht: Der kopierte Befehl enthält deine Browser-Session — Cookies und CSRF-Header deines eingeloggten Administrator-Zugangs. Ein solcher Befehl gehört niemals unverändert in ein Skript, ein Ticket, ein Forum oder einen Blogpost. Für die Automatisierung ersetzt du Cookies und Header durch -u "$OPNSENSE_API_KEY:$OPNSENSE_API_SECRET". Wer den Befehl weitergibt, gibt sonst seinen Firewall-Zugang mit.

Ergänzend gibt es die offizielle API-Referenz, die alle Core-Module und die Plugins auflistet. Sie beantwortet „welche Kommandos gibt es?"; die DevTools beantworten „wie sieht ein gültiger Aufruf konkret aus?".

Welche Bereiche es gibt — und wie riskant sie sind #

Nicht jeder Endpunkt ist gleich harmlos. Sinnvoll ist eine Einteilung in vier Stufen, bevor man etwas automatisiert:

StufeWas passiertBeispiele
Lesennichts ändert sichcore/system/status, diagnostics/traffic/interface, core/firmware/status
SchreibenKonfiguration ändert sich, wird aber noch nicht aktivfirewall/alias/addItem, firewall/alias/setItem
AnwendenÄnderung wird scharf geschaltet oder ein Dienst neu gestartetfirewall/alias/reconfigure, firewall/filter/apply
Sicherheitskritischgreift in Zugang, Netzwerk oder Vertrauen einalles unter auth/, Regeln, NAT, Routing, VPN, Zertifikate

Die wichtigsten Bereiche im Überblick — welche davon deine Installation tatsächlich hat, hängt von den installierten Plugins ab:

ModulWofürtypische Stufe
core/firmwareVersion, Updates, PluginsLesen (Update = Anwenden)
core/systemSystemstatus, NeustartLesen bis Anwenden
diagnostics/*Traffic, Interfaces, ARP, Ressourcen, LogsLesen
firewall/aliasAliase, z. B. IP-ListenSchreiben + Anwenden
firewall/filterRegeln des neuen Regelwerkssicherheitskritisch
auth/*Benutzer, Gruppen, Privilegiensicherheitskritisch
unbound/*DNS-Resolver, Overrides, BlocklistenSchreiben + Anwenden
wireguard/*Tunnel und Peerssicherheitskritisch

Für den Einstieg gilt: erst lesen, dann schreiben. Wer einen search…-Aufruf gebaut hat und die Antwort versteht, kennt danach auch die Feldnamen, die das zugehörige add… erwartet.

Die Sache mit der Firewall-API #

Hier lohnt sich ein genauer Blick, weil die Erwartung fast immer falsch ist:

Achtung: Die Firewall-API kennt nur die Regeln des neuen Regelwerks. Klassisch angelegte Regeln liefert sie nicht zurück — und ändert sie auch nicht.

OPNsense führt seit einigen Versionen zwei Regel-Implementierungen parallel:

  • Firewall → Rules [new] (in älteren Versionen: Firewall → Automation → Filter) — die neue Umsetzung mit API.
  • Firewall → Rules — die klassische Seite, ohne API.

Die offizielle Dokumentation ist an dieser Stelle unmissverständlich: Regeln, die außerhalb der neuen Seiten angelegt wurden, tauchen im API-Ergebnis nicht auf. Wer also firewall/filter/searchRule aufruft und eine leere Liste zurückbekommt, obwohl die Firewall voller Regeln steckt, hat keinen Fehler gemacht — die Regeln liegen schlicht im anderen Regelwerk.

Beide Regelwerke gelten gleichzeitig, werden aber in einer festen Reihenfolge ausgewertet: Zuerst Floating-Regeln beider Implementierungen, dann Gruppen-Regeln, dann die Interface-Regeln — die des neuen Regelwerks vor den klassischen. Wer Regeln per API ergänzt, sollte diese Reihenfolge kennen, sonst greift die neue Regel früher als erwartet.

Die Richtung ist klar: Das neue Regelwerk soll das klassische mit der Zeit ablösen, und OPNsense bringt dafür einen Migrationsassistenten mit. Bis dahin gilt für Automatisierung: prüfen, in welchem Regelwerk die Regel liegt, bevor man ein Skript darauf ansetzt.

Bevor du schreibend automatisierst #

Lesende Aufrufe kannst du bedenkenlos ausprobieren. Bei allem, was ändert, haben sich vier Gewohnheiten bewährt:

  1. Read before write. Erst den passenden get…- oder search…-Aufruf machen und die Antwort ansehen. Sie zeigt die exakten Feldnamen und die UUID des Objekts, das du ändern willst.
  2. Konfiguration sichern. System → Configuration → Backups erzeugt in Sekunden eine XML-Sicherung des gesamten Regelwerks. Sie ist die einzige verlässliche Rückfahrkarte, wenn ein Skript mehr ändert als geplant.
  3. Rechte bewusst vergeben. Ein Skript, das nur Aliase pflegt, braucht kein Recht auf auth/. Und wenn ein Aufruf mit 403 scheitert, ist die Antwort nicht „mehr Rechte", sondern „welches Recht genau".
  4. Nicht auf der produktiven Firewall lernen. OPNsense läuft problemlos in einer VM — dort kostet ein misslungener apply-Aufruf nichts außer Zeit. Auf dem Router, an dem das ganze Haus hängt, sperrst du dich im ungünstigsten Fall selbst aus.

Der letzte Punkt ist kein theoretisches Risiko: Regeln, NAT und Interfaces sind genau die Bereiche, über die dein eigener Zugang läuft. Ein firewall/filter/apply mit einer fehlerhaften Regel kann die Weboberfläche und SSH gleichzeitig unerreichbar machen — dann hilft nur noch der physische Konsolenzugang.

Was beim Ausprobieren hängen bleibt #

Drei Dinge, die sich beim Arbeiten mit der API auf 26.1 als praktisch erwiesen haben:

403 ist eine Rechteliste, keine Fehlermeldung. Wenn ein Werkzeug mehrere Endpunkte abfragt, ist die Menge der 403-Antworten die exakte Liste der Privilegien, die noch fehlen. Statt breite Rechte zu vergeben und zurückzubauen, lässt man das Werkzeug einmal laufen und ergänzt genau die Seiten, die es angemahnt hat.

Fehlende Werte sind nicht immer fehlende Rechte. Manche Endpunkte antworten sauber, liefern aber undefined oder leere Felder — weil die Datenquelle auf der Firewall nie aktiviert wurde. Firmware-Informationen bleiben leer, solange nie ein Update-Check lief; Gateway-Latenzen entstehen erst mit aktivem Gateway-Monitoring. Ein Skript sollte solche Platzhalter als „kein Wert" behandeln und nicht als Zahl weiterreichen.

Nicht alles, was die GUI kann, hat ein API-Kommando. Für einzelne Aktionen existiert schlicht kein Endpunkt — das Erzeugen eines API-Keys ist das prominenteste Beispiel und antwortet mit 404, egal wie plausibel der geratene Pfad klingt. In solchen Fällen führt der Weg über das Datenmodell des Objekts selbst, nicht über ein Spezialkommando.

Wie es weitergeht #

Wenn du die API fürs Monitoring nutzen willst, ist der nächste Schritt der read-only API-User — dort steht, welche Privilegien ein Exporter braucht und wie der Schlüssel dort hinkommt, ohne dem Monitoring die ganze Firewall zu geben. Wer die Firewall überhaupt erst aufsetzt, findet die Grundlagen in der OPNsense-Grundkonfiguration; passendes Monitoring drumherum beschreibt der Guide zu Uptime Kuma und Beszel.

Und die vielleicht wichtigste Empfehlung zum Schluss: Fang mit GET an. Die lesenden Endpunkte sind harmlos, gut dokumentiert und zeigen dir in einer halben Stunde mehr über deine Firewall, als jede Übersichtsseite es könnte.

Häufige Fragen

Hat OPNsense eine REST-API?
Ja, und zwar eine vollständige. Die Weboberfläche ist selbst nur ein Client dieser API — fast jede Seite der GUI ruft im Hintergrund Endpunkte unter /api/ auf. Alles, was du im Browser klicken kannst, lässt sich deshalb im Regelfall auch per HTTP-Request erledigen.
Wie ist die OPNsense-API aufgebaut?
Nach dem Muster /api/<modul>/<controller>/<kommando>, optional gefolgt von Parametern im Pfad. GET holt Daten, POST legt an, ändert oder löst eine Aktion aus. Request- und Response-Body sind JSON. Ein Beispiel: /api/core/firmware/status fragt den Update-Status ab.
Wie authentifiziert man sich an der OPNsense-API?
Über HTTP Basic Auth mit einem API-Schlüsselpaar: Der Key steht an der Stelle des Benutzernamens, das Secret an der des Passworts. Beides erzeugst du im Benutzerkonto unter System → Access → Users. Es gibt keine Session, keinen Token-Endpunkt und keinen OAuth-Flow — jeder Request trägt die Zugangsdaten selbst.
Warum antwortet die OPNsense-API mit 403?
Weil dem Benutzer hinter dem API-Key das Privileg für die zugehörige GUI-Seite fehlt. Ein API-Key erbt genau die Effective Privileges seines Benutzers. 403 ist damit kein Fehler im Aufruf, sondern eine Rechtefrage — im Gegensatz zu 404, das bedeutet, dass es dieses Kommando in dieser Version gar nicht gibt.
Kann ich über die API meine bestehenden Firewall-Regeln bearbeiten?
Nur, wenn sie in der neuen Regel-Implementierung liegen. OPNsense hat zwei Regelwerke: Firewall → Rules [new] (früher Firewall → Automation) mit API, und die klassische Regelseite ohne API. Die Firewall-API sieht ausschließlich das neue Regelwerk — klassisch angelegte Regeln liefert sie nicht zurück und ändert sie auch nicht. Für den Wechsel gibt es in der GUI einen Migrationsassistenten.
Wie finde ich heraus, welchen Endpunkt ich brauche?
Über die Entwicklerwerkzeuge des Browsers: Netzwerk-Tab öffnen, auf /api/ filtern und die Aktion in der GUI ausführen. Der Request, der dabei erscheint, ist genau der Aufruf, den du nachbauen willst — inklusive Methode und JSON-Body. Das ist auch der offiziell empfohlene Weg, weil die GUI dieselbe API benutzt.

Aus dem echten Betrieb

Diese Seite ist aus realer Arbeit im Homelab entstanden:

Weiterlesen