Subscribe and receive upto $1000 discount on checkout. Learn more
Subscribe and receive upto $1000 discount on checkout. Learn more
Subscribe and receive upto $1000 discount on checkout. Learn more
Subscribe and receive upto $1000 discount on checkout. Learn more
Performing Network Traffic Analysis Using tcpdump

When the network gets “just a little” more complicated

In the beginning, a Linux host is simple. One interface, a few services, a predictable set of clients. Then the environment grows the way it always does: a new reverse proxy appears, a monitoring agent gets deployed, a container runtime starts pulling images, a VPN comes online, and suddenly “the network” is no longer a single thing. It becomes a moving system.

That is when small problems start to feel expensive. A login page becomes slow only during peak hours. A database connection resets once every few minutes. A security team asks whether a host is beaconing out. A firewall rule looks correct, but traffic still disappears. At that point, guessing is not engineering.

This is where a CLI-first approach with tcpdump earns its place. We are going to capture exactly what the kernel sees, filter it down to what matters, and turn raw packets into decisions we can defend in production.

Prerequisites and production assumptions

Before we touch packet capture on a real system, we need to be explicit about assumptions. Packet capture is powerful, and in the wrong hands it becomes a data-exfiltration tool. We will set this up in a way that works for home labs, professional environments, and enterprise hosts without turning the box into a liability.

  • Platform: Linux. Commands below assume a modern system with systemd (common on Ubuntu, Debian, RHEL, Rocky, Alma, Fedora, SUSE).
  • OS state: A maintained host with current security updates. We will verify package presence and install missing components.
  • Privileges: We need root privileges for live capture. We will use sudo and also show a controlled way to allow non-root capture using Linux capabilities.
  • Storage: Captures can grow quickly. We will use ring buffers and rotation to avoid filling disks.
  • Time sync: Accurate timestamps matter for incident response. We will verify time sync status.
  • Security posture: Captures may include credentials, session tokens, internal hostnames, and sensitive payloads. We will minimize what we capture, restrict who can capture, and store captures with tight permissions.
  • Firewall: Packet capture does not require opening ports, but it does interact with how we interpret traffic (pre/post NAT, local vs forwarded). We will include verification steps that align with typical firewall deployments.

Confirm we have sudo and basic system health

We are going to confirm we can run privileged commands and that the host clock is sane. This prevents confusing “phantom” issues later when timestamps do not line up with logs.

sudo -v
date
timedatectl status

If sudo -v succeeds, we have the privileges needed. If timedatectl shows time sync is inactive, we should fix NTP before relying on capture timestamps in production investigations.

Install tcpdump safely on Linux

We are going to install tcpdump using the system package manager. This keeps updates and security fixes aligned with the OS lifecycle.

Debian/Ubuntu

We will refresh package metadata and install tcpdump. We also install iproute2 because we will use ip commands to detect interfaces and routes in a copy/paste-safe way.

sudo apt-get update
sudo apt-get install -y tcpdump iproute2

After installation, the binaries are available system-wide. We can confirm versions to ensure we are not troubleshooting an unexpectedly old build.

tcpdump --version
ip -V

RHEL/Rocky/Alma/Fedora

We will install tcpdump via dnf. On older RHEL-like systems, yum may be present, but dnf is the modern default.

sudo dnf install -y tcpdump iproute

We verify the installation the same way to confirm the toolchain is present.

tcpdump --version
ip -V

Establish a clean baseline before capturing

Before we capture anything, we need to know what “normal” looks like: which interface is actually used for default egress, what IP addresses are assigned, and what services are listening. This prevents capturing on the wrong interface and drawing the wrong conclusions.

Identify the default interface and local addressing

We are going to ask the kernel which interface it uses to reach the internet. This is more reliable than guessing names like eth0 or ens192.

ip route show default

The output shows the default route and the interface after dev. Next, we will store that interface name in a shell variable so subsequent commands remain copy/paste-safe.

EXT_IFACE=$(ip route show default | awk '/default/ {print $5; exit}')
echo "Default interface: ${EXT_IFACE}"
ip -br addr show "${EXT_IFACE}"

Now we have a reliable interface target and we can see the assigned IP addresses. This is the interface we will use for most host-level traffic analysis.

Check what is listening and what is established

We are going to list listening sockets and a quick view of established connections. This gives us immediate context: which ports should have traffic, and which processes are involved.

