Deployment guide

GoFlow2

Overview

GoFlow2 is an open source network flow collector written in Go. Routers, switches, firewalls and hypervisors export a summary of every conversation that crosses them, but each speaks a different dialect: NetFlow v5, NetFlow v9, IPFIX or sFlow v5. GoFlow2 listens for all of them at once, decodes the templates and samples, and emits every record in one common schema, so everything downstream sees the same fields whichever vendor sent them.

Records come out as newline-delimited JSON or protocol buffers. GoFlow2 writes them to a file for a log shipper to collect, or publishes them straight onto a Kafka topic.

GoFlow2 is deliberately the ingest stage and nothing more. It ships with no web interface and no authentication of its own. The SecureAnyCloud image supplies the operational layer around it: the pinned GoFlow2 2.2.6 binary runs under systemd as a dedicated unprivileged goflow2 user, and its HTTP server is bound to 127.0.0.1:8080 so the Prometheus metrics are never exposed directly. An nginx reverse proxy on port 80 puts a per-VM HTTP Basic Auth gate in front of them.

Decoded flows are written to a dedicated Azure data disk mounted at /var/lib/goflow2, because flow volume filling a root filesystem is the classic way a collector fails.

What you get:

  • GoFlow2 2.2.6, installed as a single pinned Go binary and running as the goflow2 systemd service
  • NetFlow v5, NetFlow v9 and IPFIX collection on 2055/udp, and sFlow v5 collection on 6343/udp
  • Decoded flows written as newline-delimited JSON to /var/lib/goflow2/flows.json
  • A dedicated Azure data disk at /var/lib/goflow2 so flow volume never fills the OS disk
  • Per-VM HTTP Basic Auth (user admin) protecting the Prometheus metrics, with a unique password generated on first boot
  • GoFlow2's HTTP server bound to 127.0.0.1:8080 only, with nginx on port 80 as the sole gate
  • An unauthenticated /healthz endpoint for Azure Load Balancer health probes
  • goflow2-send-test-flow, a bundled synthetic NetFlow v5 generator that proves the collector decodes flows
  • logrotate keeping 14 compressed generations and rotating with a SIGHUP reopen, so no record is lost
  • Kernel UDP receive buffers tuned for bursty exporter traffic
  • goflow2.service and nginx.service as enabled and active systemd units
  • 24/7 SecureAnyCloud support

Prerequisites

You need an active Azure subscription, an SSH key pair, and a VNet with a subnet in the target region.

Standard_B2s (2 vCPU / 4 GiB RAM) is a comfortable starting size for tens of thousands of flows per second. Scale up the VM size and the data disk if you collect from a large estate.

Configure the network security group (NSG) to allow inbound 22/tcp from your management network, 80/tcp for the metrics endpoint, and 2055/udp and 6343/udp from your flow exporters.

NetFlow, IPFIX and sFlow carry no credentials by protocol design. Restrict the two UDP rules to your own exporter address ranges. This is the correct control, not an optional hardening step.

Step 1 - Deploy from the Azure Marketplace

Sign in to the Azure Portal, choose Create a resource, search the Marketplace for GoFlow2 by SecureAnyCloud, and select Create.

On the Basics tab, pick your subscription, resource group, region and size. Under Administrator account, choose SSH public key and paste your key. Under Inbound port rules, allow SSH (22) and HTTP (80).

Review the dedicated data disk on the Disks tab, then select Review + create and Create. You add the UDP collector ports in Step 7.

Step 2 - Deploy from the Azure CLI

# Accept the Marketplace terms once per subscription (required before the first CLI deploy)
az vm image terms accept --urn sac1647283583153:goflow2-ubuntu-24-04:default:latest
az vm create \
  --resource-group <your-rg> \
  --name goflow2 \
  --image sac1647283583153:goflow2-ubuntu-24-04:default:latest \
  --size Standard_B2s \
  --admin-username azureuser \
  --ssh-key-values ~/.ssh/id_ed25519.pub \
  --vnet-name <your-vnet> --subnet <your-subnet> \
  --public-ip-sku Standard
az vm open-port --resource-group <your-rg> --name goflow2 --port 80 --priority 1010

Step 3 - Connect to your VM

ssh azureuser@<vm-public-ip>

