Tutorials

curl Error 7 and 56: Exit Codes Explained and Fixed

By Published 12 min read
curl Error 7 and 56: Exit Codes Explained and Fixed

TL;DR

curl exit code 7 is a connection failure, not a timeout. What codes 6, 7, 28 and 56 each prove about a request, and how to fix each.

On this page

What a curl Exit Code Actually Tells You#

curl does not fail vaguely. When a transfer does not complete, curl exits with a number that names the phase of the request that broke. Every request runs four phases in order: resolve the hostname, open a TCP connection, wait for an answer, transfer the body. Each phase owns an exit code, so the number is already a diagnosis.

The lab behind this article provokes each failure on purpose with system curl 8.17.0 (libcurl/8.17.0, Schannel), against a loopback origin, a reserved .invalid name, and an address in the documentation range RFC 5737 keeps off the public internet. All six checks passed on 2026-07-29, including the healthy baseline at exit 0.

curl exit codes by request phase, with message text recorded in the lab
Exitlibcurl namePhase that failedMessage curl printed
6CURLE_COULDNT_RESOLVE_HOSTResolvecurl: (6) Could not resolve host: databay-lab.invalid
7CURLE_COULDNT_CONNECTConnectcurl: (7) Failed to connect to 127.0.0.1 port 8128 after 2020 ms: Could not connect to server
28CURLE_OPERATION_TIMEDOUTWaitcurl: (28) Connection timed out after 2015 milliseconds
56CURLE_RECV_ERRORTransfercurl: (56) Recv failure: Connection was reset

Now the correction, the most repeated mistake about these numbers. Exit 7 is not a timeout. An answer still circulating on hosting forums says code 7 means the connection timed out, which sends you off raising limits that were never involved. curl's own libcurl error list defines 7 as CURLE_COULDNT_CONNECT, "Failed to connect() to host or proxy", and 28 as CURLE_OPERATION_TIMEDOUT.

The myth is sticky because the exit 7 message reports elapsed milliseconds, so it reads like a limit. It is not one. That figure is how long the attempt took, and it moves: the lab's closed-port check printed 2020 ms, and five re-runs of the identical command printed values between 2017 ms and 2038 ms. Deadlines do not wander.

Read curl's Own Diagnosis Before Changing Anything#

Two flags turn guessing into reading, both documented in the curl manual. -v prints the phase trace: every asterisk line is curl narrating what it is doing, and the last one before it gives up names the phase that failed. These are the asterisk lines from the lab's closed-port check:

curl -sSv -o out.txt http://127.0.0.1:8128/

*   Trying 127.0.0.1:8128...
* connect to 127.0.0.1 port 8128 from 0.0.0.0 port 64941 failed: Connection refused
* Failed to connect to 127.0.0.1 port 8128 after 2023 ms: Could not connect to server

Read it downward. curl printed Trying with an address, so resolution had already succeeded, and the connect attempt came back refused. There is no request line and no response header, because HTTP never started. That rules out every fix above the socket: URL paths, headers, cookies, authentication, TLS certificates and the server's application log cannot be involved in an exit 7, because none of them had happened yet.

-w gives the same conclusion in one line, which is what you want in a script or a health check. Each timing variable covers one phase, so a phase that never ran reports zero. Three runs against the same lab:

curl -sS -o out.txt -w 'exit=%{exitcode} dns=%{time_namelookup} connect=%{time_connect}\n' URL

healthy origin      exit=0 dns=0.000033 connect=0.000730
closed port         exit=7 dns=0.000051 connect=0.000000
unresolvable name   exit=6 dns=0.000000 connect=0.000000

On the healthy request both timers populate. On exit 7, DNS finished in 51 microseconds and connect time is flat zero, because no connection was ever established. On exit 6 even the DNS timer is zero. Find the first zero from the left and you have the failing phase.

Exit 7: the Connection Was Never Established#

Exit 7 has a narrow meaning: curl asked the operating system to open a TCP connection to one address and one port, and got back no connection. Nothing was ever open, so nothing HTTP-shaped happened. Read the address and port in the message first, every time. curl names exactly what it tried, that is not always what you typed, and a disagreement is the bug.

Causes, ordered by how often they turn out to be real.

  • Nothing is listening on that port. The service is stopped, crashed, still starting, or exited on a config error. This is the majority case, and the machine will tell you: ss -ltnp on Linux, netstat -ano on Windows.
  • The port is wrong. The URL names a port nothing serves, or the scheme and the port disagree, such as an https URL aimed at a plain HTTP listener. Outbound filtering has a signature: every https host fails while plain HTTP on port 80 keeps working, which points at your network or hosting plan closing outbound 443.
  • A firewall is rejecting rather than dropping. A reject rule answers immediately and produces exit 7. A drop rule stays silent and produces exit 28. The code tells you which kind of rule you met.
  • A container boundary sits in between. Inside a container, 127.0.0.1 means that container, not the host and not a sibling. Reach siblings by service name, and confirm the port is published, not merely exposed.
  • A proxy is set in the environment and curl is dutifully using it. Common enough, and invisible enough, to get its own section next.