sudo ss -lntup
sudo ss -antp | head -n 50

This does not change the system. It simply gives us a map of services and active connections so our capture filters can be precise.

Capture strategy: controlled, minimal, and defensible

In production, the goal is not “capture everything.” The goal is “capture enough to answer the question without creating new risk.” We will use three principles:

  • Minimize scope: filter by host, port, protocol, and direction when possible.
  • Minimize payload exposure: capture headers when that is sufficient, and avoid full payload unless we have a clear reason.
  • Minimize operational impact: use snapshot length, ring buffers, and rotation to protect CPU and disk.

Step 1: Run a safe, short live capture to validate visibility

We are going to run a short capture on the default interface to confirm we can see traffic. We will keep it readable by disabling name resolution and limiting output volume.

sudo tcpdump -i "${EXT_IFACE}" -nn -c 50

This captures 50 packets and exits. The -nn flag prevents DNS and service-name lookups, which keeps output fast and avoids misleading translations. If we see packets, we have confirmed visibility on the correct interface.

Verification: confirm tcpdump is actually capturing on the expected interface

We are going to print the interface details again and confirm the capture interface matches the default route interface.

echo "Capturing on: ${EXT_IFACE}"
ip -br link show "${EXT_IFACE}"

If the interface is UP and we saw packets in the previous step, our baseline is correct.

Step 2: Use precise filters to answer real operational questions

Now we move from “we can capture” to “we can prove what is happening.” Each example below starts with the reasoning, then a copy/paste-safe command, then what the output means.

Question: Is this host talking to a specific remote IP?

We are going to capture only traffic to or from a single remote IP. This is a common incident-response and troubleshooting move when we suspect a dependency, a misroute, or unexpected outbound traffic.

First, we will define the remote IP as a shell variable so the command stays clean.

REMOTE_IP="203.0.113.10"
echo "Remote IP set to: ${REMOTE_IP}"

Now we will capture only packets where that IP is either source or destination.

sudo tcpdump -i "${EXT_IFACE}" -nn "host ${REMOTE_IP}"

This immediately reduces noise. If we see SYNs without SYN-ACKs, we likely have a routing or firewall issue. If we see periodic small outbound packets, we may be looking at health checks, telemetry, or something that needs deeper review.

Question: Are we seeing TCP handshake failures on a service port?

We are going to focus on a single TCP port and watch the handshake. This is how we separate “application is down” from “network path is broken.”

First, we will pick a port and store it as a variable.

SVC_PORT=443
echo "Service port set to: ${SVC_PORT}"

Now we capture only TCP traffic for that port.

sudo tcpdump -i "${EXT_IFACE}" -nn "tcp port ${SVC_PORT}"

If we see SYN packets leaving but no SYN-ACK returning, the issue is upstream (remote service, firewall, routing, security group). If we see SYN and SYN-ACK but then RST, the issue may be local service behavior, a proxy, or a middlebox resetting connections.

Question: Is DNS the real problem?

DNS issues often masquerade as “the app is slow.” We are going to capture only DNS traffic (UDP/TCP 53) and look for timeouts, retries, and unexpected resolvers.

sudo tcpdump -i "${EXT_IFACE}" -nn "port 53"

If we see repeated queries with no responses, the resolver is unreachable or blocked. If responses arrive but point to unexpected IPs, we may be dealing with split-horizon DNS, stale records, or a compromised resolver path.

Question: Are we dropping packets or fragmenting unexpectedly?

Fragmentation and MTU issues show up as intermittent failures, especially with VPNs, tunnels, or certain cloud paths. We are going to look for IP fragments and ICMP “fragmentation needed” messages.

sudo tcpdump -i "${EXT_IFACE}" -nn "ip[6:2] & 0x1fff != 0 or icmp"

If we see fragments frequently, we should review MTU settings and path MTU discovery behavior. If we see ICMP “frag needed” but traffic still fragments, a firewall may be blocking ICMP, breaking PMTUD and causing hard-to-diagnose stalls.

Step 3: Capture to files with rotation to protect disk

Live output is great for quick answers, but production work often needs evidence we can share with a team, attach to an incident, or replay later. We are going to write captures to disk in a controlled way using rotation.

Create a secure capture directory

We are going to create a dedicated directory with strict permissions. This reduces the chance of sensitive packet data being read by unauthorized users.