Step 4 - Confirm the collector is running

On first boot, the VM generates its metrics password, starts nginx and runs a self test. This takes a few seconds after the VM reports ready. Confirm that both services are active, check the GoFlow2 version, and look at the three sockets the appliance opens.

systemctl is-active goflow2.service nginx.service

Both lines report active.

/usr/local/bin/goflow2 -v
GoFlow2 v2.2.6 (2025-12-27T05:02:22+0000)

Now check the sockets. The two UDP collectors listen on all interfaces so your exporters can reach them. The HTTP server that serves the metrics is bound to loopback only.

sudo ss -ulnp | grep -E ':(2055|6343) '
sudo ss -tlnp | grep ':8080 '
UNCONN 0 0 *:2055 *:* users:(("goflow2",pid=3089,fd=7)) UNCONN 0 0 *:6343 *:* users:(("goflow2",pid=3089,fd=8)) LISTEN 0 4096 127.0.0.1:8080 0.0.0.0:* users:(("goflow2",pid=3089,fd=6))

Step 5 - Retrieve this VM's metrics password

GoFlow2 has no accounts of its own, so nginx HTTP Basic Auth gates its metrics. The user is admin, and the password is generated uniquely on this VM's first boot. It is never baked into the image.

sudo cat /root/goflow2-credentials.txt

The file records the metrics URL, the username, the password and the addresses your exporters should send to. Store the password in your password manager. The VM keeps only the bcrypt hash, in /etc/nginx/.goflow2.htpasswd.

Step 6 - Prove the collector decodes flows

Confirm that the collector decodes flows before you point production hardware at it. The image ships goflow2-send-test-flow. It builds a well-formed NetFlow v5 datagram using RFC 5737 documentation addresses and sends it to the collector. Every run uses a fresh random source port, so you can pick your own test record out of the file.

sudo /usr/local/bin/goflow2-send-test-flow
Sent 1 synthetic NetFlow v5 flow to 127.0.0.1:2055 198.51.100.7:30909 -> 203.0.113.9:443 proto=TCP packets=33 bytes=42768 marker (unique source port): 30909 look for it with:
sudo grep 30909 /var/lib/goflow2/flows.json

Read the decoded record back from the flow file. Every conversation GoFlow2 decodes becomes one JSON object on one line.

