CLI tool
heymaikol/network-doctor avatar
heymaikol/network-doctor

Network Doctor answers which layer broke, and admits when the evidence does not say

Network Doctor is a cross-platform network troubleshooting TUI that turns interface, DNS, TCP, TLS, HTTP, proxy, and path-MTU checks into one plain-English diagnosis.

399 stars30 forksGoApache-2.0

At a glance

What is it?
Network Doctor is a Go terminal UI that replaces a wall of ping, dig, and curl output with one plain-language verdict about where a connection fails. Its design bets on independent probe branches, five explicit row states, unprivileged sockets, and a JSON mode with stable exit codes for scripts.
Who is it for?
Network Doctor earns a place on a machine where intermittent connectivity is the problem and you have stopped believing ping, because it separates local link, naming, egress, and target path into independent branches and refuses to name a cause the evidence does not support.
Can I use it commercially?
Yes. Apache-2.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository received new commits within the last day.
What is it written in?
Mainly Go, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 3, 2026, and from our analysis. They are not legal advice.

Editorial analysis

A clean run means no obvious problem, not that everything works

The interface leads with an answer and then shows its work underneath. A finished run states in plain language whether a problem was found, which part of the connection it sits in, what that means, and what to try first. Below that, under a marker labelled Technical, come the diagnosis itself, the fix, the tool worth reaching for next, and the single line of evidence the verdict rests on, sitting above the checks that produced them. Selecting any other row gives you that row's own evidence and fix. `D` opens a check's complete details, `e` shows the causal explanation, `w` saves a report you can send to whoever can help, and `?` lists every shortcut.

Two honesty rules are built into the presentation. When the evidence does not establish a cause, the answer says so rather than guessing. And a clean run is defined narrowly: it means no obvious problem was found, not that every application will work. Each row also lands in one of five states, pass, warn, fail, skip, and a not-applicable state, and warn is explicitly never counted as a failure.

The worked example the project gives shows what the skip state is for. An office printer hostname no longer resolves. The DNS row fails, every check that depended on it is skipped rather than guessed at, and the verdict names the missing DNS record instead of blaming the printer.

Probes run as independent branches so one failure cannot hide a working path

The probe set is organised as a dependency graph with independent branches, and the stated reason is that an unrelated failure should never hide a working one. Direct egress, QUIC, proxy egress, public and encrypted DNS, and the selected target path each run on their own, and the unprivileged path-MTU check hangs off the connect rather than sitting in a separate sequence.

The rows fall into four groups. The Local group covers the interface and the Wi-Fi network. Egress covers internet over TCP, QUIC on UDP 443, and internet through the environment proxy. Naming covers DNS, public DNS, and encrypted DNS over DoH or DoT. The target path group covers TCP, path MTU, TLS, HTTP, HTTPS, and an SSH or SMTP banner read. The presence of a QUIC row on UDP 443 is not incidental: the module list carries the quic-go library, so that row exercises a real QUIC stack rather than a socket-level approximation.

The unprivileged constraint shapes the whole design. Even the path-MTU check and the LAN map use unprivileged sockets and bounded probes, so no part of the tool needs root. The exact pass conditions, the JSON cause values, and the method behind the unprivileged path-MTU probe are held in the reference documentation rather than in the overview.

The port argument picks the protocol rows, not just the destination

Five invocations cover most of what you would do by hand:

sh
netdoc                  # local interface, egress, proxy, public DNS, Wi-Fi
netdoc github.com       # DNS, TCP, TLS, HTTP diagnosis of one target
netdoc github.com:22    # the port selects the protocol rows (SSH banner)
netdoc --watch host     # catch intermittent failures
netdoc --json host      # structured report for scripts or bug reports

With no target the run stays on the local side, which is the fast path when the question is whether this machine has internet at all. With a hostname you get the naming, transport, and application layers for that one destination. Adding a port is the detail that surprises people: the port does not just change where the connection goes, it selects which protocol rows appear, so port 22 produces an SSH banner row rather than an HTTP one.

The same tool is meant for two different audiences, which is why the flags are split the way they are. The terminal interface is for live investigation. `--watch` re-runs continuously for faults that come and go, and `--json` produces a stable structured report with stable exit codes for scripts and for bug reports. Nothing about the two modes is a second implementation: the same diagnosis engine produces both, and the stated reason it runs on Linux, macOS, and Windows from one codebase is that the packaging is native on each rather than emulated.

