Flow Logs
Flow Logs record the network flows between Clients and Resources. Each flow captures the actor and Policy it was authorized under, when it started and ended, the network path it took, and the volume of data transferred in each direction. This makes Flow Logs the primary stream for incident response and forensic analysis, where the goal is to reconstruct network activity precisely.
Both sides of the tunnel report flows: the initiator is always the Client
that opened the connection, while the responder is the device that answered
for the Resource, either a Gateway or another Client for device pool
Resources. Each device batch-uploads its flow data to the control plane
through a dedicated ingest endpoint, but the authorization attribution,
meaning the actor, Policy, and Resource, comes from the control plane rather
than the device, so a device can't misattribute its own traffic.
How flow uploads are authenticated
explains how this works.
Flow logs are enabled by default. You can disable them on a per-Policy basis.
Flow log reporting to the admin portal is not supported for the Internet Resource. Emitting flow logs to STDOUT is the only supported way to capture flows for it.
Log IDs
Flow Log IDs start with f, and because the bits after the stream character
are random, they carry no ordering meaning. Order entries by flow_start
instead.
Flow lifecycle
A flow produces one entry that evolves over its lifetime: when the flow
opens, the entry records the network tuples and attribution but no traffic
counters yet, and when it closes, the same entry is updated in place with
flow_end, last_packet, and the final packet and byte counts.
When delivered through a Log Sink, each flow is rendered as
a start event and an end event sharing the flow's log_id: the start
event is suffixed with -s and carries no counters, while the end event is
suffixed with -e and carries the final totals. A flow that closes before
its open state was ever delivered still yields both events.
Two-sided reporting
Each flow is reported twice on purpose: the initiator and the responder track it independently, report it with the same tuples, and label directions from the initiator's perspective, so the two records describe the same traffic and can be joined on their network tuples and time window.
This redundancy is the integrity mechanism for flow statistics. While the
ingest token signs a flow's attribution, the packet and byte counters come
from the reporting device itself, so trust in them rests on the two-sided
cross-check rather than on any single report. If the initiator and responder
report meaningfully different statistics for the same tuples within the same
flow_start to flow_end window, treat that as a suspicious event worth a
deeper look: one side may be tampering with its reports, or something between
the two devices is interfering with traffic.
Fields
Authorization
| Field | Description |
|---|---|
log_id | Unique ID of the entry. |
role | initiator for the connecting Client, or responder for the Gateway or the answering Client for device pool Resources. |
actor_id, actor_email, actor_name | The actor the flow is attributed to. |
policy_id | The Policy that authorized the flow. |
policy_authorization_id | The specific authorization grant for this flow. |
resource_id, resource_name, resource_address | The Resource the flow was authorized for. |
auth_provider_id | The authentication provider used to sign in. |
authorized_at, authorization_expires_at | When the authorization was granted and when it expires. |
Device
| Field | Description |
|---|---|
device_id | The device that reported the flow. |
client_version | Version of the Firezone Client. |
device_os_name, device_os_version | Operating system of the device. |
device_serial, device_uuid | Hardware identifiers, where available. |
Network
| Field | Description |
|---|---|
protocol | tcp or udp. |
domain | The resolved domain, for DNS Resources. |
inner_src_ip, inner_src_port, inner_dst_ip, inner_dst_port | The tunneled connection's source and destination. |
outer_src_ip, outer_src_port, outer_dst_ip, outer_dst_port | The WireGuard connection's source and destination. |
The two 4-tuples describe the same flow at two different layers:
- The inner tuple describes the connection inside the tunnel, the
traffic as the application sees it: the source is the initiator's Firezone
tunnel address, and the destination is the address and port the Resource
was reached at. For DNS Resources,
domainalso records which FQDN the destination address was resolved from. - The outer tuple is the transport the flow is tunneled over, meaning the endpoints of the encrypted WireGuard packets on the underlying network. These are typically each side's public, NAT-translated address and port, or a Relay's address when a direct connection couldn't be established.
The outer tuple identifies the network path that carried the traffic, while the inner tuple identifies the endpoints that communicated over it.
Both sides record each tuple from the initiator's perspective, with the initiator side always the source. This means the initiator's and responder's records for the same flow carry matching tuples.
Timing and counters
| Field | Description |
|---|---|
flow_start, flow_end | When the flow started and ended. |
last_packet | When the last packet was seen. |
rx_packets, tx_packets | Packets received and transmitted. |
rx_bytes, tx_bytes | Bytes received and transmitted. |
Flow Logs carry no geolocation data because, unlike the other streams, there is no subject IP to geolocate: the network tuples identify the endpoints directly. If you need location information for a device, correlate with Session Logs, which record a session every time a Client or Gateway connects to the control plane, including the connection's source IP and its resolved location.
How flow uploads are authenticated
When a Client is authorized to access a Resource, the control plane mints a per-authorization ingest token and hands one to each side of the tunnel. The token is stamped at authorization time with the complete attribution snapshot: the reporting device and its role, the policy authorization, Policy, Resource, actor, authentication provider, and the authorization's validity window.
The token is the sole authenticator for flow uploads. When the portal ingests a batch, every attribution field is copied from the verified token's claims, and the device itself supplies only the network tuples, timing, and counters. This is what prevents spoofing: a device can only report flows for authorizations it actually holds, so it cannot attribute traffic to another device, another actor, or a Resource it was never authorized to access. The strongest claim a malicious device can make is a false statistic about its own authorized traffic, and that is exactly what two-sided reporting exists to catch.
Reporting and spooling
While connected to Firezone, devices spool flow records to disk locally and upload them in batches, and on disconnect or shutdown they perform a best-effort flush of whatever is pending. Flows that couldn't be uploaded, for example from a laptop that went offline, remain spooled and are uploaded on the next connection.
A spooled flow stays uploadable until its ingest token expires, 30 days past its authorization's expiry, after which it is cleaned up on the device automatically to prevent unbounded log growth.
Flow logs are spooled at the following locations:
| Platform | Spool location |
|---|---|
| Gateway | /var/lib/firezone/flow_logs |
| Linux Client, GUI and headless | /var/lib/dev.firezone.client/flow_logs |
| Windows Client, GUI and headless | C:\ProgramData\dev.firezone.client\flow_logs |
| macOS, iOS, and Android Clients | Managed inside the app's sandbox container. |
While the Gateway or Client is stopped, it's safe to delete the contents of the spool directory to reclaim space, though any flows not yet uploaded are lost and won't appear in your Flow Logs.
Emitting flow logs to STDOUT
Independent of the hosted feature, Gateways and headless Clients can print flow records directly to STDOUT. This is useful for feeding flows into your own log pipeline, and it's the only supported way to capture flows for the Internet Resource:
- Gateways: set
FIREZONE_FLOW_LOGS=trueor pass--flow-logs. The Gateway then tracks flows even when flow logs aren't enabled for your Policies, and emits each record to its log output. Flows tracked only because of this flag stay local, since uploading to Firezone is always controlled by the portal. - Headless Clients: flow records are emitted as structured log events
under the
flow_logstarget. Include them in the Client's output withRUST_LOG=info,flow_logs=trace.
Example
A closed flow reported by a Client:
{
"log_id": "f4e8a2c61b09d735e2a48b17",
"role": "initiator",
"actor_id": "7a1d9e3c-5b2f-4c8d-9e0f-1a2b3c4d5e6f",
"actor_name": "Riley Engineer",
"actor_email": "riley@company.com",
"policy_id": "0a83588e-0d92-4a4e-a856-4b8f2f4a2f8e",
"policy_authorization_id": "6c8e0a2b-4d6f-4a8c-9e0b-2d4f6a8c0e2d",
"resource_id": "5c8f6f6e-7c1a-4d8b-9c9e-1c2c3d4e5f60",
"resource_name": "GitLab",
"resource_address": "gitlab.company.com",
"auth_provider_id": "9d0c2f4e-8b6a-4c2d-8e1f-3a5b7c9d0e2f",
"authorized_at": "2026-07-14T09:20:00.000000Z",
"authorization_expires_at": "2026-07-14T19:20:00.000000Z",
"device_id": "2b4d6f8a-0c1e-4a3b-8d5f-7e9a1c3b5d7f",
"client_version": "1.4.0",
"device_os_name": "iOS",
"device_os_version": "17.4",
"protocol": "tcp",
"domain": "gitlab.company.com",
"inner_src_ip": "100.64.0.1",
"inner_src_port": 54321,
"inner_dst_ip": "10.0.0.5",
"inner_dst_port": 443,
"outer_src_ip": "203.0.113.10",
"outer_src_port": 51820,
"outer_dst_ip": "198.51.100.5",
"outer_dst_port": 51820,
"flow_start": "2026-07-14T09:25:31.000000Z",
"flow_end": "2026-07-14T09:26:01.000000Z",
"last_packet": "2026-07-14T09:26:01.000000Z",
"rx_packets": 100,
"tx_packets": 80,
"rx_bytes": 102400,
"tx_bytes": 20480
}
Viewing and retention
Flow Logs live under LogsFlow Logs in the admin
portal. You can also query them via the REST API's /logs endpoint with
type=flow. Entries are kept for 90 days.
Need help? See all support options.