How to detect beaconing (C2 traffic) in a PCAP

Malware that has a foothold usually checks in with its controller on a timer. That rhythm survives encryption, so you can often see it in a capture even when you can't read a single payload. The hard part is telling it apart from the many legitimate programs that also run on a timer.

By Stuart Mathieson9 minute readWireshark + tsharkUpdated 12 October 2026
The quick way

In PCAPNG Analyzer Pro, this whole guide happens on upload. Every host pair’s check-ins are measured; regular ones are flagged with the client, server, port and interval; keepalives on long-lived connections are told apart; and you can see at a glance whether the destination is new to your network.

See a sample incident report →
In this guide
  1. What beaconing looks like
  2. List connection starts
  3. Measure the intervals
  4. Validate a candidate in Wireshark
  5. Rule out legitimate pollers
  6. Manual vs automated workflow

1. What beaconing looks like

A beacon is one host contacting the same destination again and again at a roughly fixed interval: every 30 seconds, every 5 minutes, every hour. Each check-in is usually small and similar in size, and the pattern continues whether or not anyone is using the machine.

Two things make it harder to spot than that description suggests:

The useful measurement is therefore not "is it regular?" but "how regular is it, relative to its average interval?" The coefficient of variation (CoV) answers that: the standard deviation of the intervals divided by their mean. A perfect timer scores 0. Uniform ±20% jitter scores about 0.12, and even ±50% jitter stays below 0.3. Ordinary human browsing usually scores well above 0.5.

2. List connection starts, not packets

Measure the time between check-ins, not between packets. A check-in over HTTPS is a burst of 10 or more packets: handshake, request, response, teardown. Packet-to-packet gaps inside that burst are milliseconds, while the gap between bursts is the beacon interval. Mixing the two produces a meaningless average.

For TCP, each new connection starts with a SYN. List every initial SYN with its time, client, server and port, leaving out retried SYNs so one connection counts once:

tshark -r capture.pcapng \
  -Y "tcp.flags.syn == 1 && tcp.flags.ack == 0 && !tcp.analysis.retransmission" \
  -T fields -e frame.time_epoch -e ip.src -e ip.dst -e tcp.dstport \
  > starts.tsv

Some beacons reuse one long-lived connection, and CDN-hosted ones change IP address. Two other event lists catch those:

# One row per DNS lookup, by client and name
tshark -r capture.pcapng -Y "dns.flags.response == 0" \
  -T fields -e frame.time_epoch -e ip.src -e dns.qry.name > lookups.tsv

# One row per TLS or QUIC ClientHello, by client and server name
tshark -r capture.pcapng -Y "tls.handshake.type == 1" \
  -T fields -e frame.time_epoch -e ip.src -e tls.handshake.extensions_server_name > hellos.tsv

Grouping by server name rather than IP address keeps a beacon together when its destination moves between CDN addresses. Recent Wireshark versions decrypt QUIC Initial packets, so the ClientHello list includes HTTP/3 connections too.

3. Measure the intervals

Save this short awk script as intervals.awk. It reads a timestamp followed by the fields that identify a pair, then prints the CoV, the number of events, the average interval and the pair:

# Input: a timestamp, then the fields that identify the pair, sorted by
# pair and then time. Prints CoV, count, mean interval and the pair.
# Set -v min=SECONDS to skip pairs whose average gap is shorter.
{
  t = $1; sub(/^[^ \t]+[ \t]+/, ""); key = $0
  if (key != prev) { report(); prev = key; n = 0; sum = 0; sumsq = 0; last = t; next }
  gap = t - last; last = t; n++; sum += gap; sumsq += gap * gap
}
END { report() }
function report(   mean, var) {
  if (n < 5) return
  mean = sum / n; if (mean < min) return
  var = sumsq / n - mean * mean; if (var < 0) var = 0
  printf "%.2f\t%d\t%.1fs\t%s\n", sqrt(var) / mean, n + 1, mean, prev
}

Sort each list by pair and time, run the script, and sort the result so the most regular pairs come first:

sort -k2 -k1,1n starts.tsv | awk -v min=5 -f intervals.awk | sort -n | head -20

Pairs need at least six events to be reported. min=5 drops pairs whose average gap is under five seconds: a browser opening several connections to one site at once otherwise looks perfectly regular. A beacon stands out like this:

0.06	20	29.8s	192.168.1.30	198.51.100.23	443

Twenty connections, one every 29.8 seconds on average, with a CoV of 0.06. In the same kind of list, ordinary browsing typically scores 0.5 to 2.

Regular timing proves automation, not malice.

A low CoV says a program is running on a timer. It doesn't say which program, or whether it's unwanted. Treat each low-CoV pair as a question to answer, not a finding to report.

4. Validate a candidate in Wireshark

Take one pair from the list and look at it directly. Filter to its connection starts:

tcp.flags.syn == 1 && tcp.flags.ack == 0 && ip.src == 192.168.1.30 && ip.dst == 198.51.100.23

Then choose View → Time Display Format → Seconds Since Previous Displayed Packet. The Time column now shows each interval directly, so you can see the rhythm and any outliers. Then check:

5. Rule out legitimate pollers

Most regular traffic on a healthy network is legitimate. Expect to find:

A beacon becomes more interesting when several of these hold together:

The capture can't tell you which process made the connections. That needs the endpoint: EDR telemetry, Sysmon network events, or netstat -ano on the host while the pattern is live.

Mind the capture point, too. Behind NAT or a proxy, many clients share one address, so the "client" in your list may be a gateway. And DNS-over-HTTPS hides lookups entirely, leaving only the connection pattern.

Manual method vs PCAPNG Analyzer

Manual method

  1. Capture for long enough to see several check-ins.
  2. Export connection starts, lookups and ClientHellos with tshark.
  3. Compute intervals and CoV per pair.
  4. Validate candidates in Wireshark.
  5. Rule out known pollers, then confirm the process on the endpoint.

With PCAPNG Analyzer

  1. Upload the capture, or let a watched folder ingest captures continuously (Pro).
  2. Review the beaconing finding: who, where, how often and how regular, with the usual harmless causes and what to check next.
  3. Click the destination to see every capture it appears in, and check the client’s JA4 and the destination’s first-seen status.
What the beaconing finding measures

The detector groups each host pair’s packets into check-ins (bursts separated by a quiet gap) and flags pairs whose check-ins come at regular intervals. By default that means at least six check-ins, at least five seconds apart on average, with the interval varying by less than 30%; all three are adjustable in Settings → Detection Rules. Check-ins that reuse one long-lived connection, the usual shape of an application keepalive, and timers between two private hosts are reported as informational. High severity is kept for very regular timers to external addresses. History adds what timing can’t: whether the destination is new, and whether the pattern repeats across captures.

Keep the context beacons need

Run PCAPNG Analyzer on your own computer or server, keep a history of captures, and see when a destination first appeared. Your packet data never leaves infrastructure you control.

Compare Free and ProSelf-host it