Caution
This is a work in progress branch, not the main one.
A cross-platform ping program using TCP, UDP or HTTP(s) instead of ICMP, inspired by Linux's ping utility.
Tip
This document is also available in 中文.
Here are some of the features of TCPING:
- An alternative to
pingin environments thatICMPis blocked. - Probes over
TCP,HTTP(S)andUDP, picked from the target you give it. - Outputs information in colored, plain, JSON, CSV and sqlite3 formats, or sends it to Grafana Alloy and InfluxDB as metrics.
- Monitor and audit your or your peers network latency, packet loss, and connection quality.
- Lets you specify the source interface, timeout, and interval between probes.
- Supports both
IPv4orIPv6and lets you enforce using either. - Prints total connection statistics by pressing the
Enterkey, without stopping the program. - Reports the longest encountered
downtimeanduptimeduration and time. - Reports the minimum, average, maximum and mean deviation of the latency, the same way
pingdoes. - Retries hostname resolution after a predetermined number of probe failures using the
-rflag, or before every single probe using--resolve-every-probe. Suitable to test yourDNSload balancing or Global Server Load Balancer(GSLB). - Reports how long hostname resolution itself took, at startup and on every retry.
- Uses different
sequence numberingfor successful and unsuccessful probes to infer the total failed or successful probes at a glance.
Check out the demos to get a look and feel of tcping.
- TCPING
Click to expand
We offer prebuilt binaries for various operating systems (Windows, Linux, macOS, FreeBSD, Docker) and architectures (amd64, arm64), which can be found on the release page.
The binaries are static, meaning they carry everything they need inside one file and do not depend on any library being present on your machine.
Once you are done with the download and installation, head to the usage section.
The best way to install tcping on Windows is through Windows Package Manager by utilizing WinGet, which is available on practically all Windows 10 and 11 machines by default since September of 2020:
winget install pj.tcpingIf you wish to manually install tcping, extract the downloaded zip file and copy tcping.exe to your system PATH like C:\Windows\System32
Caution
TCPING might falsely get flagged by Windows Defender or some anti-malware software. This is common among Go programs. Check out the official statement from the Go team here.
Warning
The --db (sqlite3) output format is not available on Windows binaries anymore. All the other flags work as expected.
Install using brew:
brew install pouriyajamshidi/tap/tcpingYou can also manually download and install tcping following the steps described in this section.
Paste the following in your terminal to grab the latest static binary for your architecture and install it:
cd /tmp &&
ARCH=$(uname -m | sed 's/x86_64/amd64/; s/aarch64/arm64/') &&
curl -LO "https://github.com/pouriyajamshidi/tcping/releases/latest/download/tcping-linux-$ARCH.tar.gz" &&
tar -xf "tcping-linux-$ARCH.tar.gz" &&
sudo install tcping /usr/local/bin/ &&
sudo install -Dm 644 completions/tcping.bash /usr/share/bash-completion/completions/tcping &&
sudo install -Dm 644 completions/_tcping /usr/share/zsh/site-functions/_tcping &&
sudo install -Dm 644 completions/tcping.fish /usr/share/fish/vendor_completions.d/tcping.fish &&
tcping --versionThe last three lines install the shell completions. Drop the ones for the shells you do not use, and start a new shell to pick them up.
If you don't have curl, swap its line for wget:
wget "https://github.com/pouriyajamshidi/tcping/releases/latest/download/tcping-linux-$ARCH.tar.gz" &&On Debian and its flavors such as Ubuntu, download the .deb package:
wget https://github.com/pouriyajamshidi/tcping/releases/latest/download/tcping-amd64.deb -O /tmp/tcping.deb
# Or for ARM64 machines
wget https://github.com/pouriyajamshidi/tcping/releases/latest/download/tcping-arm64.deb -O /tmp/tcping.debAnd install it:
sudo apt install -y /tmp/tcping.debIf you are using different Linux distros, proceed to this section.
Download the file for your respective OS and architecture:
wget https://github.com/pouriyajamshidi/tcping/releases/latest/download/tcping-freebsd-amd64.tar.gz
# Or for Linux ARM64 machines and using cURL
curl -LO https://github.com/pouriyajamshidi/tcping/releases/latest/download/tcping-linux-arm64.tar.gzExtract the file:
tar -xvf tcping-freebsd-amd64.tar.gzMake the file executable:
chmod +x tcpingCopy the executable to your system PATH like /usr/local/bin/:
sudo cp tcping /usr/local/bin/The archive also carries a completions folder. See Shell Completions to install them.
Tip
In case you have brew installed, you can install tcping using brew install pouriyajamshidi/tap/tcping
These are some additional ways in which tcping can be installed:
-
Dockerimages:docker pull pouriyajamshidi/tcping:latest # Or docker pull ghcr.io/pouriyajamshidi/tcping:latest -
Using
go install:This requires at least go version
1.26.7go install github.com/pouriyajamshidi/tcping/v3@latest
-
Directly without installation in x-cmd.
x tcping example.com 80
Or install
tcpinglocally using x-cmd, without needing root privileges or affecting your global setup.x env use tcping tcping example.com 80
-
Finally, you can compile the code yourself by running the
makecommand:make build
This will place the executables in the
outputfolder.
Completion scripts for bash, zsh, fish and PowerShell live in the
completions folder. They complete the flags, the interface names
for -I and the file names for --csv and --db.
The Debian package and the Linux quick install put them in place for you. The release archives ship them next to the binary, so they can also be installed from there with the commands below.
-
bash:sudo install -Dm 644 completions/tcping.bash /usr/share/bash-completion/completions/tcping
-
zsh:sudo install -Dm 644 completions/_tcping /usr/share/zsh/site-functions/_tcping
-
fish:install -Dm 644 completions/tcping.fish ~/.config/fish/completions/tcping.fish -
PowerShell, by dot-sourcing the script from your profile:Add-Content $PROFILE ". C:\path\to\tcping.ps1"
Start a new shell afterwards to pick them up.
tcping can run in various ways.
- The simplest form is providing the target and the port number:
tcping www.example.com 443- You can also use the
host:portformat:
tcping www.example.com:443
# Or with an IP address
tcping 192.168.1.1:80
# IPv6 addresses (use quotes to prevent shell interpretation)
tcping '[2001:db8::1]:443'- Specify the interval between probes (2 seconds), the timeout (5 seconds) and source interface:
tcping www.example.com 443 -i 2 -t 5 -I eth2- Enforce using IPv4 or IPv6 only:
tcping www.example.com 443 -4
# Or
tcping www.example.com 443 -6- Show timestamp of probes:
tcping www.example.com 443 -D- Retry resolving the hostname after 5 failures:
tcping www.example.com 443 -r 5
- Stop after 5 probes:
tcping www.example.com 443 -c 5- Change the default output from colored to:
# Save the output in CSV format:
tcping www.example.com 443 --csv example.com.csv
# Save the output in sqlite3 format:
tcping www.example.com 443 --db example.com.db
# Show the output in JSON format:
tcping www.example.com 443 -j
# Show the output in JSON format - pretty:
tcping www.example.com 443 -j --pretty
# Show the output in plain (no ANSI colors):
tcping www.example.com 443 --no-colorNote
Check the available flags here for a more advanced usage.
The Docker image can be used with the same set of flags, like:
# If downloaded from Docker Hub
docker run -it pouriyajamshidi/tcping:latest example.com 443
# Or using host:port format
docker run -it pouriyajamshidi/tcping:latest example.com:443
# If downloaded from GitHub container registry:
docker run -it ghcr.io/pouriyajamshidi/tcping:latest example.com 443
# Or using host:port format
docker run -it ghcr.io/pouriyajamshidi/tcping:latest example.com:443Tip
Press the Enter key while the program is running to see the summary of all probes without stopping the program, as shown in the demos section.
Give a URL instead of a host and a port and tcping probes it over HTTP(S), reporting the status code and how long the whole request took:
tcping https://www.example.com/healthThe port comes from the scheme, 80 or 443, unless the URL carries its own
or you pass one after it:
tcping http://www.example.com:8080/health
# Same thing
tcping http://www.example.com/health 8080-v shows everything a probe collected: the HTTP version, the TLS version and
cipher, how many days are left on the certificate and the connect, TLS
handshake and first-byte timings. --insecure skips the certificate check,
which is what you want against a self-signed or an expired one:
tcping https://www.example.com/health -v
tcping https://self-signed.example.com --insecuretcping udp://127.0.0.1 53UDP has no handshake, so a probe only succeeds when the other end answers it. To get an answer, run tcping as a UDP server on the other end, which echoes every datagram back to its sender:
# on the machine being probed:
tcping --udp-server 127.0.0.1 9999
# on the machine probing it:
tcping udp://127.0.0.1 9999Note
A UDP probe that is refused reports port unreachable, which means
something is blocking us. A probe that gets no answer at all is reported as
a failure too, but it cannot tell an open port that stays quiet from a
packet that was dropped on the way.
Every UDP probe sends its own number as the payload, which the server echoes
back. Adding -v shows that number on each line, so a lost probe can be named:
Reply from 127.0.0.1 on port 9999 UDP_conn=4 time=1.276 ms
reply echoed back probe 4
tcping www.example.com 443 --alloy http://localhost:4318Instead of printing each probe, tcping sends it to Alloy over OTLP, which can
forward it to Prometheus and turn a run into a graph. Every probe sends
tcping_probe_success, tcping_probe_rtt_milliseconds and
tcping_probes_total, labelled with the source, target, port and protocol. An
HTTP(S) target also sends the status code, the connect, TLS handshake and
first-byte timings, and the days left on the certificate. A UDP target sends
whether the reply was echoed back, whether the port refused us and how big the
reply was.
The address the target resolved to is sent on its own as
tcping_target_address, which is always 1 and carries the address as a label.
It is kept off the probe metrics because a label is part of what identifies a
series: with -r or --resolve-every-probe a hostname that resolves somewhere
else mid-run would leave the old series behind and start a new one, which
breaks a graph into pieces and makes the counters add up wrong. Query it on its
own to see which addresses a target has been answering from:
tcping_target_address{target="www.example.com"}
If you want the address alongside the probes, join to it, keeping in mind that this only works while the target has one address at a time. A round-robin hostname has several of them live at once and the join has nothing to pick between them:
tcping_probe_rtt_milliseconds * on (source, target, port) group_left (ip) tcping_target_address
The source label says which machine the probe was sent from. It defaults to
that machine's hostname, so several machines probing the same target land in
their own series instead of on top of each other. Use --source-label to name
them yourself:
tcping www.example.com 443 --alloy http://localhost:4318 --source-label parisThe whole statistics block you would normally see on exit is sent every 10
seconds, so a run that nobody is watching still reports it: the packet loss,
the minimum, average, maximum and mean deviation of the latency, the total
uptime and downtime, the longest streak of each and when it ran from and to,
when the last successful and unsuccessful probes landed, how many times the
hostname had to be looked up again and how often it answered from a different
address, and when the run started, how long it has been going and when it
ended. Times are sent as milliseconds since the epoch, since a metric can only
carry a number. Use --stats-interval to change the interval.
On the Alloy side you need an OTLP receiver pointed at Prometheus. A working one, along with a Prometheus, an InfluxDB and a Grafana dashboard you can start in one command, is in docs/observability.
export INFLUXDB_TOKEN=your-api-token
tcping www.example.com 443 --influxdb http://localhost:8086 --influxdb-org home --influxdb-bucket tcpingInstead of printing each probe, tcping writes it to InfluxDB v2 or v3 as line
protocol. Every probe writes one point, named after what was probed:
tcping_tcp, tcping_udp or tcping_http, tagged with the source, target,
port and protocol. All three hold success, rtt_ms, the address the target
resolved to in the ip field, and the successful and unsuccessful probe
counts. The address is a field rather than a tag because tags identify a
series: with -r or --resolve-every-probe a hostname that resolves somewhere
else mid-run would otherwise leave the old series behind and start a new one. A tcping_http point also carries the status code,
the connect, TLS handshake and first-byte timings and the days left on the
certificate, and a tcping_udp point carries the probe number, the size of
the reply and whether it was echoed back or refused.
The whole statistics block you would normally see on exit is written to
tcping_statistics every 10 seconds, so a run that nobody is watching still
reports it: the packet loss, the minimum, average, maximum and mean deviation
of the latency, the total uptime and downtime, the longest streak of each and
when it ran from and to, when the last successful and unsuccessful probes
landed, how many times the hostname had to be looked up again and how often it
answered from a different address, and when the run started, how long it has
been going and when it ended. Times are written as milliseconds since the
epoch, since a string field cannot be graphed. Use
--stats-interval to change the interval.
The source tag says which machine the probe was sent from. It defaults to
that machine's hostname, so several machines writing to the same bucket land
in their own series instead of on top of each other. Use --source-label to
name them yourself:
tcping www.example.com 443 --influxdb http://localhost:8086 \
--influxdb-org home --influxdb-bucket tcping --source-label parisThe API token can be given with --influxdb-token, or in the INFLUXDB_TOKEN
environment variable, which keeps it out of your shell history. The flag wins
if both are set.
To try this without setting a server up first, there is a ready made stack in docs/observability.
Flags can be given before or after the target, and with either one or two
dashes, so -c 5 and --c 5 are the same flag.
| Flag | Default | Description |
|---|---|---|
-h |
Show the available flags and exit | |
--version |
Show the version and exit | |
-u |
Check for updates and exit |
| Flag | Default | Description |
|---|---|---|
-c <n> |
no limit | Stop after <n> probes, regardless of the result |
-i <seconds> |
1 |
Interval between probes. Real number allowed, e.g. -i 0.5 |
-t <seconds> |
1 |
Time to wait for a response, in seconds. Real number allowed. 0 means infinite timeout |
-4 |
Only use IPv4 addresses | |
-6 |
Only use IPv6 addresses | |
-I <name|IP> |
Interface name or IP address to send the probes and the DNS lookups from |
Tip
Without specifying the -4 and -6 flags, tcping will randomly select an IP address based on DNS lookups.
| Flag | Default | Description |
|---|---|---|
-r <n> |
never | Retry resolving the target's hostname after <n> failed probes, e.g. -r 10 |
--resolve-every-probe |
Resolve the target's hostname before every single probe instead of only at startup. Takes precedence over -r and has no effect when the target is an IP address |
|
--dns-server <IP> |
system-wide | Custom DNS server to use. An IP and port combination is allowed, e.g. --dns-server 1.1.1.1:53 |
--dns-timeout <seconds> |
2 |
Time to wait for a DNS response, in seconds. Real number allowed. 0 means infinite timeout |
| Flag | Default | Description |
|---|---|---|
-D |
Show a timestamp for each probe | |
--no-color |
Do not colorize the output | |
--show-source-address |
Show the source IP address and port used for the probes | |
--failures-only |
Only show the failed probes. The successful ones are still counted | |
--no-stats |
Do not show the statistics when the program exits. Pressing the Enter key still shows them. No effect when the output goes elsewhere than the terminal | |
-v |
Show everything an HTTP(S) probe collected: the HTTP version, the TLS version and cipher, the certificate expiry and the connect, TLS and first-byte timings. For a UDP target, shows the probe's number and whether the reply carried it back, so a lost probe can be told apart from the rest. No effect on TCP targets |
| Flag | Default | Description |
|---|---|---|
-j |
Output in JSON format |
|
--pretty |
Prettify the JSON output. No effect without -j |
|
--csv <file> |
Store the output in a CSV file. The statistics go to the same name with a _stats suffix |
|
--csv-fixed-name |
Use the --csv filename as it is, without a date/time suffix, so repeated runs overwrite the same file |
|
--db <file> |
Store the output in a sqlite3 database, e.g. --db /tmp/tcping.db. Not available on Windows |
| Flag | Default | Description |
|---|---|---|
--alloy <URL> |
Send the results to a Grafana Alloy OTLP HTTP endpoint as metrics instead of printing them, e.g. --alloy http://localhost:4318 |
|
--influxdb <URL> |
Write the results to an InfluxDB v2 or v3 server as line protocol instead of printing them, e.g. --influxdb http://localhost:8086 |
|
--influxdb-org <org> |
InfluxDB organization to write to. Required with --influxdb |
|
--influxdb-bucket <bucket> |
InfluxDB bucket to write to. Required with --influxdb |
|
--influxdb-token <token> |
InfluxDB API token. Required with --influxdb. Can also be given in the INFLUXDB_TOKEN environment variable, which keeps it out of your shell history |
|
--stats-interval <seconds> |
10 |
How often to send the statistics to Alloy or InfluxDB. No effect without --alloy or --influxdb |
--source-label <name> |
hostname | Name this machine in the metrics sent to Alloy or InfluxDB, so that several machines probing the same target can be told apart |
| Flag | Default | Description |
|---|---|---|
--insecure |
Do not verify the server certificate when probing an https:// target. Useful for self-signed or expired certificates |
|
--udp-server |
Do not probe. Listen on the given host and port and echo every UDP datagram back to its sender, so a UDP probe pointed at this machine gets a reply |
Pull requests are welcome to solve bugs, add new features and to help with the open issues that can be found here
- Pick any issue that you feel comfortable with.
- Fork the repository.
- Create a branch.
- Commit your work.
- Add tests.
- Run the tests
go test ./...ormake testand ensure they are successful. - Create a pull request
Current number of open issues: .
Please make sure that your pull request only covers one specific issue/feature and doesn't handle two or more issues. This makes it simpler for us to review your pull request and helps keeping a clean git history.
To try your changes against a bad network, tools/netcond.sh can add latency,
drop a share of the packets or block one destination outright, and undo
everything it added afterwards:
sudo ./tools/netcond.sh delay 1.1.1.1 800ms 200ms 30
sudo ./tools/netcond.sh loss 1.1.1.1 40
sudo ./tools/netcond.sh clearDo you wish that tcping could do more? Or maybe you have faced a bug?
Please feel free to open an issue and if you can, you are welcome to open a pull request to contribute.
Although, keep in mind that unless you are fixing a really tiny issue, please ensure to first communicate your intention on an issue before starting your work.
If tcping is useful for you, consider sharing it with your network to extend its reach and help other people to also benefit from it.
Furthermore, you can support the project using the links below:
-
Buy me a coffee: "Buy Me A Coffee"
-
GitHub Sponsors: sponsor








