nslookup vs curl — DNS Resolution & Debugging
What they do
nslookup — DNS resolver only. Sends a query to a DNS server asking "what IP is this hostname?" Never connects to the target host.
curl — HTTP/HTTPS client. Makes a full application-layer request — fetches content, sends data, tests APIs. DNS resolution is just the first step.
Are both TCP-based?
No.
| Tool | Protocol | Transport |
|---|---|---|
nslookup |
DNS | UDP port 53 (default), falls back to TCP port 53 for large responses |
curl (HTTP) |
HTTP/1.1, HTTP/2 | TCP port 80 |
curl (HTTPS) |
HTTP over TLS | TCP port 443 |
curl (HTTP/3) |
HTTP/3 | UDP (QUIC) port 443 |
Full flow comparison
sequenceDiagram
participant You
participant DNS as DNS Server (8.8.8.8)
participant Server as api.example.com
Note over You,DNS: nslookup api.example.com
You->>DNS: UDP 53 — Query: A record for api.example.com?
DNS-->>You: UDP 53 — Answer: 93.184.216.34
Note over You,Server: curl https://api.example.com
You->>DNS: UDP 53 — Query: A record for api.example.com?
DNS-->>You: UDP 53 — Answer: 93.184.216.34
You->>Server: TCP SYN → port 443
Server-->>You: TCP SYN-ACK
You->>Server: TLS ClientHello
Server-->>You: TLS ServerHello + Certificate
You->>Server: HTTP GET /
Server-->>You: HTTP 200 OK + body
How each resolves a domain
curl — uses the OS resolver (getaddrinfo)
graph TD
A[curl api.example.com] --> B{"/etc/hosts?"}
B -->|match found| C[use that IP directly]
B -->|no match| D[/etc/resolv.conf nameserver]
D --> E[systemd-resolved stub 127.0.0.53]
E --> F[upstream DNS query UDP 53]
F --> G[IP returned to curl]
Respects: /etc/hosts, search domains, ndots, local stub resolver cache.
nslookup — bypasses OS resolver
graph TD
G[nslookup api.example.com] --> H["reads /etc/resolv.conf nameserver directly"]
H --> I[DNS query UDP 53 to upstream]
I --> J[IP returned]
K[/etc/hosts] -.->|IGNORED| G
L[systemd-resolved cache] -.->|BYPASSED| G
Never uses getaddrinfo(). Always sends raw DNS wire queries itself.
The resolver libraries
| Tool | Library | Behavior |
|---|---|---|
nslookup / dig |
Own DNS implementation | Raw DNS queries, no OS resolver chain |
curl (default) |
glibc getaddrinfo() |
Full OS resolver chain |
curl (with c-ares) |
c-ares | Async DNS, similar to nslookup — bypasses getaddrinfo |
Check which curl you have:
curl --version | grep AsynchDNS
# AsynchDNS without c-ares = threaded getaddrinfo (libc)
# AsynchDNS with c-ares = c-ares (bypasses OS resolver)
Debugging: curl works but nslookup fails
Cause 1 — Search domain expansion
curl myservice expands to myservice.corp.internal via search domains. nslookup myservice queries the bare name.
# Test with full name
nslookup myservice.corp.internal
Cause 2 — systemd-resolved caching the answer
curl → getaddrinfo() → 127.0.0.53 (stub, has cached answer) → works.
nslookup → upstream DNS directly → upstream is broken → fails.
# Force nslookup to use the stub resolver
nslookup myservice 127.0.0.53
# If this works, systemd-resolved is shielding curl from the broken upstream
Cause 3 — DNSSEC validation failure
Internal zone is not DNSSEC-signed. Upstream returns SERVFAIL when trying to validate. systemd-resolved has DNSSEC=allow-downgrade so curl is fine; nslookup queries upstream directly and gets the SERVFAIL.
# Disable DNSSEC validation for the query
dig myservice +cd +short
# If this works, DNSSEC is the issue
Cause 4 — IPv4/IPv6 (AAAA SERVFAIL) — most common
nslookup queries both A and AAAA records. Internal DNS handles A fine but returns SERVFAIL for AAAA (instead of the correct NOERROR + empty answer). curl's getaddrinfo() ignores the AAAA failure and uses the A record.
# Isolate which record type fails
nslookup -type=A myservice # should work
nslookup -type=AAAA myservice # will show SERVFAIL
Fix: Configure the internal DNS zone to return NOERROR (empty answer) for AAAA queries.
Workaround:
curl -4 myservice # force IPv4
nslookup -type=A myservice # query only A record
Quick diagnostic flow
graph TD
A[curl works, nslookup SERVFAIL] --> B{nslookup -type=A works?}
B -->|Yes, -type=AAAA fails| C["IPv6/AAAA issue on internal DNS"]
B -->|Both A and AAAA fail| D{nslookup myservice 127.0.0.53 works?}
D -->|Yes| E[systemd-resolved hiding upstream failure]
D -->|No| F{nslookup myservice.corp.internal works?}
F -->|Yes| G[Search domain expansion issue]
F -->|No| H{dig myservice +cd works?}
H -->|Yes| I[DNSSEC validation failure]
H -->|No| J[Upstream DNS server is broken]
Key rule
If
curl hostworks butnslookup hostfails — the problem is in DNS resolution, not connectivity. The two tools traverse different resolver paths. Narrow down which path is broken.
If nslookup works but curl fails — DNS is fine; the issue is TCP, TLS, firewall, or the application itself.