If the rule refusing you belongs to a network you do not run, that is policy, and the way forward is its administrator rather than another attempt. ERR_CONNECTION_REFUSED is the browser's name for the explicit-refusal case, and its triage transfers directly.

The Proxy Variable That Explains Most of the Rest#

This is the cause almost nobody names, and in PHP applications, CI runners and container images it explains a large share of reported curl error 7. curl reads proxy settings from its environment with no flag involved. If http_proxy, HTTPS_PROXY or ALL_PROXY is set in the process running curl, the request goes to that proxy instead of your target, exactly as if you had passed --proxy. Read the official text for code 7 again: failed to connect to host or proxy. The number does not distinguish the two.

When that proxy is gone, wrong, or unreachable from where curl runs, the result is exit 7, and the message names the proxy rather than your target. That is the tell: if the host and port in the error are not in your URL, stop debugging the destination. The variable is usually set somewhere you are not looking: a Dockerfile ENV line, a systemd unit's Environment=, /etc/environment, a CI base image, or a shell profile your service manager never reads.

One precision detail catches people out: the HTTP variable is accepted in its lower case form only, http_proxy, while the others are read in either case, for CGI security reasons set out in curl's proxy environment reference. Two commands settle it:

env | grep -i _proxy
curl -v --noproxy '*' https://example.com/

If the second succeeds where the plain request failed, the environment is the cause, and the fix belongs where the variable is set, not in your code. NO_PROXY is the documented escape hatch and stronger than it looks: hostnames or domains, IP networks in CIDR notation since curl 7.86.0, and a single asterisk meaning every host. Listing internal names and loopback addresses there fixes a machine that reaches the internet but not its neighbors.

cURL Error 7 in PHP, WordPress and Containers#

PHP's curl extension is libcurl, so the numbers are identical. curl_errno() returns the error number for the last operation, and 7 there is exactly the failure above. WordPress relays it into Site Health and update checks as cURL error 7: Failed to connect. A socket did not open, and the CMS is quoting what it was handed.

Proving which layer owns the fault saves the most time, and one command does it: run the same request from a shell, as the same user, on the same host or in the same container.

sudo -u www-data curl -sSv --max-time 10 https://api.example.com/
echo "exit=$?"

Three outcomes. If the shell command fails the same way, the CMS is innocent and you are debugging the host's networking. If it succeeds as your login user but fails as the web server's user, the difference is that user's environment or a policy scoped to it. If it works on the host but fails inside the container, the boundary is the container.

The ranking differs in this lane. First, outbound filtering on the host or hosting plan, which stops the web server reaching port 443 while your SSH session is fine; that belongs to the provider's support queue, not to a plugin setting. Second, proxy variables the pool inherited, or WP_PROXY_HOST in wp-config.php, because PHP-FPM does not inherit your SSH session: read the pool config, the systemd unit and the image. Third, the container boundary, which is also why WordPress loopback requests for WP-Cron and the REST API fail. Fourth, SELinux with httpd_can_network_connect off on RHEL-family systems, which refuses outbound connections from the web server alone and logs nothing.

If this started right after a PHP upgrade, look at what else the upgrade replaced: a new base image brings a new environment, and its proxy variables or egress rules are likelier than the runtime.

Exit 6, 28 and 56: the Other Three Phases#

Exit 6 is DNS, and it happens before the network is touched. The lab's unresolvable name failed with every timer at zero. curl sent no packet to any server, so no firewall, port or certificate can be involved. Check the spelling, then the resolver in the context curl runs in: a name that resolves on your laptop but not on a server means split-horizon DNS, an internal zone, or a container using a different resolver. The browser-side version is ERR_NAME_NOT_RESOLVED.

Exit 28 is silence. The lab hit 28 against the unroutable documentation address with --connect-timeout 2: nothing answered, and the deadline expired. Two deadlines report as 28. --connect-timeout limits the connection phase, --max-time the whole transfer. A 28 during connect points at a packet-dropping firewall, a dead route, or an address nobody answers for; a 28 after the connection succeeded points at a slow or stuck server, and %{time_connect} tells you which. Raising the limit only helps the second. The browser equivalent is ERR_CONNECTION_TIMED_OUT.

Exit 56 proves you got through. The lab's origin sent headers, wrote part of the body, then reset the socket, and curl printed the partial body followed by curl: (56) Recv failure: Connection was reset. That sequence is the diagnosis: resolve, connect and request all succeeded, which eliminates every cause on the exit 7 list. Something tore down a working connection. In order: the server process died mid-response, often an out-of-memory kill or a worker restart; an idle timeout closed a pooled keep-alive connection your client then reused; a middlebox objected in flight; or plain HTTP was sent to a TLS port. Retrying once separates the keep-alive case, because a fresh connection works. The browser name is ERR_CONNECTION_RESET.

