Quickstart
This walkthrough takes a sysadmin from a running obserae to a working homelab setup — a network map, an allowed-traffic baseline, two detections, and your first queries — in about 10 minutes.
It assumes obserae is already installed (Installation)
and that at least one exporter is sending flows to it
(Configuring Exporters). Every step uses the web GUI by default, with
a CLI equivalent for the terminal (the CLI snippets assume the binary install
with the control socket at ./data/obserae.sock).
1. Make sure obserae is running
We pick up once obserae is installed and running
(Installation) and at least one exporter is sending
flows to it (Configuring Exporters). The GUI requires a login — the
generated admin password is printed once at first boot (reset it any time with
obserae-cli user reset-admin-password).
Open the GUI at http://localhost:8080 and sign in. The Cockpit landing page is your health check: the daemon’s version and uptime, and a flow counter that climbs as your exporters send data.
Prefer the terminal?
./obserae-cli --socket ./data/obserae.sock statusprints the same liveness and per-table counts. An alias saves typing —alias obserae-cli='./obserae-cli --socket ./data/obserae.sock'— and the CLI snippets later on assume it.
2. Describe everything in one config
obserae is driven by one consolidated YAML bundle — cartography, the flow matrix, alerting, outputs, retention and the rest all live in a single file. Importing it replaces each section it contains and leaves the others untouched, so the file is also a complete, version-controllable description of your setup.
Save this as config.yml. It models a classic homelab: a home network (DHCP
clients), a lab network (self-hosted servers), a group of known public DNS
resolvers, an allowed-traffic baseline, and two detections.
cartography:
networks:
- name: home
cidr: 192.168.0.0/24
description: Home / client devices (DHCP)
dhcp_start: 192.168.0.100
dhcp_end: 192.168.0.150
- name: lab
cidr: 192.168.10.0/24
description: Lab / self-hosted servers
hosts:
# Known public DNS resolvers — modelled on the built-in `internet4` network.
- name: dns-cloudflare
interfaces: [{ name: eth0, network: internet4, ip: 1.1.1.1 }]
services: [{ name: dns, protocol: UDP, port: 53, interfaces: [eth0] }]
- name: dns-google
interfaces: [{ name: eth0, network: internet4, ip: 8.8.8.8 }]
services: [{ name: dns, protocol: UDP, port: 53, interfaces: [eth0] }]
- name: dns-quad9
interfaces: [{ name: eth0, network: internet4, ip: 9.9.9.9 }]
services: [{ name: dns, protocol: UDP, port: 53, interfaces: [eth0] }]
# One lab server, so rules and queries have a concrete target.
- name: lab-web
interfaces:
- { name: eth0, network: lab, ip: 192.168.10.10 }
services:
- { name: http, protocol: TCP, port: 80, interfaces: [eth0] }
- { name: https, protocol: TCP, port: 443, interfaces: [eth0] }
- { name: ssh, protocol: TCP, port: 22, interfaces: [eth0] }
groups:
- name: public-dns
members: [dns-cloudflare, dns-google, dns-quad9]
flow_matrix:
rules:
# Egress — what internal hosts (home + lab) may do outbound.
# `internal4` matches all your private IPv4 space; here that is home + lab.
- { name: dns-to-known-resolvers, src: internal4, src_service: '*', dst: group:public-dns, dst_service: dns, protocol: UDP }
- { name: web-egress-http, src: internal4, src_service: '*', dst: internet4, dst_service: 80/tcp, protocol: TCP }
- { name: web-egress-https, src: internal4, src_service: '*', dst: internet4, dst_service: 443/tcp, protocol: TCP }
- { name: web-egress-quic, src: internal4, src_service: '*', dst: internet4, dst_service: 443/udp, protocol: UDP }
- { name: ntp-egress, src: internal4, src_service: '*', dst: internet4, dst_service: 123/udp, protocol: UDP }
# East-west — the only home -> lab traffic we expect.
- { name: home-to-lab-http, src: network:home, src_service: '*', dst: network:lab, dst_service: 80/tcp, protocol: TCP }
- { name: home-to-lab-https, src: network:home, src_service: '*', dst: network:lab, dst_service: 443/tcp, protocol: TCP }
alerting:
queries:
- name: ssh-home-to-lab
description: SSH initiated from home into the lab
query_text: |-
FROM sessions
| LAST 300
| WHERE protocol == TCP AND server_port == 22 AND server_ip == "network:lab" AND ip == "network:home"
| KEEP ip_a, ip_b, server_ip, server_port, opened_at
tags: [ssh, lateral]
- name: tcp-lab-to-home
description: TCP initiated from the lab back toward home
query_text: |-
FROM sessions
| LAST 300
| WHERE protocol == TCP AND server_ip == "network:home" AND ip == "network:lab"
| KEEP ip_a, ip_b, server_ip, server_port, opened_at
tags: [lateral]
rules:
- { name: ssh-home-to-lab, query: ssh-home-to-lab, condition_type: presence, severity: medium, interval_secs: 60, cooldown_secs: 1800 }
- { name: tcp-lab-to-home, query: tcp-lab-to-home, condition_type: presence, severity: high, interval_secs: 60, cooldown_secs: 1800 }
Apply it from the web GUI — the default path. Open ⇄ Config I/O
(/config-io), click Validate file… to check it, then Import file… to
apply it; the page reports which sections were applied. (You can also build the
same cartography, flow matrix and alerting interactively from their own pages and
Export them back to this single file.)
Prefer the terminal? The CLI works on the exact same bundle:
obserae-cli config validate config.yml # parse + validate, no changes obserae-cli config import config.yml # apply it obserae-cli config export # round-trip the live config to stdout
The full references are in Configuration, Cartography and Detection Rules; the Config I/O page is documented in Web GUI.
3. The flow matrix is your allowed baseline
A flow-matrix rule describes legitimate connectivity. obserae matches every session against the matrix; anything that matches is expected, and anything that doesn’t is the signal worth investigating. You didn’t list “lab may SSH into home” or “home may RDP the lab” — so the day that happens, it stands out.
Open the Flow Matrix page (sidebar → Flow Matrix) to see the rules you just imported, and to toggle or edit them. They say: internal hosts may resolve DNS only against the three known resolvers, reach the internet on HTTP/HTTPS/QUIC/NTP, and home may reach the lab’s web ports. Everything else is unaccounted-for. (Step 7 shows how to list it.)
Prefer the terminal?
obserae-cli rule lsprints the same matrix.
Service names vs. literal ports. A catalogued service name (
dns,https…) only works when the destination is a host or group that declares that service — likepublic-dnsabove. For anetwork:orinternet4destination there is no declared service to resolve, so pin the port directly:80/tcp,443/tcp,123/udp.
References: Detection Rules, and how ports/services and the internet4 /
internal4 keywords work in Detection Rules.
4. Two detections, on top of the baseline
The alerting section turns saved NFQL queries into alert rules. The two above
fire on lateral movement that a homelab should never see:
ssh-home-to-lab— an SSH session whose server side is inlaband whose other endpoint is inhome.tcp-lab-to-home— any TCP connection initiated from the lab back toward home (servers shouldn’t be reaching into client space).
Both use condition_type: presence — the rule fires whenever its query returns a
row — with a 30-minute cooldown so one noisy host doesn’t spam you. In the GUI,
the Rules page (sidebar → Rules) lists the alerting rules, and the
Detection page shows the alerts they raise.
Prefer the terminal?
obserae-cli alert lslists raised alerts.
(These patterns also surface in step 7, because they aren’t in the allowed
baseline — the matrix and the explicit detections reinforce each other.)
Reference: Alerting. Deliver alerts to a webhook or Gotify via
the outputs section — see Outputs.
5. Look at what’s happening
From the web GUI
Open http://localhost:8080. The Cockpit shows live counters; Cartography
draws your home/lab/DNS map with live link activity; and the Investigation
page runs any NFQL query interactively. See Web GUI.
…or from the CLI
# What's been ingested?
obserae-cli status
# Last 60 seconds of raw flows
obserae-cli query 'FROM flows | LAST 60 | LIMIT 10'
# Which internal hosts are talking to the internet right now?
obserae-cli query 'FROM sessions | LAST 3600 | WHERE ip == "internal4" AND server_ip == "internet4" | KEEP ip_a, ip_b, server_ip, server_port | LIMIT 20'
# What did the matcher confirm as allowed in the last 5 minutes?
obserae-cli matches ls --since 5m
The full query language reference is in NFQL, with copy-paste patterns in the cookbook.
6. Drill into a session
A “session” is the consolidation of all flows belonging to the same conversation (both directions, same protocol, same endpoints). One TCP connection produces one session row, regardless of how many NetFlow records described it. See Sessions for the model.
The Sessions page (sidebar → Sessions) is the fastest way to see who is
talking to whom: a filterable table of every session. Set the destination
chip to lab-web, then click a row — a drawer shows both directions, the inferred
server side and how it was inferred (tcp_handshake, cartography,
iana_port…), and the raw flows behind it.
Prefer a query? The same data via NFQL — on the Investigation page, or from the CLI:
obserae-cli query \ 'FROM sessions | LAST 3600 | WHERE state == "closed" AND server_ip == "host:lab-web" | KEEP ip_a, ip_b, server_ip, server_port, role_method, ab_bytes, ba_bytes | SORT ab_bytes DESC | LIMIT 20'
7. Find traffic that matched no rule
The payoff — the closed sessions that no flow-matrix rule accounted for, your “what shouldn’t be happening?” view.
On the Sessions page, turn on the Unmatched only filter: it lists exactly those sessions. This is where the SSH-into-lab and lab-to-home patterns from step 4 surface, even before an alert fires.
Prefer a query? The same set via NFQL — on the Investigation page, or from the CLI:
obserae-cli query \ 'FROM session_matches | LAST 3600 > FROM sessions | LAST 3600 | WHERE state == "closed" | PIVOT NOT session_id == session_id | KEEP ip_a, ip_b, server_ip, server_port, role_method'
Where to next
- Web GUI — tour of every screen in the browser interface.
- NFQL — the full query language, with recipes.
- Cartography — describe a real network.
- Detection Rules — write effective flow-matrix rules.
- Alerting — build detections from saved queries.
- Operations — run obserae as a systemd service.
Tear-down
If this was a throwaway trial, stop the daemon the way you started it
(Ctrl+C, systemctl stop obserae, or docker rm -f obserae) and remove its
data directory (./data for the binary, or the obserae-data volume for
Docker). obserae never writes outside its data directory, so cleanup is that
single step.