Every Linux package ships a second binary the diagnosis never uses

Linux packages install two commands at the same version: the diagnosis binary itself and a simulator that exists for Challenge Mode. macOS and Windows get only the first. The Homebrew formula installs the diagnosis binary alone, and the guidance for also wanting the simulator on those platforms is to take a Linux package.

The container image exists to close that gap, and the Dockerfile is unusually insistent about what it is not. It is described as an execution layer and nothing else: it ships the same simulator the Linux packages ship, and that binary builds its networks out of the same unprivileged user, network, and mount namespaces it uses natively. There is no container simulation backend, no reduced-fidelity mode, and no second implementation to keep in step. macOS and Windows get a Linux kernel from Docker Desktop or Podman Machine, and the real simulator runs on it.

The version story is the other reason the file is written the way it is. Both binaries are built from one source tree with one version string, so the diagnosis binary the challenge runs is the one the image tag names. The simulator finds it as its sibling, which is why they share a directory and why nothing else named netdoc is on the path inside the image. The build cross-compiles for the target architecture from the build host, so no emulator runs the Go toolchain, and a local build with no version argument produces dev, which the comments call the truth about a local build.

Prebuilt packages are standalone, so nothing upgrades them for you

The Linux story splits by distribution, and the reason is a toolchain constraint rather than a packaging preference. Fedora stable uses the prebuilt release RPM, and the file is explicit that being prebuilt is what makes this work: the Go version limitation that prevents source builds on Fedora 43, 44, and 45 does not apply to a downloaded package. Fedora Rawhide uses a COPR repository that builds from source and publishes for Rawhide on x86_64 and aarch64 alone.

Everywhere else you take a downloaded package:

sh
sudo apt install ./network-doctor_X.Y.Z_linux_amd64.deb    # Debian, Ubuntu, Mint
sudo dnf install ./network-doctor_X.Y.Z_linux_amd64.rpm    # RHEL, Rocky, Alma
sudo apk add --allow-untrusted ./network-doctor_X.Y.Z_linux_amd64.apk    # Alpine

Those are standalone, and the consequence is stated without softening: dnf and apt will not pull the next version for you. The COPR repository upgrades normally, and so do the other two package managers that are not downloading a file. Homebrew Core carries a formula bottled for both platforms, so brew upgrade treats it like any other formula, and a release reaches the Scoop bucket as soon as it publishes, so scoop update picks it up like any other app. Upgrade paths, trust roots, and the Linux-only rule for the simulator all live in the installation document rather than in the overview.

Verification is attestation-based, and go install is the fallback

The installed command is not the project name, which trips people up once. The project is network-doctor and the binary is netdoc. To find out what you are actually running, the tool has its own version flag, and the release process is built around signed attestation: each release carries an attestation binding every artifact to the workflow run that produced it. The instructions for verifying one, along with building from a clone, are kept in the installation document under a download verification heading rather than in the overview.

For everyone else there is a prebuilt binary from the latest release, with Windows shipping as a zip and the rest as bare executables, and there is a source route that needs Go 1.27 or newer:

sh
go install github.com/heymaikol/network-doctor/cmd/netdoc@latest

That 1.27 floor is the same constraint that shows up in the Fedora packaging, where it blocks source builds on three stable releases. The module file declares go 1.27.0 and the container build stage uses a Go 1.27 Alpine image, so the floor is consistent across the source route, the packages, and the image, which means an older distribution's packaged Go is the thing to check before anything else.

The compile also sets cgo off, and the reason given in the build file is resolution: a cgo-enabled build resolves names differently, and the tool's own DNS findings would then be measuring the wrong thing.

Profiles and watch mode are the two capabilities the overview file finishes on

The capabilities section promises each feature a sentence in the overview and a full contract in the reference, and the visible file stops partway through the third item, mid-sentence, at drill-down tools. So two of the three are readable and one is not.

Service profiles compose ordinary runs into a single service-specific check with one aggregate verdict, and the important property is that every component keeps its own full report underneath, so an aggregate pass never hides which part passed. The built-in profile names are github, ssh, smtp, and web. Watch mode re-runs continuously and keeps a bounded incident timeline around each intermittent failure, running from the last working state to the recovery, with `i` to inspect an incident and `w` to save it. That timeline is the feature aimed at the hardest class of problem, since a single run of a fault that only appears sometimes proves nothing.