sudo install -d -m 0700 -o root -g root /var/log/tcpdump

This creates /var/log/tcpdump owned by root with permissions that only root can access.

Run a rotating capture with a ring buffer

We are going to capture on the default interface, limit per-file size, and keep a fixed number of files. This prevents disk exhaustion while still giving us a time window of history.

sudo tcpdump -i "${EXT_IFACE}" -nn -s 0 -C 50 -W 10 -w /var/log/tcpdump/capture.pcap

This writes up to 10 files of 50 MB each (about 500 MB total). When the limit is reached, it overwrites the oldest file. The -s 0 captures full packets; in sensitive environments we may want to reduce this (see next step) to minimize payload exposure.

Reduce payload exposure with snapshot length

If we only need headers and not full payloads, we can reduce the snapshot length. This is a practical security control: we still see flows, flags, and metadata, but we capture less sensitive content.

sudo tcpdump -i "${EXT_IFACE}" -nn -s 128 -C 50 -W 10 -w /var/log/tcpdump/headers_only.pcap

This captures only the first 128 bytes of each packet, which is often enough for troubleshooting handshakes, DNS, and many protocol headers.

Verification: confirm files are being written and permissions are correct

We are going to list the directory and confirm ownership and size growth.

sudo ls -l /var/log/tcpdump
sudo du -sh /var/log/tcpdump

If files appear and sizes increase during active traffic, rotation is working. If the directory remains empty, we likely captured on the wrong interface or the process exited due to permissions or filter errors.

Step 4: Make capture operationally repeatable with a systemd service

In production, we often need a capture that survives disconnects, runs consistently during an incident window, and can be started/stopped cleanly. We are going to create a systemd unit that runs a controlled rotating capture.

Create a dedicated configuration file

We are going to store settings in /etc/default/tcpdump-capture so changes are centralized and auditable. This also keeps the unit file stable.

sudo tee /etc/default/tcpdump-capture >/dev/null <<'EOF'
# Interface to capture on. Keep this explicit for production.
IFACE=

# BPF filter. Keep it narrow when possible.
# Example: "tcp port 443" or "host 203.0.113.10"
FILTER=

# Snapshot length. Use 128 for headers-only, 0 for full packets.
SNAPLEN=128

# Rotation: size in MB per file and number of files.
FILE_SIZE_MB=50
FILE_COUNT=10

# Output directory and base filename.
OUT_DIR=/var/log/tcpdump
OUT_FILE=capture.pcap
EOF

This file is created with root ownership. We intentionally leave IFACE and FILTER empty so we must make an explicit choice before enabling capture.

Populate the interface safely using the detected default route

We are going to write the detected default interface into the config file. This keeps the service copy/paste-safe while still being explicit.

EXT_IFACE=$(ip route show default | awk '/default/ {print $5; exit}')
echo "Detected default interface: ${EXT_IFACE}"
sudo sed -i "s/^IFACE=.*/IFACE=${EXT_IFACE}/" /etc/default/tcpdump-capture

The config now has a concrete interface value. This reduces the risk of capturing on the wrong interface after a reboot.

Create the systemd unit

We are going to create a service that reads the config file, ensures the output directory exists, and runs tcpdump with rotation. We also set conservative hardening options appropriate for a capture process.

sudo tee /etc/systemd/system/tcpdump-capture.service >/dev/null <<'EOF'
[Unit]
Description=Controlled tcpdump capture with rotation
After=network-online.target
Wants=network-online.target

[Service]
Type=simple
EnvironmentFile=/etc/default/tcpdump-capture
ExecStartPre=/usr/bin/install -d -m 0700 -o root -g root ${OUT_DIR}
ExecStart=/usr/sbin/tcpdump -i ${IFACE} -nn -s ${SNAPLEN} -C ${FILE_SIZE_MB} -W ${FILE_COUNT} -w ${OUT_DIR}/${OUT_FILE} ${FILTER}
Restart=on-failure
RestartSec=3

# Hardening (tcpdump needs raw socket access; we keep the rest tight)
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=strict
ProtectHome=true
ReadWritePaths=${OUT_DIR}
LockPersonality=true
MemoryDenyWriteExecute=true
RestrictSUIDSGID=true
RestrictRealtime=true

[Install]
WantedBy=multi-user.target
EOF