sleep 3
sudo tail -1 /var/lib/goflow2/flows.json | python3 -m json.tool | head -20
{ "type": "NETFLOW_V5", "time_received_ns": 1785142429141646182, "sequence_num": 1, "sampling_rate": 0, "sampler_address": "127.0.0.1", "time_flow_start_ns": 1785142428000000000, "time_flow_end_ns": 1785142429000000000, "bytes": 42768, "packets": 33, "src_addr": "198.51.100.7", "dst_addr": "203.0.113.9", "etype": "IPv4", "proto": "TCP", "src_port": 30909, "dst_port": 443, "in_if": 1, "out_if": 2, "src_as": 64500, "dst_as": 64501,

The record carries the five-tuple, the byte and packet counts, the interface indexes, the autonomous system numbers and the derived source and destination networks. A NetFlow v9, IPFIX or sFlow sample from a real router produces a record with the same field names, which is the purpose of the collector.

Step 7 - Point your exporters at the collector

Open the two collector ports to your exporters only. Replace the source prefix with the address range that your routers, switches or hypervisors send from.

az network nsg rule create --resource-group <your-rg> --nsg-name <your-nsg> \
  --name allow-netflow --priority 1020 --protocol Udp --destination-port-ranges 2055 \
  --source-address-prefixes <your-exporter-range> --access Allow
az network nsg rule create --resource-group <your-rg> --nsg-name <your-nsg> \
  --name allow-sflow --priority 1021 --protocol Udp --destination-port-ranges 6343 \
  --source-address-prefixes <your-exporter-range> --access Allow

Then configure the exporter itself. On Cisco IOS, the following exports NetFlow v9 to this VM:

flow exporter SAC-GOFLOW2 destination <vm-private-ip> transport udp 2055 export-protocol netflow-v9

On a Juniper device, or any sFlow-capable switch, point the sFlow collector at <vm-private-ip> port 6343. On Linux hosts, softflowd -i eth0 -n <vm-private-ip>:2055 exports NetFlow from a live interface.

Use the private IP when the exporter is inside the same VNet. Use the public IP only if you must, and keep the NSG source range tight.

NetFlow v9 and IPFIX are template-based, so the first samples from a new exporter cannot be decoded until its option and data templates arrive. This typically takes a minute or two. To see which exporters have sent templates, run:

PW=$(sudo grep '^GOFLOW2_PASSWORD=' /root/goflow2-credentials.txt | cut -d= -f2-)
curl -s -u "admin:$PW" http://127.0.0.1/templates

When a real exporter is sending, watch the records arrive:

sudo tail -f /var/lib/goflow2/flows.json

Press Ctrl+C to stop following.

Step 8 - Scrape the metrics from Prometheus

GoFlow2 exposes counters for packets received, samples decoded, decoding errors and drops. Those counters tell you whether the collector is keeping up.

The metrics endpoint is behind Basic Auth, and an unauthenticated request is rejected. /healthz is deliberately left open so an Azure Load Balancer can probe it.

curl -s -o /dev/null -w 'healthz unauthenticated: HTTP %{http_code}\n' http://127.0.0.1/healthz
curl -s -o /dev/null -w 'metrics unauthenticated: HTTP %{http_code}\n' http://127.0.0.1/metrics
healthz unauthenticated: HTTP 200 metrics unauthenticated: HTTP 401

With the per-VM password, the metrics are returned:

PW=$(sudo grep '^GOFLOW2_PASSWORD=' /root/goflow2-credentials.txt | cut -d= -f2-)
curl -s -u "admin:$PW" http://127.0.0.1/metrics | grep -E '^goflow2_flow_(traffic_packets_total|traffic_bytes_total|process_nf_total)'
goflow2_flow_process_nf_total{router="127.0.0.1",version="5"} 5 goflow2_flow_traffic_bytes_total{local_ip="::",local_port="2055",remote_ip="127.0.0.1",type="netflow"} 360 goflow2_flow_traffic_packets_total{local_ip="::",local_port="2055",remote_ip="127.0.0.1",type="netflow"} 5

GoFlow2's own readiness endpoint reports OK when it is collecting:

PW=$(sudo grep '^GOFLOW2_PASSWORD=' /root/goflow2-credentials.txt | cut -d= -f2-)
curl -s -u "admin:$PW" http://127.0.0.1/__health

From your Prometheus server, scrape the endpoint over the network with the same credentials:

scrape_configs: - job_name: goflow2 static_configs: - targets: ['<vm-private-ip>:80'] basic_auth: username: admin password: <GOFLOW2_PASSWORD>

The counters worth alerting on are goflow2_flow_dropped_packets_total, which rises when the kernel receive buffer overflows, and goflow2_flow_decoder_error_total, which rises when an exporter sends something GoFlow2 cannot parse.

Step 9 - Where the flow data lives

Decoded flows are written to a dedicated Azure data disk, not the OS disk, and logrotate keeps 14 compressed generations.

df -h /var/lib/goflow2 | tail -1 sudo wc -l /var/lib/goflow2/flows.json
/dev/sda 20G 32K 19G 1% /var/lib/goflow2 5 /var/lib/goflow2/flows.json

Rotation uses a rename plus a SIGHUP, because GoFlow2's file transport reopens its output on that signal. As a result, no record is lost or duplicated at the rotation boundary, which a copy-and-truncate strategy cannot guarantee.

cat /etc/logrotate.d/goflow2

Step 10 - Working with the flow records

Every record is one JSON object on one line, so jq is the natural tool. List the top talkers by flow count:

sudo tail -n 10000 /var/lib/goflow2/flows.json | jq -r '.src_addr' | sort | uniq -c | sort -rn | head -10

Total bytes seen per destination port:

sudo tail -n 10000 /var/lib/goflow2/flows.json | jq -r '"\(.dst_port) \(.bytes)"' | awk '{b[$1]+=$2} END {for (p in b) print b[p], p}' | sort -rn | head -10

To ship the records onward, point any log shipper at the file. Promtail, Fluent Bit, Vector, Filebeat and the Azure Monitor agent all tail newline delimited JSON natively, and because the file is reopened on SIGHUP rather than truncated, a shipper following it will not lose its position at rotation.

Configuration

Every runtime option is in one environment file, so you never edit the systemd unit.

sudo cat /etc/goflow2/goflow2.env

The GOFLOW2_ARGS line is passed verbatim to the binary. The defaults are:

GOFLOW2_ARGS=-listen netflow://:2055,sflow://:6343 -format json -transport file \
  -transport.file /var/lib/goflow2/flows.json -addr 127.0.0.1:8080 \
  -templates.path /templates -loglevel info

To run four parallel sFlow sockets on a busy collector, change the listen URI to sflow://:6343?count=4,netflow://:2055. To emit protocol buffers instead of JSON, set -format bin. To publish onto Kafka rather than a file, replace the transport arguments:

GOFLOW2_ARGS=-listen netflow://:2055,sflow://:6343 -format bin -transport kafka \
  -transport.kafka.brokers <broker-host>:9092 -transport.kafka.topic flows \
  -addr 127.0.0.1:8080 -loglevel info

Apply any change with a restart, then confirm the service came back:

sudo systemctl restart goflow2.service sleep 3 systemctl is-active goflow2.service

If your exporters use vendor specific IPFIX fields that GoFlow2 does not map out of the box, write a mapping file and add -mapping /etc/goflow2/mapping.yaml to the arguments. The upstream mapping documentation covers the field identifiers.

Maintenance

Rotate the metrics password. The Basic Auth credential is an nginx htpasswd entry, so replacing it is a one liner. Record the new password somewhere safe first, because only the bcrypt hash is stored.

sudo htpasswd -B /etc/nginx/.goflow2.htpasswd admin
sudo systemctl reload nginx

Add TLS. The metrics endpoint serves plain HTTP on port 80. For anything crossing an untrusted network, put a certificate in front of it with your own domain, then restrict port 80 to your Prometheus server.

Sizing the data disk. A single flow record is roughly 700 bytes of JSON. At 1,000 flows per second that is around 60 GB per day before compression, so size the data disk for your retention window, or ship records off the VM with a log shipper or Kafka and keep only a short local buffer.

Watch for drops. If goflow2_flow_dropped_packets_total is climbing, the kernel receive buffer is overflowing. The image already raises net.core.rmem_max in /etc/sysctl.d/60-goflow2.conf; beyond that, add parallel sockets with ?count=N on the listen URI or move to a larger VM size.

Upgrades. GoFlow2 is a single static binary at /usr/local/bin/goflow2. To move to a newer upstream release, download the new goflow2-<version>-linux-amd64 asset, verify its checksum, replace the binary and restart the service.

Backups. Snapshot the data disk on your normal schedule. The collector itself is stateless: /etc/goflow2/goflow2.env is the only configuration worth keeping.

Troubleshooting

No records appear in flows.json. Confirm the exporter can reach the VM at all, then confirm the packets arrive. sudo tcpdump -ni any udp port 2055 or udp port 6343 shows inbound datagrams; if nothing arrives, the NSG rule or the exporter's destination address is wrong. If datagrams arrive but no records are written, the exporter is probably sending NetFlow v9 or IPFIX and its templates have not been received yet; wait a minute and check /templates.

goflow2_flow_decoder_error_total is rising. An exporter is sending something GoFlow2 cannot parse. sudo journalctl -u goflow2.service -n 50 names the source address, which identifies the device.

The metrics endpoint returns 401. The password is per VM and lives in /root/goflow2-credentials.txt. If that file still says first boot has not run, check sudo systemctl status goflow2-firstboot.service.

The metrics endpoint returns 502. nginx is up but GoFlow2 is not answering on loopback. Check systemctl is-active goflow2.service and sudo journalctl -u goflow2.service -n 50.

Check the service logs at any time:

sudo journalctl -u goflow2.service -n 20 --no-pager

Support

This image is published and supported by SecureAnyCloud with 24/7 support. Raise an issue through the Azure Marketplace listing or contact SecureAnyCloud support directly.

Deploy on Azure

Launch GoFlow2 on Ubuntu 24.04 LTS on Azure with 24/7 support from SecureAnyCloud.

View this image

Need Help?

Our support team is available 24/7.

sales@secureanycloud.com

Deploy on Azure