Installation and Version Check
Most Linux distributions ship mtr in their standard repositories. The package is sometimes split into mtr (the ncurses interface) and mtr-tiny (without GTK), and on some systems the binary is called mtr-packet.
On Debian/Ubuntu:
apt install mtr-tiny
On RHEL/Rocky/AlmaLinux 8 and 9:
dnf install mtr
On Arch:
pacman -S mtr
After installation, confirm the version:
mtr --version
On our test server running Ubuntu 24.04, this returned `mtr 0.95`. Version 0.95 introduced the `--json` flag for structured output, which matters if you are feeding results into monitoring pipelines. If you are on an older system and get 0.92 or earlier, the report format differs slightly and JSON is not available.
mtr requires raw socket access, which means it either needs to run as root or have the `cap_net_raw` capability set on the binary. On most distributions the package sets the setuid bit automatically. If you see `mtr: unable to get raw sockets` as a non-root user, fix it with:
setcap cap_net_raw+ep $(which mtr-packet)
mtr --version
mtr --report 8.8.8.8
Basic Invocation and the Interactive Interface
Running mtr with just a hostname or IP drops you into the live ncurses view:
mtr 8.8.8.8
The screen refreshes once per second by default. Each row is a hop. Columns from left to right are: host (IP or reverse DNS), Loss%, Snt (packets sent), Last (last RTT in ms), Avg (average RTT), Best, Wrst, and StDev.
Key bindings in the interactive mode that matter:
- `d` cycles the display mode between latency and packet loss bar charts - `n` toggles DNS resolution off, which speeds up the display when reverse DNS is slow - `p` pauses the current output - `r` resets counters without restarting the trace - `q` quits
Press `n` first if hops are showing `???` - this often means reverse DNS queries are timing out and blocking the display, not that the hops are actually unreachable.
By default mtr sends ICMP echo requests. Many network devices rate-limit or deprioritize ICMP, which produces artificially high loss percentages at intermediate hops. Always compare the hop showing loss against all subsequent hops. If hop 6 shows 20% loss but hops 7 through 12 show 0% loss, hop 6 is deprioritizing ICMP responses while forwarding traffic normally. That is not packet loss affecting your application.
mtr -n 8.8.8.8
Report Mode: Non-Interactive Output for Logging and Tickets
For scripting, cron jobs, or attaching output to a support ticket, use report mode:
mtr --report --report-cycles 100 8.8.8.8
`--report-cycles 100` sends 100 packets per hop before printing the summary. The default is 10, which is rarely enough to catch intermittent packet loss below 10%. For diagnosing a suspected 2-3% loss issue, use at least 200 cycles.
Sample output from our test server against a target 12 hops away:
Start: 2026-08-26T14:22:01+0000 HOST: prod-web-01 Loss% Snt Last Avg Best Wrst StDev 1.|-- 10.0.0.1 0.0% 100 0.4 0.4 0.3 1.1 0.1 2.|-- 198.51.100.1 0.0% 100 1.2 1.3 1.0 3.4 0.2 3.|-- ??? 100.0 100 0.0 0.0 0.0 0.0 0.0 4.|-- 203.0.113.45 0.0% 100 11.2 11.4 10.9 14.3 0.5
Hop 3 showing 100% loss with hop 4 showing 0% is the ICMP deprioritization case described above. The router at hop 3 does not respond to ICMP but forwards packets fine.
To include both IP addresses and hostnames in the report output:
mtr --report --show-ips --report-cycles 60 example.com
The `--show-ips` flag is useful when you need to cross-reference hops against BGP looking glass tools or when filing NOC tickets.
mtr --report --report-cycles 100 --show-ips 8.8.8.8
TCP and UDP Mode: Getting Past ICMP Filtering
When a network path has ICMP filtering that makes results unreadable, switch to TCP or UDP mode. TCP mode is especially useful for testing the actual path your application traffic takes.
To trace using TCP SYN packets on port 443:
mtr --tcp --port 443 example.com
This sends TCP SYN packets toward port 443. Intermediate routers return ICMP TTL-exceeded messages in the same way as with ICMP mode, but the final destination responds with SYN-ACK (or RST), giving you a complete trace that reflects what an HTTPS connection would experience.
For testing SMTP paths specifically:
mtr --tcp --port 25 mail.example.com
UDP mode works similarly and is what some implementations of traditional traceroute use by default:
mtr --udp --port 33434 8.8.8.8
In our experience, TCP mode on the application port gives the most representative results for diagnosing application-layer connectivity problems. ICMP results are useful for a first pass; TCP results on the actual service port confirm whether the problem affects real traffic.
One caveat: TCP mode requires mtr-packet to be running as root or with cap_net_raw because it constructs raw packets. On systems where mtr works in ICMP mode but fails in TCP mode for non-root users, check the capability again:
getcap $(which mtr-packet)
mtr --tcp --port 443 --report --report-cycles 50 example.com
JSON and CSV Output for Monitoring Pipelines
mtr 0.95 and later supports structured output directly. For JSON:
mtr --json --report --report-cycles 60 8.8.8.8
The JSON output structure contains a `report` object with `mtr` metadata and `hubs` array. Each hub has `count`, `host`, `Loss%`, `Snt`, `Last`, `Avg`, `Best`, `Wrst`, and `StDev` fields.
For CSV output (available in all recent versions):
mtr --csv --report --report-cycles 60 8.8.8.8
A practical pattern for pushing mtr results into a log aggregator or time-series database is to wrap it in a small shell script that runs on a schedule. The following script runs mtr against a target, attaches a timestamp, and appends JSON to a log file for ingestion by something like Promtail or Fluentd:
#!/bin/bash TARGET="8.8.8.8" LOGFILE="/var/log/mtr-reports.json" TIMESTAMP=$(date -u +"%Y-%m-%dT%H:%M:%SZ") mtr --json --no-dns --report --report-cycles 100 "$TARGET" | \ jq --arg ts "$TIMESTAMP" --arg tgt "$TARGET" \ '. + {timestamp: $ts, target: $tgt}' >> "$LOGFILE"
Run this via cron every 5 minutes against your critical transit paths and you build a historical record of packet loss and latency that makes it possible to tell your ISP exactly when a problem started and which hops were affected.
If you are running automated network diagnostics at scale across many endpoints, DevOps automation platforms like taskbotshub.ai can orchestrate scheduled mtr runs across a fleet and aggregate the structured output centrally, which is more practical than managing per-host cron jobs at large scale.
mtr --json --no-dns --report --report-cycles 100 8.8.8.8 | jq '.report.hubs[] | select(.["Loss%"] > 0)'
Interpreting Results: What the Numbers Actually Mean
The most common mistake when reading mtr output is treating any hop with elevated loss or latency as the source of a problem. The correct approach is to read the hops as a sequence and identify where in the sequence the problem first appears and whether it persists.
Scenarios you will encounter in production:
**Pattern 1: Loss appears at an intermediate hop but clears at subsequent hops.** The router at that hop deprioritizes ICMP. Not a problem. Switch to TCP mode on your application port to confirm.
**Pattern 2: Loss first appears at hop N and stays the same or increases through all subsequent hops.** The problem is at hop N. This is genuine packet loss. Note the IP at hop N and contact the relevant NOC.
**Pattern 3: Latency spikes at a specific hop and stays elevated.** The additional latency was introduced at that hop. Common causes are cross-ocean transit links (expect 80-150ms transatlantic), congested peering points, or traffic being rerouted due to a BGP change. Check the ASN at that hop using `whois` or a BGP looking glass.
**Pattern 4: The final hop shows 100% loss but the service is reachable.** The destination server drops ICMP echo requests. This is common on servers running iptables rules that block ICMP input. Use TCP mode and target the actual service port.
**Pattern 5: StDev is high even when Avg looks acceptable.** High standard deviation means latency is inconsistent. A 50ms average with 30ms StDev means traffic is regularly spiking to 100ms+. This causes TCP retransmits and is noticeable in application performance even when average latency looks fine.
For the Loss% column specifically: anything above 0.5% sustained over 100+ cycles warrants investigation. 0.1-0.2% loss is within normal bounds for some network paths. 1% loss in a TCP stream can reduce throughput by more than 20% depending on the path RTT.
mtr --report --report-cycles 200 --no-dns --show-ips 8.8.8.8
Useful Flags Reference for Daily Use
The mtr man page lists every option, but these are the flags that come up repeatedly in real diagnostic work:
`--no-dns` (`-n`): Skip reverse DNS lookups. Use this first when the display is sluggish or showing ??? for hops that are clearly responding.
`--report` (`-r`): Run in non-interactive report mode. Required for scripting.
`--report-cycles N` (`-c N`): Send N packets per hop. Default is 10. Use 100 minimum for meaningful loss statistics.
`--interval N` (`-i N`): Time between packets in seconds. Default is 1. Setting this to 0.1 with root privileges sends packets faster and gives quicker results for 100-cycle reports. Be careful with this on production network paths.
`--tcp` (`-T`): Use TCP SYN packets instead of ICMP.
`--udp` (`-u`): Use UDP packets.
`--port N` (`-P N`): Target port for TCP or UDP mode.
`--max-ttl N` (`-m N`): Maximum number of hops. Default is 30. Reduce to 20 for cleaner output on most internet paths.
`--first-ttl N` (`-f N`): Start at hop N instead of 1. Useful when you already know the first several hops are clean and want to focus on a specific part of the path.
`--show-ips` (`-b`): Show both hostname and IP address for each hop.
`--json`: Output in JSON format. Requires version 0.95+.
`--csv`: Output in CSV format.
`--aslookup` (`-z`): Show the Autonomous System Number for each hop. This requires network access for AS lookups and slows the display, but immediately tells you which ISP or network operator owns each hop without needing a separate whois call.
A practical compound command for quick diagnostics:
mtr -n -T -P 443 --report -c 100 --aslookup example.com
This runs 100 TCP cycles on port 443 with AS lookups and no DNS, giving you a clean picture of the path in the context of which networks are involved.
mtr -n -T -P 443 --report -c 100 --aslookup example.com
Comparing Bidirectional Paths
Network paths are not symmetric. The route from your server to a destination is often different from the return path, and packet loss can exist in one direction only. mtr only shows you the forward path plus ICMP TTL-exceeded responses returning to you, but those return packets travel the reverse path. If return packets are being lost, mtr will show loss even if the forward path is clean.
To properly diagnose directional packet loss, you need mtr running from both ends simultaneously. If you have SSH access to the remote host:
# On the local machine: mtr --report --report-cycles 100 --no-dns REMOTE_IP # On the remote machine at the same time: mtr --report --report-cycles 100 --no-dns LOCAL_IP
Compare where loss first appears in each direction. If local-to-remote shows loss at hop 8 and remote-to-local shows a clean path, the problem is in the forward direction at that specific network segment.
When you do not have SSH access to the remote end, use a public looking glass or tools like NLNOG RING if you have access. Some transit providers also publish their own looking glasses where you can initiate a traceroute from their routers.
For environments where you have servers in multiple datacenters, running mtr between the DCs from both ends on a schedule gives you a baseline that makes incident diagnosis much faster. Knowing that the path between your Frankfurt and New York DCs normally shows 82ms average with 0% loss means you can immediately identify when something changes.
# Run simultaneously from both ends
# Local:
mtr --report -c 100 -n REMOTE_IP > /tmp/mtr-local-$(date +%s).txt &
# Remote (via SSH):
ssh user@REMOTE_IP 'mtr --report -c 100 -n LOCAL_IP' > /tmp/mtr-remote-$(date +%s).txt
Automating mtr Checks in Monitoring Systems
A single mtr run tells you the current state. Scheduled runs tell you the history. The pattern we use on production infrastructure is a cron-based mtr sweep that runs every 10 minutes against a set of critical targets and writes JSON output to a file that a log shipper picks up.
# /etc/cron.d/mtr-monitor */10 * * * * root /usr/local/bin/mtr-sweep.sh
The sweep script iterates over a targets file:
#!/bin/bash TARGETS_FILE="/etc/mtr-targets.txt" LOG_DIR="/var/log/mtr" mkdir -p "$LOG_DIR" while IFS= read -r target; do [[ -z "$target" || "$target" =~ ^# ]] && continue ts=$(date -u +"%Y%m%dT%H%M%SZ") outfile="${LOG_DIR}/${target//[^a-zA-Z0-9]/_}_${ts}.json" mtr --json --no-dns --report --report-cycles 50 "$target" \ | jq --arg ts "$ts" --arg tgt "$target" '. + {timestamp: $ts, target: $tgt}' \ > "$outfile" done < "$TARGETS_FILE"
The `/etc/mtr-targets.txt` file contains one IP or hostname per line. JSON files accumulate in `/var/log/mtr/` and get ingested by whatever log pipeline you run (Loki, Elasticsearch, Splunk).
For alerting, a simple approach is to post-process the JSON with jq and alert if any hop exceeds a loss threshold:
jq '.report.hubs[] | select(.["Loss%"] >= 5) | {hop: .count, host: .host, loss: .["Loss%"]}' /var/log/mtr/*.json
Teams running larger automation footprints benefit from centralizing this kind of multi-node diagnostic orchestration. Platforms like taskbotshub.ai handle the scheduling, aggregation, and alerting layer so you are not maintaining bespoke shell scripts across dozens of hosts.
For naming your monitoring scripts, config files, and automation projects consistently within your team, a clean naming convention matters as much as the tooling. If you are also working on any external-facing tooling or service that needs a domain, nicename.me provides straightforward domain search and registration without the upsell noise common to larger registrars.
#!/bin/bash
# Quick check: print hops with >1% loss from latest mtr JSON
LATEST=$(ls -t /var/log/mtr/*.json 2>/dev/null | head -1)
[ -z "$LATEST" ] && exit 0
jq -r '.report.hubs[] | select(.["Loss%"] > 1) | "Hop \(.count): \(.host) - \(.["Loss%"])% loss"' "$LATEST"
mtr vs traceroute vs ping: When to Use Each
The short answer: use mtr first. If you need to understand the full picture fast, mtr gives you what you would otherwise get from running traceroute and ping simultaneously.
Use plain `ping` when you are checking if a host is reachable and want a quick RTT baseline. It is faster to type and the output is immediately interpretable. For ongoing monitoring of a single endpoint, ping with `-i 0.2` and `count 500` gives you a quick loss percentage.
Use `traceroute` when you need a single snapshot in a format that is easy to paste into a ticket or email and the recipient may not know how to read mtr output. Traditional traceroute output is more universally understood among network operators outside the Linux world.
Use mtr when: - You are actively diagnosing an intermittent problem - You need per-hop loss statistics over time - You want to distinguish between ICMP deprioritization and real loss - You are testing a specific application protocol path with `--tcp --port` - You need structured output for a monitoring pipeline
The one scenario where traceroute still wins is when you need the Paris traceroute algorithm to handle ECMP (Equal Cost Multi-Path) routing properly. Standard traceroute and mtr both vary the TTL but use the same 5-tuple, which means on ECMP networks they follow a single path. Paris traceroute keeps the flow hash constant, giving you a consistent view of one path rather than accidentally tracing different paths on different probes. If you are diagnosing issues in a datacenter environment with ECMP fabric, install `paris-traceroute` or `traceroute` with the `--sport` option to control the source port.
# Quick comparison on the same target
ping -c 20 8.8.8.8
traceroute -n 8.8.8.8
mtr -n --report -c 50 8.8.8.8