This unit does not open ports and does not change firewall behavior. It simply runs a controlled capture and writes to a restricted directory.

Start the service and verify status

We are going to reload systemd, start the service, and confirm it is running. Verification here matters because a mis-specified interface or filter will cause immediate failure.

sudo systemctl daemon-reload
sudo systemctl start tcpdump-capture.service
sudo systemctl status --no-pager tcpdump-capture.service

If the service is active, capture is running. If it failed, the status output will usually show whether the interface was invalid or the filter expression was malformed.

Enable persistence across reboots

We are going to enable the service so it starts automatically after reboot. This is useful during incident windows or when we need continuous short-history capture for intermittent issues.

sudo systemctl enable tcpdump-capture.service
sudo systemctl is-enabled tcpdump-capture.service

If is-enabled returns enabled, the capture will persist across reboots.

Verification: confirm capture files are rotating under systemd

We are going to confirm that files exist and that the service is writing to the expected directory.

sudo ls -l /var/log/tcpdump
sudo journalctl -u tcpdump-capture.service --no-pager -n 50

If files are present and the journal shows no errors, the service is operating as intended.

Step 5: Controlled non-root capture with Linux capabilities

In many environments, we do not want to hand out full root access just to capture packets. We are going to allow tcpdump to open raw sockets using Linux capabilities. This is still sensitive, but it is narrower than full root.

Grant capture capabilities to tcpdump

We are going to set cap_net_raw and cap_net_admin on the tcpdump binary. This allows packet capture without running as root in many cases. We should treat this as a policy decision and document it, because it changes the security posture of the host.

TCPDUMP_BIN=$(command -v tcpdump)
echo "tcpdump path: ${TCPDUMP_BIN}"
sudo setcap cap_net_raw,cap_net_admin=eip "${TCPDUMP_BIN}"
getcap "${TCPDUMP_BIN}"

If getcap shows the capabilities, the binary can perform privileged network operations without full root. We should restrict which users can execute tcpdump via file permissions and group membership.

Restrict execution to a dedicated group

We are going to create a group for packet capture operators and restrict the binary so only that group (and root) can run it. This is a practical enterprise control.

sudo groupadd -f pcap
sudo chgrp pcap "${TCPDUMP_BIN}"
sudo chmod 0750 "${TCPDUMP_BIN}"
ls -l "${TCPDUMP_BIN}"

Now only root and members of the pcap group can execute tcpdump. We can add an operator to the group when needed.

echo "To add a user to the pcap group, run:"
echo "sudo usermod -aG pcap <username>"

We intentionally do not run the user modification automatically because usernames differ per environment and we want that change to be deliberate and auditable.

Firewall considerations and interpretation

Packet capture does not require firewall changes, but firewall placement changes what we observe:

  • Local INPUT filtering: We may see inbound SYNs that never reach the application if the firewall drops them.
  • Local OUTPUT filtering: We may see the application attempt connections that never leave the host.
  • NAT: Captures on the host show pre/post NAT depending on where NAT occurs (host vs upstream gateway).

We are going to verify firewall status using common tools. We will run whichever applies to the host.

Verify nftables (common modern default)

We are going to list rulesets to understand whether drops or rejects could explain what we see in captures.

sudo nft list ruleset | head -n 200

If we see default drop policies, we should expect to observe traffic arriving at the interface but not reaching sockets.

Verify iptables (legacy or compatibility)

We are going to list filter rules and policies. This helps correlate capture evidence with enforcement.

sudo iptables -S
sudo iptables -L -n -v | head -n 100

If counters increase on DROP rules while we see matching packets in tcpdump, the firewall is likely the reason the application does not see the traffic.

Troubleshooting

When tcpdump does not behave as expected, the failure modes are usually consistent. We are going to focus on symptoms, likely causes, and fixes that work in real environments.

Symptom: “tcpdump: command not found”

  • Likely cause: Package not installed or PATH mismatch.
  • Fix: Install via the OS package manager and verify the binary path.
command -v tcpdump || true
sudo sh -c 'command -v apt-get >/dev/null && apt-get update && apt-get install -y tcpdump iproute2 || true'
sudo sh -c 'command -v dnf >/dev/null && dnf install -y tcpdump iproute || true'
command -v tcpdump
tcpdump --version

After installation, command -v tcpdump should return a path and tcpdump --version should print version details.

