Essential Network Tools
curl, netstat, nslookup, dig & telnet
LinkedIn Hook
Every backend engineer has stared at a broken API and thought: "Is it the server? The DNS? The firewall? My code?"
The difference between a junior who guesses and a senior who knows?
Five commands.
nslookup— Is DNS resolving correctly?dig— What do the actual DNS records say?curl— Is the API actually returning what I think it is?netstat/ss— Is my server even listening on that port?telnet/nc— Is that remote port open at all?These are the tools that let you point your finger at the exact layer where things are breaking. Network? DNS? Application? Firewall? You'll know in 60 seconds.
Lesson 7.3 is live — practical examples, annotated command output, and the cheat sheet you'll actually use on the job.
Read the full lesson → 03-other-tools.md
#Networking #curl #DNS #dig #InterviewPrep #BackendDevelopment #SoftwareEngineering #SystemDesign #DevTools
What You'll Learn
- How to use
nslookupfor quick DNS lookups on any OS including Windows - How to use
digfor deep, authoritative DNS inspection with full trace output - How to use
curlto test APIs, debug headers, authentication, and redirects - How to use
netstatandssto find what is listening on which port and why - How to use
telnetandncto verify a remote port is open before blaming the code - How to read the raw output from each tool and extract the signal from the noise
- Which tool to reach for at each layer of the debugging stack
Tool 1 — nslookup: DNS Lookup the Simple Way
What It Is
nslookup (Name Server Lookup) is a command-line tool for querying DNS records. It ships natively on Windows, macOS, and Linux, making it the most universally available DNS tool in your arsenal. It runs in one-shot mode (pass the domain as an argument) or interactive mode (enter nslookup alone and type queries).
Basic Usage
nslookup google.com
Annotated output:
Server: 192.168.1.1 <- which resolver answered your query (your router/ISP/configured DNS)
Address: 192.168.1.1#53 <- resolver IP and port (53 = DNS)
Non-authoritative answer: <- came from cache, not the authoritative nameserver directly
Name: google.com <- the canonical name you queried
Address: 142.250.80.46 <- the A record (IPv4 address) returned
Address: 142.250.80.78 <- Google often returns multiple IPs for load balancing
The "Non-authoritative answer" line is normal — it means your resolver had the answer cached. Only authoritative nameservers (Google's own DNS servers) give authoritative answers.
Query a Specific Record Type
nslookup -type=MX google.com # Mail exchange records — where does Gmail receive email?
nslookup -type=AAAA google.com # IPv6 address record
nslookup -type=TXT google.com # TXT records — SPF, DKIM, domain verification tokens
nslookup -type=NS google.com # Which nameservers are authoritative for this domain?
nslookup -type=CNAME www.github.com # Canonical name alias
MX record output:
Server: 192.168.1.1
Address: 192.168.1.1#53
Non-authoritative answer:
google.com mail exchanger = 10 smtp.google.com. <- priority 10 (lower = higher priority)
Query a Specific DNS Server
nslookup google.com 8.8.8.8 # ask Google's public resolver directly
nslookup google.com 1.1.1.1 # ask Cloudflare's resolver instead
This is useful for comparing what different resolvers return — helpful when diagnosing DNS propagation issues after a DNS change.
Interactive Mode
nslookup # enters interactive prompt
> google.com # query any domain
> set type=MX # change record type
> github.com # query MX records for GitHub
> server 8.8.8.8 # switch to a different resolver
> exit
nslookup vs dig: When to Use Which
| Situation | Use |
|---|---|
| Windows machine with no other tools | nslookup |
| Quick cross-platform DNS check | nslookup |
| Need full TTL, flags, authority section | dig |
| Tracing the full DNS resolution chain | dig +trace |
| Scripting / parsing DNS output | dig +short |
Tool 2 — dig: DNS Lookup the Authoritative Way
What It Is
dig (Domain Information Groper) is the professional DNS query tool. It gives you the complete picture: every section of the DNS response, TTLs, query timing, which server answered, and the full resolution chain. It is standard on macOS and Linux. On Windows, install it via BIND tools or use WSL.
Basic Usage
dig google.com
Fully annotated output:
; <<>> DiG 9.18.1 <<>> google.com <- dig version and what you queried
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 12345
;; ^-- status is the key field:
;; NOERROR = found it
;; NXDOMAIN = domain does not exist
;; SERVFAIL = resolver had an error
;; REFUSED = resolver refused the query
;; QUESTION SECTION:
;google.com. IN A <- what you asked: A record for google.com
;; ANSWER SECTION:
google.com. 255 IN A 142.250.80.46
;; ^domain ^TTL ^class ^type ^value
;; 255 seconds until this cache entry expires
;; AUTHORITY SECTION:
google.com. 52779 IN NS ns1.google.com. <- authoritative nameservers
google.com. 52779 IN NS ns2.google.com. <- for this domain
;; ADDITIONAL SECTION:
ns1.google.com. 21599 IN A 216.239.32.10 <- "glue records" — IPs of NSes
ns2.google.com. 21599 IN A 216.239.34.10 <- so you can reach the NS servers
;; Query time: 12 msec <- how long the lookup took
;; SERVER: 192.168.1.1#53(192.168.1.1) <- which resolver answered
;; WHEN: Tue Apr 22 10:00:00 UTC 2026
;; MSG SIZE rcvd: 111 <- response packet size in bytes
Query Specific Record Types
dig google.com MX # mail exchange records
dig google.com AAAA # IPv6 address records
dig google.com TXT # text records (SPF, DKIM, domain verification)
dig google.com NS # nameserver records
dig google.com SOA # Start of Authority — serial number, refresh intervals
dig google.com CNAME # canonical name alias
Short Output — Just the Answer
dig +short google.com
# 142.250.80.46
# 142.250.80.78
dig +short google.com MX
# 10 smtp.google.com.
dig +short google.com NS
# ns1.google.com.
# ns2.google.com.
# ns3.google.com.
# ns4.google.com.
+short is perfect for scripting — no headers, just the values.
Full Resolution Trace — Following the Chain
dig +trace google.com
This is one of the most powerful debugging tools in networking. It shows the complete path from root servers down to the authoritative answer:
. 518400 IN NS a.root-servers.net. <- Step 1: ask root servers
. 518400 IN NS b.root-servers.net. who handles .com?
com. 172800 IN NS a.gtld-servers.net. <- Step 2: .com TLD servers
com. 172800 IN NS b.gtld-servers.net. tell us who handles google.com
google.com. 345600 IN NS ns1.google.com. <- Step 3: Google's own
google.com. 345600 IN NS ns2.google.com. nameservers answer
google.com. 300 IN A 142.250.80.46 <- Step 4: final answer
;; Received 55 bytes from 216.239.32.10#53(ns1.google.com.) in 4 ms
Use +trace when: DNS changes are not propagating, you suspect a broken delegation, or you want to verify which nameserver is actually authoritative.
Query a Specific Resolver
dig @8.8.8.8 google.com # use Google's public DNS
dig @1.1.1.1 google.com # use Cloudflare's DNS
dig @9.9.9.9 google.com # use Quad9 DNS
dig @192.168.1.1 google.com # query your local router's resolver
Tool 3 — curl: HTTP from the Terminal
What It Is
curl (Client URL) is a command-line HTTP client. It lets you make HTTP requests, inspect headers, send JSON bodies, handle authentication, and follow redirects — all without a browser or Postman. If you work with APIs, curl is non-negotiable.
Basic GET Request
curl https://api.github.com/users/octocat
This sends a GET request and prints the response body. The output is the raw JSON (or HTML, or whatever the server returns).
Verbose Mode — See Everything
curl -v https://example.com
Annotated verbose output:
* Trying 93.184.216.34:443... <- TCP connection attempt
* Connected to example.com (93.184.216.34) port 443 <- TCP connected
* ALPN: offers h2,http/1.1 <- TLS: negotiating HTTP version
* TLSv1.3 (OUT), TLS handshake, Client hello <- TLS handshake starting
* TLSv1.3 (IN), TLS handshake, Server hello <- server responds
* SSL connection using TLSv1.3 / TLS_AES_256_GCM_SHA384 <- cipher suite agreed
* Server certificate: example.com <- cert details follow
* issuer: C=US; O=DigiCert Inc; CN=DigiCert TLS RSA SHA256 2020 CA1
* SSL certificate verify ok. <- cert is valid and trusted
> GET / HTTP/1.1 <- request line (method + path + version)
> Host: example.com <- Host header (required in HTTP/1.1)
> User-Agent: curl/7.88.1 <- curl identifies itself
> Accept: */* <- will accept any content type
< HTTP/1.1 200 OK <- response status line
< Content-Type: text/html; charset=UTF-8 <- response headers start with <
< Cache-Control: max-age=604800
< Content-Length: 1256
<!doctype html> <- response body
<html>...
Lines starting with * are connection info. Lines with > are your request. Lines with < are the server's response headers.
Just the Headers (HEAD Request)
curl -I https://example.com
Sends a HEAD request — gets response headers only, no body. Fast for checking Content-Type, Cache-Control, CORS headers, server type, and redirect targets.
HTTP/2 200
content-type: text/html; charset=UTF-8
cache-control: max-age=604800
etag: "3147526947"
last-modified: Thu, 17 Oct 2019 07:18:26 GMT
POST Request with JSON Body
curl -X POST \
-H "Content-Type: application/json" \
-d '{"name": "test", "email": "user@example.com"}' \
https://api.example.com/users
Breaking it down:
-X POST— override the HTTP method-H "..."— add a request header (use multiple-Hflags for multiple headers)-d '...'— the request body (automatically sets Content-Length)
Authentication Headers
# Bearer token (JWT, OAuth)
curl -H "Authorization: Bearer eyJhbGciOiJSUzI1NiJ9..." https://api.example.com/protected
# Basic auth (encodes username:password in Base64)
curl -u username:password https://api.example.com/protected
# API key as header
curl -H "X-API-Key: your-api-key-here" https://api.example.com/data
Follow Redirects
curl -L https://example.com # follow 301/302 redirects automatically
curl -Lv https://example.com # follow redirects AND show each step verbosely
Without -L, curl stops at the redirect response and shows you the 301/302. With -L, it follows the chain to the final destination.
Save Response to File
curl -o output.json https://api.example.com/data # save to named file
curl -O https://example.com/file.zip # save with server's filename
Useful One-Liners
# Check HTTP status code only (no body, no progress bar)
curl -s -o /dev/null -w "%{http_code}" https://example.com
# output: 200
# Show response time breakdown
curl -s -o /dev/null -w "dns:%{time_namelookup}s tcp:%{time_connect}s tls:%{time_appconnect}s total:%{time_total}s" https://example.com
# output: dns:0.023s tcp:0.045s tls:0.123s total:0.187s
# Silent mode (suppress progress bar, useful in scripts)
curl -s https://api.example.com/data | jq .
# Send a custom HTTP method
curl -X DELETE https://api.example.com/users/42
curl -X PATCH -H "Content-Type: application/json" -d '{"status":"active"}' https://api.example.com/users/42
Debugging Auth and CORS Issues
When an API call fails, run it with -v to immediately see:
- Whether the TLS handshake succeeded
- Which request headers you actually sent (not what you thought you sent)
- The exact response headers the server returned (look for
WWW-Authenticate,Access-Control-Allow-Origin,X-RateLimit-Remaining) - The full response body including error messages
curl -v -H "Authorization: Bearer TOKEN" https://api.example.com/protected 2>&1 | less
Piping to less lets you scroll through the full verbose output at your own pace.
Tool 4 — netstat / ss: What Is Listening on My Machine?
What They Are
netstat (Network Statistics) shows all active network connections, listening ports, and socket states. ss (Socket Statistics) is the modern replacement — faster, more features, and the preferred tool on modern Linux systems. On macOS, netstat is still common. On Windows, netstat is built in.
Core Commands
# Show ALL connections and listening ports, numeric (no hostname resolution)
netstat -an
# Linux: TCP listening sockets with process info
netstat -tlnp
# Modern Linux — prefer ss
ss -tlnp # TCP listening with process info
ss -an # all sockets, numeric
ss -tunp # TCP + UDP, numeric, with process
Flag breakdown for netstat -tlnp:
-t— TCP only-l— listening sockets only (not established connections)-n— numeric output (no DNS resolution — much faster)-p— show the process name and PID
Reading the Output
$ ss -tlnp
State Recv-Q Send-Q Local Address:Port Peer Address:Port Process
LISTEN 0 128 0.0.0.0:22 0.0.0.0:* users:(("sshd",pid=1234))
LISTEN 0 128 0.0.0.0:80 0.0.0.0:* users:(("nginx",pid=5678))
LISTEN 0 511 127.0.0.1:5432 0.0.0.0:* users:(("postgres",pid=9012))
LISTEN 0 128 0.0.0.0:3000 0.0.0.0:* users:(("node",pid=3456))
Reading each column:
- State — LISTEN (waiting for connections), ESTABLISHED (active connection), TIME_WAIT (closing), CLOSE_WAIT (remote end closed)
- Local Address:Port — what IP and port this machine is bound to.
0.0.0.0means all interfaces.127.0.0.1means localhost only (not externally reachable). - Peer Address:Port — the remote end.
*means not connected (for LISTEN state). - Process — which program owns this socket and its PID.
Common Use Cases
Which process is using port 3000?
ss -tlnp | grep :3000
# or
netstat -tlnp | grep :3000
# or on macOS/Linux:
lsof -i :3000
Is my web server actually listening?
ss -tlnp | grep :80
ss -tlnp | grep :443
How many connections to my database?
ss -tn dst :5432 | wc -l
Show all ESTABLISHED connections (not just listeners)
ss -tnp state established
Connection States You Will See
| State | Meaning |
|---|---|
LISTEN | Socket is open and accepting connections |
ESTABLISHED | Active connection in progress |
TIME_WAIT | Connection closing — waiting to ensure remote end got the FIN |
CLOSE_WAIT | Remote end closed; local end hasn't closed yet |
SYN_SENT | TCP SYN sent, waiting for SYN-ACK |
FIN_WAIT1/2 | Local end initiated close |
Finding a Process by Port — Platform Quick Reference
# Linux
ss -tlnp | grep :3000
lsof -i :3000
# macOS
lsof -i :3000
# Windows
netstat -ano | findstr :3000
# then look up the PID:
tasklist | findstr <PID>
# Windows PowerShell
Get-Process -Id (Get-NetTCPConnection -LocalPort 3000).OwningProcess
Tool 5 — telnet / nc: Is That Port Even Open?
What They Are
telnet and nc (netcat) let you open a raw TCP connection to a specific host and port. If the connection succeeds, the port is open and accepting connections. If you get "Connection refused," the port is closed. If it hangs, a firewall is blocking.
telnet for Port Testing
telnet example.com 80
Possible outcomes:
# Port is open:
Connected to example.com.
Escape character is '^]'.
(blank line — server is waiting for HTTP request)
# Port is closed:
telnet: connect to address 93.184.216.34: Connection refused
# Firewall blocking (hangs until timeout):
Trying 93.184.216.34...
(no output — waits 30+ seconds then times out)
Once connected, you can manually type raw protocol commands. For HTTP:
telnet example.com 80
GET / HTTP/1.0
Host: example.com
(press Enter twice)
The server will respond with a raw HTTP response. This is how developers tested HTTP before modern tools existed — and it still works perfectly for diagnosing what a server actually returns at the protocol level.
Testing SMTP Manually
telnet mail.example.com 25
# Connected — server greets you:
220 mail.example.com ESMTP Postfix
EHLO mycomputer.local
# Server lists supported commands:
250-mail.example.com
250-SIZE 10240000
250-STARTTLS
250 HELP
This confirms the mail server is reachable and responding to SMTP commands. Used to debug email delivery issues.
nc (netcat) — the Modern Alternative
nc is cleaner and more scriptable than telnet:
nc -zv example.com 80 # z = zero I/O (just test), v = verbose
# output: Connection to example.com 80 port [tcp/http] succeeded!
nc -zv example.com 443 # test HTTPS port
nc -zv example.com 5432 # test PostgreSQL port
nc -zv example.com 6379 # test Redis port
Port range scan:
nc -zv example.com 20-25 # test ports 20 through 25 (FTP, SSH, SMTP range)
With timeout (don't wait forever):
nc -zv -w 3 example.com 80 # give up after 3 seconds
Windows Alternative
Windows does not ship with telnet enabled by default and does not have nc. Use PowerShell:
Test-NetConnection -ComputerName example.com -Port 80
# output:
# ComputerName : example.com
# RemoteAddress : 93.184.216.34
# RemotePort : 80
# InterfaceAlias : Ethernet
# SourceAddress : 192.168.1.100
# TcpTestSucceeded : True <- True = port open, False = port closed/blocked
The Debugging Ladder
When something is not working, use this sequence to pinpoint the exact layer of failure:
1. Can you reach the host at all?
ping example.com
└─> No response = network/firewall issue at ICMP layer
2. Is the port open?
telnet example.com 80 (or nc -zv example.com 80)
└─> Connection refused = nothing listening on that port
└─> Timeout = firewall blocking TCP to that port
└─> Connected = port is open, problem is at application layer
3. Is DNS resolving correctly?
nslookup example.com
dig example.com
└─> NXDOMAIN = domain does not exist or DNS is broken
4. What is the HTTP response?
curl -v https://example.com
└─> Shows exact headers, status code, body, TLS details
5. What is listening locally?
ss -tlnp | grep :80
└─> Nothing there = your server is not running or listening on wrong port
Common Mistakes
-
Not using
curl -vwhen debugging API issues. The-vflag reveals the complete TLS handshake, every request header your client actually sent, and every response header the server returned. Auth failures, CORS errors, and content-type mismatches are all visible in the headers — but only if you look at them. Runningcurlwithout-vand only looking at the response body means you are missing the most diagnostic half of the conversation. -
Using
pingto test if a port is open.pingsends ICMP echo requests — it tests network-layer reachability, not TCP port availability. A server can be completely pingable and still have port 443 closed (or vice versa — a server can be ping-blocked by a firewall but have its ports fully open). Usetelnet example.com 443ornc -zv example.com 443to test port connectivity specifically. -
Running
netstatwithout-n. Without the-nflag, netstat attempts to resolve every IP address to a hostname using reverse DNS. On a server with many connections, this can take minutes and produce garbled output as slow DNS queries come back out of order. Always use-nfor numeric output. The same applies toss. If you genuinely need hostnames, add them back selectively after identifying the IPs you care about.
Interview Questions
1. How would you check which process is using port 3000 on Linux?
Use ss -tlnp | grep :3000 for a listening socket, or ss -tnp | grep :3000 for an established connection. The output includes the process name and PID in the Process column. Alternatively, lsof -i :3000 gives the same result with a different output format. On older systems, netstat -tlnp | grep :3000 works equivalently.
2. What is the difference between dig and nslookup?
Both query DNS, but dig provides significantly more information: full response sections (QUESTION, ANSWER, AUTHORITY, ADDITIONAL), TTLs for every record, query time, the exact server that answered, status codes (NOERROR/NXDOMAIN/SERVFAIL), and the +trace option to follow the full resolution chain from root servers down. nslookup has a simpler output and is cross-platform (ships natively on Windows). Use nslookup for quick checks anywhere; use dig when you need complete DNS visibility, TTL values, or trace-level debugging.
3. How would you test if port 443 is open on a remote server without a browser?
Run telnet example.com 443 or nc -zv example.com 443. If the connection succeeds, the port is open. "Connection refused" means nothing is listening there. A timeout means a firewall is blocking TCP traffic to that port. On Windows without these tools, use Test-NetConnection -ComputerName example.com -Port 443 in PowerShell and check the TcpTestSucceeded field.
4. How do you use curl to debug an API authentication issue?
Run curl -v -H "Authorization: Bearer TOKEN" https://api.example.com/protected. The -v flag shows the full request headers you sent (confirming the Authorization header is present and correctly formatted) and the full response headers from the server. Look at: the HTTP status code (401 = not authenticated, 403 = authenticated but not authorized), the WWW-Authenticate response header (tells you what auth scheme the server expects), and the response body error message. If the token looks correct in the request but still fails, check whether the server is returning a 401 or a redirect (which might strip the auth header).
5. What is the difference between netstat and ss?
ss is the modern replacement for netstat on Linux systems. ss reads directly from the kernel's socket tables via netlink sockets, making it significantly faster — especially on servers with thousands of connections where netstat (which parses /proc/net/tcp) becomes noticeably slow. ss also provides more detail about socket internals (like TCP retransmit counts and congestion window size) and has a more powerful filtering syntax. The flags are largely compatible: ss -tlnp is the equivalent of netstat -tlnp. On macOS and Windows, netstat is still the standard tool since ss is Linux-specific.
Quick Reference — Cheat Sheet
nslookup
| Purpose | Command |
|---|---|
| Basic A record lookup | nslookup google.com |
| MX (mail) records | nslookup -type=MX google.com |
| Query specific resolver | nslookup google.com 8.8.8.8 |
| TXT records (SPF/DKIM) | nslookup -type=TXT google.com |
| Interactive mode | nslookup (then type queries) |
dig
| Purpose | Command |
|---|---|
| Basic A record lookup | dig google.com |
| Just the answer (scriptable) | dig +short google.com |
| MX records | dig google.com MX |
| AAAA (IPv6) records | dig google.com AAAA |
| Full resolution trace | dig +trace google.com |
| Query specific resolver | dig @8.8.8.8 google.com |
| TXT records | dig google.com TXT |
curl
| Purpose | Command |
|---|---|
| Basic GET request | curl https://example.com |
| See all headers + TLS | curl -v https://example.com |
| HEAD request (headers only) | curl -I https://example.com |
| POST with JSON body | curl -X POST -H "Content-Type: application/json" -d '{"key":"val"}' https://api.example.com/endpoint |
| Bearer token auth | curl -H "Authorization: Bearer TOKEN" https://api.example.com/protected |
| Follow redirects | curl -L https://example.com |
| Get status code only | curl -s -o /dev/null -w "%{http_code}" https://example.com |
| Save to file | curl -o file.json https://api.example.com/data |
| Silent (no progress bar) | curl -s https://example.com |
netstat / ss
| Purpose | Command |
|---|---|
| All connections, numeric | netstat -an / ss -an |
| TCP listeners with process | netstat -tlnp / ss -tlnp |
| Who is on port 3000? | ss -tlnp | grep :3000 |
| All established connections | ss -tnp state established |
| TCP + UDP listeners | ss -tunlp |
| Process on port (Mac/Linux) | lsof -i :3000 |
telnet / nc
| Purpose | Command |
|---|---|
| Test if port is open | telnet example.com 80 |
| Port test (clean output) | nc -zv example.com 80 |
| Port test with timeout | nc -zv -w 3 example.com 80 |
| Test port range | nc -zv example.com 20-25 |
| Windows port test | Test-NetConnection -ComputerName example.com -Port 80 |
| Manual HTTP over telnet | telnet example.com 80 then type GET / HTTP/1.0 |
The Layer-by-Layer Debugging Stack
Symptom: "It's not working"
│
├─ ping example.com → Network reachable?
├─ nslookup / dig → DNS resolving correctly?
├─ telnet / nc → Port open? Firewall blocking?
├─ curl -v → HTTP response correct? Headers OK?
└─ ss -tlnp → Is the server actually listening locally?
Previous: Lesson 7.2 — traceroute → Next: Lesson 8.1 — The Complete Web Request Journey →
This is Lesson 7.3 of the Networking Interview Prep Course — 8 chapters, 32 lessons.