Using curl Through a Proxy Deliberately#

Plenty of readers meet these codes while pointing curl at a proxy on purpose, for regional QA or authorized collection. The flag is -x, also spelled --proxy, taking a scheme, a host and a port. Credentials belong in -U rather than in the URL, and socks5h:// rather than socks5:// makes the proxy resolve the hostname, which takes exit 6 out of your local resolver's hands.

The codes keep their meanings but change subject. With -x in play, an exit 7 names the first hop: curl could not connect to the proxy itself, usually a wrong port, a stopped gateway, or an endpoint your own network filters. Once -v shows the tunnel established, later failures belong to the destination. The proxy configuration generator produces the exact invocation for an endpoint and protocol, and the proxy checker confirms an endpoint is alive before you debug against a dead gateway.

One thing deserves saying plainly. A proxy changes the route a request takes. It does not grant access to anything. If a server answered with a refusal, a block page, a 403 or a 429, that was a deliberate decision by whoever runs it. The response is to stop and take it up with the site, or on a managed network with the administrator who wrote the rule. Switching exits, addresses or accounts to push refused traffic through is not a fix, and this guide will not help you do it. Turning a proxy or a VPN off to find out whether it caused your exit 7 is legitimate diagnosis. Turning one on to get past a rule that already said no is a different act.

Frequently Asked Questions

What does curl error 7 mean?
curl could not establish a TCP connection to the address and port shown in the message. The name resolved, but no connection came back: the far end refused it, or the attempt failed some other way before anything was open. In practice that is a service not listening, a wrong port, a firewall rejecting the connection, a container boundary, or a proxy set in the environment that curl dutifully tried to use.
Is curl error 7 a connection timeout?
No, and this is the most repeated mistake about these codes. Exit 7 is CURLE_COULDNT_CONNECT, a failure of the connect operation. The timeout is exit 28, CURLE_OPERATION_TIMEDOUT. The confusion comes from the message, which reports how many milliseconds the attempt took. That is elapsed time, not a limit: the same lab check printed 2020 ms on one run and values between 2017 ms and 2038 ms on five more, while a real deadline does not move.
How do I fix curl error 7 on port 443?
Confirm the destination really serves TLS on 443, then confirm your side can reach it. Run the request with -v and see whether curl gets past the Trying line. If nothing on the machine can open outbound 443 while port 80 still works, the network you are calling from is filtering it, which is common on shared hosting and corporate networks, and that change belongs to whoever administers it. If only one application fails, check its environment for proxy variables first.
What causes cURL error 7 in PHP or WordPress?
The same socket failure, surfaced through a CMS. PHP's curl extension is libcurl, so curl_errno returns the identical number. Usual causes: outbound filtering that blocks the web server but not your SSH session, a container boundary where 127.0.0.1 means the container itself, proxy variables inherited by PHP-FPM or set as WP_PROXY_HOST in wp-config.php, and SELinux with httpd_can_network_connect disabled. Reproduce the request in a shell as the web server's user to find out which.
What is the difference between curl error 6 and curl error 7?
They are consecutive phases. Exit 6 means the hostname never became an IP address, so curl sent nothing to anyone and DNS is the only thing to investigate. Exit 7 means the address was known and the connection to it failed, so DNS worked and the problem is the port, the listener, a firewall, or an intermediary. Reading which of the two you got saves you from debugging the wrong layer.
What does curl error 56 mean?
Exit 56 is CURLE_RECV_ERROR: the connection was established and then broke while curl was receiving data. The lab recorded it as Recv failure: Connection was reset. Because it proves the connect phase succeeded, it rules out everything on the exit 7 list. Look instead at a server process that died mid-response, an idle timeout closing a reused keep-alive connection, an appliance inspecting traffic in the path, or plain HTTP sent to a TLS port.
Does curl return the HTTP status code as its exit code?
No. A 404 or a 500 is a completed transfer, so curl exits 0 and writes the error page into your output. That surprises people writing scripts. Add the fail flag to turn HTTP errors into a non-zero exit, or print the status explicitly with the write-out option, and keep exit codes for transport problems, which is what they describe.
Will using a proxy fix curl error 7?
Rarely, and a proxy set by accident is itself a leading cause of exit 7 rather than a cure, since curl obeys proxy environment variables with no flag involved. A proxy changes the path a request takes; it does not grant access. If a server deliberately refused your request, that refusal is the answer, and the next step is the site owner or your network administrator. Turning an existing proxy off to test whether it is the trigger is the useful move.

Related reading

Ready to scale your data collection?

Join 8,000+ customers on Databay: 34M+ residential IPs across 200+ countries, pay as you go.

Pricing, order minimums, and traffic validity vary by product.