Symptom: “tcpdump: interface: No such device exists”

  • Likely cause: Wrong interface name, interface renamed after reboot, or capturing on a down interface.
  • Fix: Detect the default interface again and confirm link state.
ip -br link
EXT_IFACE=$(ip route show default | awk '/default/ {print $5; exit}')
echo "Default interface: ${EXT_IFACE}"
ip -br link show "${EXT_IFACE}"

If the interface is not present, we should capture on the correct one from ip -br link or fix the network configuration first.

Symptom: “tcpdump: You don’t have permission to capture on that device”

  • Likely cause: Not running with sufficient privileges.
  • Fix: Use sudo, or apply capabilities and group restrictions as shown earlier.
sudo tcpdump -i any -nn -c 5

If this works under sudo, the issue is privilege-related. If we need controlled non-root capture, we should use the capabilities approach and restrict execution to a dedicated group.

Symptom: Capture runs but shows no traffic

  • Likely causes: Capturing on the wrong interface, overly restrictive filter, traffic is on a VLAN/bond/bridge interface, or offloaded traffic visibility differences.
  • Fix: Capture on any briefly to confirm traffic exists, then narrow down.
sudo tcpdump -i any -nn -c 50

If traffic appears on any but not on ${EXT_IFACE}, we should identify which interface is actually carrying the packets and adjust the capture target accordingly.

Symptom: “tcpdump: syntax error” when using a filter

  • Likely cause: Malformed BPF expression or quoting issues.
  • Fix: Use simple filters first, then build up. Keep the filter inside double quotes.
sudo tcpdump -i "${EXT_IFACE}" -nn "tcp"
sudo tcpdump -i "${EXT_IFACE}" -nn "tcp port 443"
sudo tcpdump -i "${EXT_IFACE}" -nn "host 203.0.113.10 and tcp port 443"

Once a simple filter works, we can safely extend it. If a complex filter fails, we should simplify until it works, then add conditions one at a time.

Symptom: Disk space fills up during capture

  • Likely cause: Writing to a single file without rotation, or rotation settings too large for the filesystem.
  • Fix: Use -C and -W rotation, and confirm directory size regularly.
sudo du -sh /var/log/tcpdump || true
sudo tcpdump -i "${EXT_IFACE}" -nn -s 128 -C 20 -W 5 -w /var/log/tcpdump/limited.pcap

This reduces total disk usage to about 100 MB while still providing a useful rolling window.

Common mistakes

Mistake: Capturing without -nn and misreading the output

Symptom: Output is slow, hostnames appear that do not match expectations, and ports show as service names that confuse incident timelines.

Fix: Always start with -nn for operational captures.

sudo tcpdump -i "${EXT_IFACE}" -nn -c 50

This keeps output deterministic and fast.

Mistake: Capturing “everything” and creating a data-handling incident

Symptom: Large PCAP files contain sensitive payloads, credentials, or internal identifiers, and access control becomes unclear.

Fix: Reduce scope with filters and reduce payload with snapshot length. Store captures in a restricted directory.

sudo install -d -m 0700 -o root -g root /var/log/tcpdump
sudo tcpdump -i "${EXT_IFACE}" -nn -s 128 -C 50 -W 10 -w /var/log/tcpdump/headers_only.pcap "tcp port 443"

This captures only what we need, where we can control access.

Mistake: Capturing on the wrong interface in virtualized or containerized hosts

Symptom: The application is clearly receiving traffic, but the capture shows nothing (or shows only internal chatter).

Fix: Confirm the default route interface and, if needed, do a short capture on any to locate the real path.

ip route show default
sudo tcpdump -i any -nn -c 50

Once we identify the interface carrying the traffic, we switch the capture to that interface and apply the correct filters.

How do we at NIILAA look at this

This setup is not impressive because it is complex. It is impressive because it is controlled. Every component is intentional. Every configuration has a reason. This is how infrastructure should scale — quietly, predictably, and without drama.

At NIILAA, we help organizations design, deploy, secure, and maintain CLI-first operational capabilities like this in real production environments: from least-privilege access models and audit-friendly service design, to incident-ready capture workflows, retention controls, and repeatable verification. When the network gets noisy, we make sure the way we observe it stays calm and defensible.

Leave A Comment

All fields marked with an asterisk (*) are required