Skip to content

Network Debugging


A network failure rarely means “the network is down.” A machine can have a perfectly working network interface while having no usable route, a working route while DNS is broken, or working DNS while the application itself is not listening on the expected port.

The useful debugging skill is therefore not memorizing one magic command. It is narrowing the failure down layer by layer.

Debug From The Bottom Up

A practical Linux networking investigation can follow this path:

Network interface
       ↓
IP configuration
       ↓
Routing
       ↓
DNS
       ↓
Port / connection
       ↓
Application protocol

Each step answers a different question:

LayerQuestion
InterfaceIs the network interface present and up?
IPDoes the machine have the expected address?
RoutingDoes the kernel know where to send the packet?
DNSCan the hostname be resolved?
PortIs something reachable/listening on the expected port?
ApplicationDoes the service actually respond correctly?

The key is to avoid jumping immediately to the application. If the machine cannot resolve a hostname or has no route, debugging HTTP headers will not help.

Start With The Interface

First inspect the interfaces:

ip link

Look for the expected interface and whether it is operational.

Then inspect its addresses:

ip addr

You are checking basic facts such as:

Is the interface present?
Is it UP?
Does it have an IP address?
Does the address look correct?

For example, an interface might appear as:

2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> ...
    inet 192.168.1.20/24 ...

This tells you that the interface exists, is up, and has an IPv4 address.

If the interface is missing or down, there is little point debugging DNS or HTTP yet.

Check The Routing Table

Once the interface and IP address look correct, ask the kernel where it would send traffic.

ip route

You may see:

default via 192.168.1.1 dev eth0
192.168.1.0/24 dev eth0 proto kernel scope link src 192.168.1.20

The important entry for ordinary Internet access is usually:

default via 192.168.1.1 dev eth0

That means traffic for destinations without a more specific route should be sent to:

192.168.1.1

through:

eth0

Ask The Kernel Which Route It Will Use

Instead of merely reading the routing table, you can ask Linux to perform a route lookup:

ip route get 8.8.8.8

The result tells you which route, interface, source address, and next hop Linux would use for that destination.

This is particularly useful when a machine has multiple interfaces.

For example:

Network
   │
   ├── eth0 → network A
   │
   └── eth1 → network B

Looking at ip route tells you what routes exist.

ip route get answers the more practical question:

For this particular destination, what will Linux actually do?

Test Basic IP Connectivity

If the interface and routing look reasonable, test a known IP address:

ping 8.8.8.8

If this succeeds, you have evidence that:

interface
   ↓
IP configuration
   ↓
routing
   ↓
remote IP connectivity

are functioning at least well enough for ICMP traffic to reach the destination and return.

If it fails, don’t immediately conclude that the route is broken.

The destination might simply block ICMP.

That is why ping is a useful diagnostic signal, not a universal proof of connectivity.

What Does A Failed ping Actually Tell You?

Suppose:

ping 8.8.8.8

fails.

Possible causes include:

Interface down
    ↓
Wrong/missing IP configuration
    ↓
Missing route
    ↓
Gateway problem
    ↓
Remote network problem
    ↓
ICMP blocked

So the correct response is not:

“Ping failed, therefore the Internet is down.”

Instead:

“This particular ICMP test did not succeed. Now determine which part of the path failed.”

That distinction is important in real troubleshooting.

Test DNS Separately

Now try a hostname:

ping example.com

This test combines two things:

DNS resolution
     +
ICMP connectivity

That makes it less precise than testing an IP address directly.

Suppose:

ping 8.8.8.8

works, but:

ping example.com

fails with a name-resolution error.

That is a strong clue:

IP connectivity works
        ↓
DNS resolution is the likely problem

Now inspect the resolver configuration:

cat /etc/resolv.conf

You may see nameserver entries such as:

nameserver 192.168.1.1

You can also inspect /etc/hosts:

cat /etc/hosts

Remember that /etc/hosts can provide local hostname mappings without consulting DNS.

Use getent To Test Name Resolution

A useful Linux-native way to test name resolution is:

getent hosts example.com

If it returns an address:

93.184.216.34 example.com

the system was able to resolve the name through its configured name-service mechanism.

This can be more useful than testing with ping, because it isolates the name-resolution part.

For example:

getent hosts example.com

