Fix: curl: (7) Failed to connect / (6) Could not resolve host / (28) Operation timed out
The Error
You run a curl command and get one of these:
Exit code 6 — DNS resolution failed:
curl: (6) Could not resolve host: api.example.comExit code 7 — Connection refused or unreachable:
curl: (7) Failed to connect to api.example.com port 443 after 12 ms: Connection refusedcurl: (7) Failed to connect to api.example.com port 443 after 30021 ms: Couldn't connect to serverExit code 28 — Timeout:
curl: (28) Operation timed out after 30000 milliseconds with 0 bytes receivedcurl: (28) Connection timed out after 10015 millisecondsExit code 60 — SSL certificate problem:
curl: (60) SSL certificate problem: unable to get local issuer certificateExit code 35 — SSL handshake failure:
curl: (35) error:0A000410:SSL routines::sslv3 alert handshake failureExit code 56 — Connection reset:
curl: (56) Recv failure: Connection reset by peerEach exit code points to a different root cause. Here’s how to diagnose and fix them all.
Why This Happens
curl follows a predictable sequence when making a request:
- DNS resolution — Translate the hostname to an IP address. If this fails, you get exit code 6.
- TCP connection — Connect to the IP on the specified port. If the server is down, the port is wrong, or a firewall blocks it, you get exit code 7. If the connection takes too long, exit code 28.
- TLS handshake — If it’s HTTPS, negotiate encryption. Certificate problems give exit code 60. Handshake failures give 35.
- Data transfer — Send the request and receive the response. If the connection drops mid-transfer, exit code 56.
Knowing which step failed tells you exactly where to look.
Quick Reference: curl Exit Codes
| Exit Code | Meaning | Common Cause |
|---|---|---|
| 6 | Could not resolve host | DNS misconfiguration, typo in hostname, no internet |
| 7 | Failed to connect | Server down, wrong port, firewall blocking |
| 28 | Operation timed out | Server too slow, network issues, timeout too short |
| 35 | SSL handshake error | TLS version mismatch, cipher incompatibility |
| 56 | Connection reset | Server closed connection, proxy interference |
| 60 | SSL certificate problem | Expired cert, missing CA bundle, self-signed cert |
Fix 1: Enable Verbose Mode First
Before trying anything else, run your command with -v (verbose). This shows every step of the connection process and tells you exactly where it fails.
curl -v https://api.example.com/endpointThe output shows DNS resolution, TCP connection, TLS handshake, and HTTP exchange. Look for the line where it stops or errors out.
For even more detail:
curl -v --trace-time https://api.example.com/endpointThis adds timestamps so you can see where time is being spent.
If you want full binary trace output:
curl --trace trace.log --trace-time https://api.example.com/endpointFix 2: DNS Resolution Failures (Exit Code 6)
You get Could not resolve host when curl cannot translate the hostname to an IP address.
Check if DNS works at all:
# Test DNS resolution directly
nslookup api.example.com
# or
dig api.example.com
# or on systems without dig
host api.example.comIf these also fail, the problem is your DNS configuration, not curl.
Check your DNS server:
# Linux
cat /etc/resolv.conf
# macOS
scutil --dns | head -20If /etc/resolv.conf is empty or points to an unreachable nameserver, fix it:
# Temporarily set Google DNS
echo "nameserver 8.8.8.8" | sudo tee /etc/resolv.confFlush the DNS cache:
# macOS
sudo dscacheutil -flushcache && sudo killall -HUP mDNSResponder
# Linux (systemd-resolved)
sudo systemd-resolve --flush-caches
# Windows
ipconfig /flushdnsCheck /etc/hosts for overrides:
grep api.example.com /etc/hostsIf the hostname is mapped to a wrong IP there, fix or remove the entry.
Docker containers often have DNS issues. The container might not inherit the host’s DNS config. Check inside the container:
docker exec -it mycontainer cat /etc/resolv.confIf it shows an unreachable nameserver, pass DNS explicitly:
docker run --dns 8.8.8.8 myimageOr in docker-compose.yml:
services:
app:
dns:
- 8.8.8.8
- 8.8.4.4Fix 3: Connection Refused (Exit Code 7)
Failed to connect means DNS resolved fine, but the TCP connection was rejected. The remote host actively refused the connection.
Check if the server is up:
# Is the port open?
nc -zv api.example.com 443
# Or with telnet
telnet api.example.com 443If the port is closed, the server process isn’t running or it’s listening on a different port.
Check if you’re hitting the right port:
# Default ports
# HTTP = 80, HTTPS = 443
# curl uses these automatically based on the URL scheme
# Explicitly specify a port
curl -v https://api.example.com:8443/endpointCheck if a firewall is blocking outbound connections:
# Test from the same machine
# If this works but your target doesn't, it's not a general firewall issue
curl -v https://google.com
# Check iptables (Linux)
sudo iptables -L -n | grep -i drop
sudo iptables -L -n | grep 443If the server is on localhost, check what’s actually listening:
# Linux
ss -tlnp | grep :8080
# macOS
lsof -i :8080
# Windows
netstat -ano | findstr :8080Related: If you’re hitting a local dev server that refuses connections, see Fix: ERR_CONNECTION_REFUSED on localhost.
Fix 4: Timeout Issues (Exit Code 28)
curl has two separate timeout mechanisms. Understanding the difference matters.
--connect-timeout — How long to wait for the TCP connection to be established. Default: 300 seconds (5 minutes).
--max-time — Total time allowed for the entire operation (connect + transfer). Default: no limit.
# Wait up to 5 seconds for connection, 30 seconds total
curl --connect-timeout 5 --max-time 30 https://api.example.com/endpointIf you’re timing out, the causes are:
- Server is slow or overloaded. Increase
--max-time. - Network latency is high. Increase
--connect-timeout. - A firewall is silently dropping packets (no RST, no response — just silence). This looks like a timeout instead of “connection refused.” This is common with cloud security groups and corporate firewalls. The same issue causes SSH connection timeouts.
- DNS resolution is slow. Add
--dns-serversor resolve manually first.
Test whether it’s a DNS or connection timeout:
# Bypass DNS by providing the IP directly
# First resolve the hostname
dig +short api.example.com
# Then connect using the IP with a Host header
curl -v --connect-to api.example.com:443:93.184.216.34:443 https://api.example.com/endpointIf the IP-based request works but the hostname-based one times out, your DNS is the bottleneck.
Force IPv4 or IPv6:
Sometimes curl tries IPv6 first, times out, then falls back to IPv4 — making everything slow. Force one protocol:
# Force IPv4
curl -4 https://api.example.com/endpoint
# Force IPv6
curl -6 https://api.example.com/endpointIf -4 is significantly faster, IPv6 is broken on your network. You can make this permanent:
# In ~/.curlrc
-4Fix 5: SSL Certificate Errors (Exit Code 60)
curl verifies SSL certificates by default. If verification fails, you get exit code 60.
See exactly what’s wrong with the certificate:
curl -vI https://api.example.com 2>&1 | grep -A5 "SSL certificate"Or use openssl for a detailed view:
openssl s_client -connect api.example.com:443 -showcerts </dev/null 2>/dev/nullUpdate your CA certificate bundle:
# Debian/Ubuntu
sudo apt update && sudo apt install -y ca-certificates
sudo update-ca-certificates
# RHEL/CentOS/Fedora
sudo yum install -y ca-certificates
sudo update-ca-trust
# Alpine (Docker)
apk add --no-cache ca-certificatesSpecify a CA bundle manually:
curl --cacert /etc/ssl/certs/ca-certificates.crt https://api.example.com/endpoint--insecure / -k skips verification entirely:
curl -k https://api.example.com/endpointThis disables all certificate checks. Use it only for debugging. Never use -k in scripts that download and execute code or handle sensitive data.
Corporate proxy intercepting HTTPS? Your company’s TLS inspection proxy replaces the server’s certificate with one signed by a corporate CA. You need to add that CA to your trust store. Ask IT for the root certificate, then:
sudo cp corporate-ca.crt /usr/local/share/ca-certificates/
sudo update-ca-certificatesRelated: For an in-depth guide on SSL certificate issues across all tools, see Fix: SSL certificate problem: unable to get local issuer certificate.
Fix 6: SSL Handshake Failures (Exit Code 35)
The TLS handshake fails when curl and the server can’t agree on a protocol version or cipher suite.
Force a specific TLS version:
# Force TLS 1.2
curl --tlsv1.2 https://api.example.com/endpoint
# Force TLS 1.3
curl --tlsv1.3 https://api.example.com/endpointSome older servers only support TLS 1.2. Some newer servers have dropped TLS 1.2 support. Verbose mode (-v) shows which version curl is trying.
Check which ciphers the server supports:
nmap --script ssl-enum-ciphers -p 443 api.example.comSpecify a cipher manually:
curl --ciphers 'ECDHE-RSA-AES128-GCM-SHA256' https://api.example.com/endpointCheck your curl’s TLS backend:
curl --versionThis shows which SSL library curl is compiled with (OpenSSL, LibreSSL, GnuTLS, NSS, etc.). Different backends support different TLS features. If you need TLS 1.3 and your curl uses an old OpenSSL, update it.
Fix 7: Connection Reset (Exit Code 56)
Connection reset by peer means the connection was established but the remote side closed it unexpectedly during data transfer.
Common causes:
- The server crashed or restarted during the request.
- A load balancer or proxy timed out. The server took too long to respond, and an intermediate proxy killed the connection.
- The request body is too large. The server rejected it before you finished sending.
- Rate limiting. The server dropped your connection because you sent too many requests.
- Protocol mismatch. You’re sending HTTP/2 but the server expects HTTP/1.1, or vice versa.
Force HTTP/1.1:
curl --http1.1 https://api.example.com/endpointForce HTTP/2:
curl --http2 https://api.example.com/endpointSome servers or proxies mishandle HTTP/2 connection multiplexing. Falling back to HTTP/1.1 often fixes unexplained resets.
Fix 8: Proxy Configuration
If you’re behind a proxy, curl needs to know about it. If you’re not behind a proxy but have proxy environment variables set, curl sends requests to a proxy that doesn’t exist.
Set a proxy explicitly:
curl --proxy http://proxy.corp.example.com:8080 https://api.example.com/endpoint
# SOCKS5 proxy
curl --socks5 127.0.0.1:1080 https://api.example.com/endpointEnvironment variables curl respects:
# Set proxy
export http_proxy=http://proxy.corp.example.com:8080
export https_proxy=http://proxy.corp.example.com:8080
# Bypass proxy for specific hosts
export no_proxy=localhost,127.0.0.1,.internal.example.comcurl checks http_proxy, https_proxy, HTTPS_PROXY, HTTP_PROXY, ALL_PROXY, and NO_PROXY. Note: http_proxy is lowercase by convention (uppercase is also supported).
Check if proxy variables are accidentally set:
env | grep -i proxyIf you see proxy variables you don’t need, unset them (see Fix: Environment Variable Is Undefined for more on environment variable issues):
unset http_proxy https_proxy HTTP_PROXY HTTPS_PROXY ALL_PROXYBypass proxy for a single request:
curl --noproxy '*' https://api.example.com/endpointFix 9: Follow Redirects
curl does not follow HTTP redirects by default. If the server returns a 301 or 302, curl just shows you the redirect response — it doesn’t follow it. This can look like an error when you expected content.
# Follow redirects
curl -L https://example.com/old-path
# Follow redirects with a limit (default is 50)
curl -L --max-redirs 5 https://example.com/old-pathWithout -L, you’ll get an empty response or HTML saying “Moved Permanently” and might think the server is broken.
Fix 10: Retry Failed Requests
For transient errors (server temporarily down, network blip, rate limiting), curl has built-in retry:
# Retry up to 3 times with exponential backoff
curl --retry 3 --retry-delay 2 --retry-max-time 60 https://api.example.com/endpoint--retry — Number of retries.
--retry-delay — Seconds between retries (doubles each time with --retry-all-errors).
--retry-max-time — Maximum total time for all retries.
By default, curl only retries on transient HTTP errors (408, 429, 500, 502, 503, 504) and connection failures. To retry on all errors:
curl --retry 3 --retry-all-errors https://api.example.com/endpointHandling rate limiting (HTTP 429):
When a server returns 429 Too Many Requests, it usually includes a Retry-After header. curl’s --retry respects this header automatically. But if you need to be more deliberate:
# Check the response headers first
curl -I https://api.example.com/endpoint
# Look for:
# HTTP/2 429
# Retry-After: 30If you’re scripting, handle 429 explicitly:
response=$(curl -s -o /dev/null -w "%{http_code}" https://api.example.com/endpoint)
if [ "$response" = "429" ]; then
echo "Rate limited. Waiting before retry..."
sleep 30
curl https://api.example.com/endpoint
fiStill Not Working?
VPN Is Interfering
VPNs can break curl requests in multiple ways: DNS resolution goes through the VPN’s nameservers (which may not resolve external hosts), routes change so traffic goes to a different gateway, and split tunneling may not be configured.
Test with the VPN disconnected. If curl works without the VPN, the issue is VPN routing or DNS.
curl Works But the Response Is Wrong
If curl connects successfully but returns unexpected content (HTML login page, empty response, 403 Forbidden):
# Send proper headers — many APIs require these
curl -H "Accept: application/json" \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "User-Agent: MyApp/1.0" \
https://api.example.com/endpointSome servers block requests without a User-Agent header. Some CDNs require specific headers or return a CAPTCHA page for bot-like requests.
Intermittent Failures with DNS Round-Robin
If the target hostname resolves to multiple IPs and only some are healthy, you’ll get intermittent failures. Check all resolved IPs:
dig +short api.example.comTest each IP directly:
curl -v --resolve api.example.com:443:93.184.216.34 https://api.example.com/endpoint
curl -v --resolve api.example.com:443:93.184.216.35 https://api.example.com/endpointIf one IP fails, the DNS record has a stale or dead entry. Report it to the service operator.
curl Hangs Indefinitely
If curl appears to hang with no output and no timeout, you probably haven’t set --max-time. By default, curl waits forever for data. Always set a timeout in scripts:
curl --connect-timeout 10 --max-time 60 https://api.example.com/endpointConnection Works from One Machine but Not Another
Compare the network path:
# Check routing
traceroute api.example.com
# Check if you're resolving to the same IP
dig +short api.example.com
# Check if your outbound IP is blocked
curl https://ifconfig.meDifferent machines may use different DNS servers, have different firewall rules, or egress from different IPs. Some APIs allowlist specific source IPs.
curl in a Docker Container Fails
Docker containers have their own networking stack. Common issues:
- No DNS: The container’s
/etc/resolv.confpoints to an unreachable nameserver. Fix with--dns 8.8.8.8. - No CA certificates: Minimal images don’t include CA bundles. Install
ca-certificates. See Fix: Docker Permission Denied for other common Docker issues. - Network mode: The container might be on a bridge network that can’t reach the external host. Try
--network hostfor debugging.
# Quick test inside a container
docker run --rm alpine sh -c "apk add --no-cache curl ca-certificates && curl -v https://api.example.com"HTTP/2 Problems
Some servers or proxies mishandle HTTP/2. Symptoms include random resets, incomplete responses, or hangs. Force HTTP/1.1 as a diagnostic step:
curl --http1.1 https://api.example.com/endpointIf HTTP/1.1 works but HTTP/2 doesn’t, the issue is server-side HTTP/2 implementation. Report it to the service operator or stick with HTTP/1.1.
Outdated curl Version
Old versions of curl may lack TLS 1.3 support, HTTP/2 support, or bug fixes for specific server configurations. Check your version:
curl --versionIf you’re on an older version (pre-7.70), consider upgrading:
# Ubuntu/Debian
sudo apt update && sudo apt install -y curl
# macOS (Homebrew)
brew install curl
# The Homebrew version is separate from Apple's system curl
# Use the full path or add to PATH:
export PATH="$(brew --prefix)/opt/curl/bin:$PATH"Large File Downloads Fail Midway
For large downloads, use -C - to resume interrupted transfers:
curl -C - -O https://example.com/large-file.tar.gzIf the server doesn’t support range requests, the resume will fail. In that case, use --retry to restart from scratch on failure:
curl --retry 5 --retry-all-errors -O https://example.com/large-file.tar.gzRelated: For SSL certificate issues in Git, Node.js, and Python, see Fix: SSL certificate problem: unable to get local issuer certificate. If you’re debugging a server that returns 502 behind nginx, see Fix: Nginx 502 Bad Gateway. For CORS errors when making requests from the browser, see Fix: Access to fetch has been blocked by CORS policy.
Related Articles
Fix: SSL certificate problem: unable to get local issuer certificate
How to fix 'SSL certificate problem: unable to get local issuer certificate', 'CERT_HAS_EXPIRED', 'ERR_CERT_AUTHORITY_INVALID', and 'self signed certificate in certificate chain' errors in Git, curl, Node.js, Python, Docker, and more. Covers CA certificates, corporate proxies, Let's Encrypt, certificate chains, and self-signed certs.
Fix: Docker Volume Permission Denied – Cannot Write to Mounted Volume
How to fix Docker permission denied errors on mounted volumes caused by UID/GID mismatch, read-only mounts, or SELinux labels.
Fix: SSH Connection Timed Out or Connection Refused
How to fix SSH errors like 'Connection timed out', 'Connection refused', or 'No route to host' when connecting to remote servers.
Fix: Nginx 403 Forbidden – Permission Denied or Directory Index Disabled
How to fix the Nginx 403 Forbidden error caused by file permissions, missing index files, SELinux, or incorrect root path configuration.