The cut-off drill-down entry is the one to look up before you rely on it. Its opening clause is that when a row is not proof enough you can run the real tools, which is the same promise the plain-language verdict makes when it names the next tool to reach for, but the rest of the sentence and the rest of the list are not in the file as published.

The test files are platform acceptance tests, and releases move every few days

The repository root is mostly tests, and their names describe the project's promises better than the overview does. There are three acceptance files covering the general case, macOS, and Windows separately, so the cross-platform claim is checked per platform rather than asserted. Then there is an architecture test, a check-script test, a container test, a zsh completion test, a host dependencies test, an install test, and a packaging test. Two of them are about the documentation rather than the code: a docs links test and a docs site test, which together mean a broken cross-reference fails the build. There is also a demo test driven by a tape file, and one test whose name is simply about em dashes, which is a small signal about how strictly the prose is held to a style.

The schema directory alongside a JSON schema library in the module list is the mechanism behind the stable JSON output, since a machine-readable report is only stable if something validates its shape. Release plumbing sits in a goreleaser configuration, linting in a golangci configuration, and the terminal interface is built on the Charm libraries for bubbles, bubbletea, and lipgloss, with quic-go for the QUIC probe and a terminal colour and OSC 52 stack in the indirect dependencies.

The release cadence is fast: v1.19.0 on 2026-10-02, v1.18.0 on 2026-09-30, and v1.17.9 on 2026-09-25, three tagged releases in eight days, with patch-level numbers in the sequence. The repository also carries agent instructions, a contributing guide, a code of conduct, a security policy, and a notice file beside the Apache-2.0 licence.

Editorial conclusion

Network Doctor earns a place on a machine where intermittent connectivity is the problem and you have stopped believing ping, because it separates local link, naming, egress, and target path into independent branches and refuses to name a cause the evidence does not support. It is a poor fit if you want a network monitor rather than a diagnosis, if you need root to go deeper than its unprivileged probes reach, or if you are on Fedora stable and expect packages to upgrade themselves. Before trusting it on a server, check three things yourself: that a clean run is read as no obvious problem found rather than as proof your applications work, that the JSON output and its exit codes are what your automation should branch on, and that a downloaded package will not update on its own unless you installed through the COPR repository, Homebrew, or the Scoop bucket.

Frequently asked questions

What does Network Doctor actually diagnose?

It runs probes grouped into four independent branches: local interface and Wi-Fi, egress including TCP, QUIC on UDP 443, and the environment proxy, naming with plain, public, and encrypted DNS, and the target path with TCP, path MTU, TLS, HTTP, HTTPS, and an SSH or SMTP banner. A finished run leads with a plain-language verdict and then shows the evidence under a technical marker.

Does Network Doctor need root access?

No. Even the path-MTU check and the LAN map use unprivileged sockets and bounded probes. The probes are also organised so an unrelated failure never hides a working one, and a row that cannot run because something it depends on failed is marked skipped rather than guessed at.

Can I use Network Doctor in a script?

Yes. It has a JSON mode for a structured report for scripts or bug reports, with stable JSON and exit codes. The terminal interface is for live investigation, and a watch flag re-runs continuously to catch intermittent failures while keeping a bounded incident timeline around each one.

How do I install Network Doctor on Fedora?

Fedora stable uses the prebuilt release RPM, because the Go version limitation that prevents COPR source builds on Fedora 43, 44, and 45 does not apply to a prebuilt package. Fedora Rawhide uses a COPR repository that builds from source. Note that downloaded packages are standalone, so dnf will not pull the next version for you, while the COPR repository upgrades normally.

What is the difference between network-doctor and netdoc?

network-doctor is the project name and netdoc is the installed command. You can also install from source with Go 1.27 or newer using go install, and releases carry a signed attestation binding each artifact to the workflow run that built it, with verification steps kept in the installation document.

Official sources

  1. heymaikol/network-doctor on GitHub
  2. License: Apache-2.0
  3. Project website
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/heymaikol-network-doctor.svg)](https://hysenlabs.com/projects/heymaikol-network-doctor)