fails:

DNS/name resolution problem

while:

getent hosts example.com

works but:

ping example.com

fails:

Name resolution is probably fine.
Investigate connectivity or ICMP behavior.

Compare Hostname And IP Tests

This gives you a simple diagnostic pattern.

Start with:

ping 8.8.8.8

Then:

getent hosts example.com

Then:

ping example.com

The results help separate the problem:

IP test fails
    ↓
Look at interface, IP, route, gateway, connectivity

IP test works
    +
Name resolution fails
    ↓
Look at DNS

IP test works
    +
Name resolution works
    +
Hostname ping fails
    ↓
Look at ICMP / destination behavior

Don’t treat these as absolute rules. They are clues that narrow the search.

Check Whether A Port Is Reachable

Suppose the server is reachable, but an application is unavailable on TCP port 8080.

You need a different test.

A simple TCP connection test is:

nc -vz example.com 8080

The options mean:

-v
    verbose output

-z
    scan/check without sending application data

If the connection succeeds, you have evidence that TCP connectivity to that port exists.

If it fails, the problem may be:

No service listening
    ↓
Firewall filtering
    ↓
Wrong address/port
    ↓
Routing/connectivity problem

Again, the result narrows the possibilities rather than identifying one cause automatically.

Check Local Listening Ports

If the service is supposed to run on your own machine, inspect listening sockets:

ss -lnt

You might see:

LISTEN 0 128 0.0.0.0:8080 0.0.0.0:*

This tells you that something is listening on TCP port 8080.

If you expect a service on port 8080 but ss shows nothing listening there, the problem is local to the service or its configuration. There is no reason to start debugging DNS yet.

For a more useful view that includes the owning process:

sudo ss -lntp

This can show which process owns the listening socket.

127.0.0.1 Versus 0.0.0.0

One common source of confusion is the address a service binds to.

Suppose a Python service listens on:

127.0.0.1:8080

That means it is listening only on the local loopback interface.

A remote machine cannot normally connect to it through the machine’s external IP.

Compare:

127.0.0.1:8080

with:

0.0.0.0:8080

The first means:

local machine only

The second means:

listen on all IPv4 interfaces

This is an application-level configuration issue that often looks like a networking failure from the outside.

Test The Service Locally First

Suppose a web service should be available on port 8080.

Start by testing from the same machine:

curl http://127.0.0.1:8080

If that works, the application is at least responding locally.

Now try the machine’s actual IP:

curl http://192.168.1.20:8080

If this fails while the loopback request works, inspect how the service is bound.

For example:

sudo ss -lntp

If you see:

127.0.0.1:8080

you have found a likely explanation.

The service is alive, but it is not listening on the network interface you are trying to reach.

Test The Application Layer

Once lower-level connectivity works, use the protocol-specific tool.

For HTTP:

curl -v http://example.com

Now you can distinguish:

TCP connection succeeds
        ↓
HTTP request sent
        ↓
HTTP response received

If you receive:

HTTP/1.1 404 Not Found

the network worked.

The server received your request and deliberately returned an HTTP response.

That is very different from:

Connection refused

or:

Connection timed out

Those occur before a normal HTTP response is received.

Read The Error As A Layer Clue

Consider three different outcomes.

Connection Refused

curl: (7) Failed to connect ... Connection refused

This generally means the destination was reachable enough to reject the TCP connection, commonly because nothing is listening on that port.

Check:

ss -lnt

on the destination.

Connection Timed Out

A timeout is different.

It can indicate that packets are being dropped somewhere along the path, a firewall is silently filtering the traffic, or the destination is otherwise unreachable.

Now investigate:

ip route

and:

ping <destination-ip>

where appropriate.

You may also use:

nc -vz <destination> <port>

to test the specific TCP port.

HTTP Error

Suppose:

curl https://example.com/api

returns:

HTTP/1.1 500 Internal Server Error

This means the network path and HTTP exchange worked well enough for the server to return a response.

The problem has moved upward into the application/service layer.

This is an important debugging boundary:

Connection refused
    → TCP/service problem

Connection timeout
    → connectivity/filtering/path problem

HTTP 500
    → application/server problem

A Practical Debugging Flow

Suppose someone reports:

“The server cannot reach https://example.com.”

