Firezone Flow Logs | Firezone Docs

Flow Logs

Minimum versions: macOS 1.5.19, iOS 1.5.19, Android 1.5.13, Windows GUI 1.5.16, Linux GUI 1.5.16, Windows Headless 1.5.11, Linux Headless 1.5.11, and Gateway 1.6.0.

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 for all Policies except those targeting the Internet Resource. You must enable Flow Logs explicitly for those Policies. You can enable or disable Flow Logs on a per-Policy basis.

DNS queries are not tracked by Flow Logs. If DNS query logging would be useful for your organization, leave a comment on the tracking issue describing your use case.

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 inner network tuple and attribution, but outers and the traffic counters are null. When it closes, the same entry is updated in place with the complete outers array, 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 inner tuple, and label directions from the initiator's perspective, so the two records describe the same traffic and can be joined on their inner tuple and time window.

Both records also name both devices. Their role and counters differ, and their outers arrays may differ because NAT or a Relay can make the outer path appear different from each endpoint.

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 inner tuple 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 Which of the two devices reported this entry: initiator or responder.
initiator_actor_id, initiator_actor_email, initiator_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.
initiator_auth_provider_id The authentication provider used to sign in.
authorized_at, authorization_expires_at When the authorization was granted and when it expires.

The actor is always the initiating Client's actor. A Gateway has no actor of its own, and for device pool Resources the answering Client has an owner that is not recorded here.

Devices

Field Description
initiator_device_id The Client that opened the connection.
responder_device_id The device that answered: a Gateway, or another Client for device pool Resources.
initiator_client_version Version of the Firezone Client that opened the connection.
initiator_device_os_name, initiator_device_os_version Operating system of that Client.
initiator_device_serial, initiator_device_uuid Hardware identifiers of that Client, where available.

Both devices are named on both entries, so a single entry tells you the two ends of a flow without joining anything else. role tells you which of the two wrote the entry.

Every field starting with initiator_ describes the initiating Client, even on the responder's entry. The responder's own version and hardware details are not recorded.

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.
outers The WireGuard network paths, in the order they were observed.

The inner 4-tuple and the outers array describe the same flow at two different layers:

The outers array identifies the network paths that carried the traffic, while the inner tuple identifies the endpoints that communicated over them. Its items are ordered by when each path was observed, so a flow that changes paths can contain more than one item. When present, the array contains at least one path. outers is null while a flow is open and contains the complete path history when the flow closes.

Both sides orient the inner tuple and each outer path from the initiator's perspective, with the initiator side as the source. The inner tuples match, but the outers arrays can disagree when NAT or a Relay sits between the peers because each side observes the path from a different point.

Timing and counters

Field Description
flow_start, flow_end When the flow started and ended.
last_packet When the last packet was seen.
tx_packets, tx_bytes Traffic sent from the initiator to the responder.
rx_packets, rx_bytes Traffic sent from the responder back to the initiator.

Like the network tuples, the counters are labeled from the initiator's perspective on both entries. tx always means initiator to responder, no matter which side reported it.

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: both devices in the flow, which of the two this token reports as, 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:

Example

A closed flow reported by a Client:

{
  "log_id": "f4e8a2c61b09d735e2a48b17",
  "role": "initiator",
  "initiator_actor_id": "7a1d9e3c-5b2f-4c8d-9e0f-1a2b3c4d5e6f",
  "initiator_actor_name": "Riley Engineer",
  "initiator_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",
  "initiator_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",
  "initiator_device_id": "2b4d6f8a-0c1e-4a3b-8d5f-7e9a1c3b5d7f",
  "responder_device_id": "8f1c3e5a-6b2d-4e7c-9a0f-3c5e7a9b1d3f",
  "initiator_client_version": "1.4.0",
  "initiator_device_os_name": "iOS",
  "initiator_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,
  "outers": [
    {
      "src_ip": "203.0.113.10",
      "src_port": 51820,
      "dst_ip": "198.51.100.5",
      "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 Logs→Flow 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.