Don’t immediately start changing configuration.

Walk through the path:

    flowchart TD
    A["Start: Network request fails"] --> B["Check interface and IP<br/>ip link / ip addr"]
    B --> C["Check route<br/>ip route / ip route get"]
    C --> D["Test known IP<br/>ping"]
    D --> E["Test name resolution<br/>getent hosts"]
    E --> F["Test TCP port<br/>nc -vz"]
    F --> G["Test application<br/>curl -v"]
    G --> H["Inspect application response"]
  

Each step removes a category of possible problems.

A More Concrete Example

Imagine this command fails:

curl https://example.com

Start with:

ip link

The interface exists and is up.

Then:

ip addr

The machine has a valid IP address.

Then:

ip route

A default route exists.

Test an IP:

ping 8.8.8.8

This works.

Now test resolution:

getent hosts example.com

Suppose this fails.

At this point, don’t investigate TCP port 443.

The evidence already tells you:

Interface      ✓
IP             ✓
Route          ✓
IP connectivity ✓
DNS             ✗

The investigation has narrowed to name resolution.

That is the entire point of layered troubleshooting.

Another Example: DNS Works, HTTP Does Not

Suppose:

getent hosts example.com

works.

Then:

curl -v https://example.com

fails to connect.

Now the problem is no longer basic DNS.

You can test TCP:

nc -vz example.com 443

If that fails, investigate the TCP path and filtering.

If it succeeds, but curl fails during TLS negotiation, the problem has moved above basic TCP connectivity.

This is much more efficient than randomly changing DNS settings, routes, or interface configuration.

Debug A Local Service

Suppose you have a service expected at:

192.168.1.20:8080

First:

sudo ss -lntp

If nothing is listening on 8080, fix the service.

If you see:

127.0.0.1:8080

the service is local-only.

If you see:

0.0.0.0:8080

it is listening on all IPv4 interfaces, so investigate the network path or filtering if remote clients still cannot connect.

From another machine:

nc -vz 192.168.1.20 8080

Then:

curl http://192.168.1.20:8080

This gives you a clean progression:

Is a process listening?
        ↓
Can TCP reach it?
        ↓
Does the application respond?

Don’t Use ping As The Only Network Test

ping is useful because it tests IP-level reachability using ICMP.

But an application might be perfectly reachable even when ping fails.

For example:

ping example.com
    → ICMP blocked

curl https://example.com
    → HTTP works

There is no contradiction.

Different protocols can be treated differently by firewalls and hosts.

For application debugging, test the application protocol itself.

For HTTP:

curl -v https://example.com

For a raw TCP port:

nc -vz example.com 443

For IP-level reachability:

ping example.com

Each command answers a different question.

A Useful Command Sequence

When you don’t know where a network problem is, this is a practical starting sequence:

ip link
ip addr
ip route
ip route get 8.8.8.8
ping 8.8.8.8
getent hosts example.com
nc -vz example.com 443
curl -v https://example.com

You won’t always need every command.

The sequence moves from:

local configuration
        ↓
routing
        ↓
IP connectivity
        ↓
DNS
        ↓
TCP
        ↓
application

Stop as soon as the evidence identifies the layer that is failing.

What You Should Remember

Network debugging becomes much easier when you stop treating “the network” as one thing.

A failure can occur at several distinct layers:

Interface
   ↓
IP address
   ↓
Routing
   ↓
DNS
   ↓
TCP port
   ↓
Application protocol

Use the command that matches the question:

ip link
    → Is the interface up?

ip addr
    → Does it have the expected address?

ip route
    → Where will packets go?

ip route get
    → What route will Linux actually choose?

ping
    → Can IP/ICMP reach the destination?

getent hosts
    → Can the system resolve the name?

ss
    → What sockets are listening or connected?

nc
    → Can I establish a TCP connection to this port?

curl
    → Does the application protocol actually work?

The goal isn’t to run every command every time.

The goal is to move from one layer to the next until you have enough evidence to explain the failure.

What’s Next

The next topic steps back from troubleshooting individual connections and looks at something deeper: how the Linux kernel participates in networking. You’ll see where interfaces, IP, routing, sockets, and packet processing fit inside the kernel, and why commands such as ip, ss, and curl are really different views into the same underlying networking system.

Last updated on