Health check consistently failing

I have been having this problem for the last day. I have not been able to pass a health check since I signed up. I have ssh to validate connectivity and that was successful but when I deploy via my GitHub Action, I get nothing but this.

Hi… If you haven’t already, try the curl -i variant from within the SSH session and look at the exact response code, for the check’s particular URL path. It has to be 200 OK and not a redirect or the like.

The health check will not automatically follow any HTTP 301 or 302 redirect, so it will fail if it receives anything other than a 200 OK response.

Often people are unknowingly redirecting to https:, a login page, etc., and that (non-intuitively) counts as failure.

I performed both -i and -v variants:

curl -v http://localhost:8000/api/health/000/api/health/
* Host localhost:8000 was resolved.
* IPv6: ::1
* IPv4: 127.0.0.1
* Trying [::1]:8000…
* connect to ::1 port 8000 from ::1 port 58300 failed: Connection refused
* Trying 127.0.0.1:8000…
* Connected to localhost (127.0.0.1) port 8000
* using HTTP/1.x

GET /api/health/ HTTP/1.1
Host: localhost:8000
User-Agent: curl/8.14.1
Accept: */*

* Request completely sent off
< HTTP/1.1 200 OK
< Server: gunicorn
< Date: Sun, 16 Aug 2026 15:29:49 GMT
< Connection: close
< Content-Type: application/json
< Vary: Accept, origin
< Allow: GET, HEAD, OPTIONS
< X-Frame-Options: DENY
< Content-Length: 15
< X-Content-Type-Options: nosniff
< Referrer-Policy: same-origin
< Cross-Origin-Opener-Policy: same-origin
<
* shutting down connection #0
{“status”:“ok”}

curl -i http://localhost:8000/api/health/
HTTP/1.1 200 OK
Server: gunicorn
Date: Sun, 16 Aug 2026 15:14:03 GMT
Connection: close
Content-Type: application/json
Vary: Accept, origin
Allow: GET, HEAD, OPTIONS
X-Frame-Options: DENY
Content-Length: 15
X-Content-Type-Options: nosniff
Referrer-Policy: same-origin
Cross-Origin-Opener-Policy: same-origin

{“status”:“ok”}

This is definitely an “internal” Fly issue.

This looks like your app is not listening even from the local console, where there could not be networking things to get in the way. Was the above taken at machine start? If so, would you try shelling into a running instance, and trying again? If that works, it might suggest your app has a long start-up time, and the healthcheck gives up even though your app does start eventually.

In this forum we see this especially with Java apps, which seem to do some transpilation or cache warm-up, before the listener kicks in. But I suppose it could theoretically happen with any technology; the trick is either to make app boot more efficient, bump to a more powerful machine, or increase the healthcheck timeout.

Update

Ah! Hold up…

Could your app be listening only on IPv4? I wonder if the healthcheck expects the listener to attach to IPv6 as well.

Yes. It is not using IPv6.

Where would I configure that to “not” expect IPv6 for healthcheck? Or is that even possible

I even tried to restart my machine and it failed. I have recreated verifying that it is not machine specific and the health check still fails. I have verified that my DB and Django code is good. This seems like a problem with Fly’s edge proxy and my machine’s private network. I don’t know if I can fix that.

These come in over IPv4 last I looked, although there isn’t a lot of documentation about how they work.

My impression is that the Fly Proxy itself doesn’t perform the health checks and that they’re instead the responsibility of flyd (the local minder of the physical host machine) or one of its cohort of local daemons.


Personally, I would try disabling the health check entirely for a while. Also, ss -tnpl, from within an SSH session, is another classic debugging step.

If those both fail, then posting the output of fly m status -d and the full fly.toml would give people more to go on…

(As it stands, readers here in the community forum don’t even know the app name, etc.)

Well the reasoning for the health checks is I want to ensure everything is up and running before saying deployment was successful. Didn’t want my front-end trying to hit something that wasn’t active. I will try the additional debugging from ssh.

Add these if you can @dalythe :trophy:. If you do, please use Markdown formatting (code fences) so that the indentation is correct for readers.

@dalythe

I’m getting 400 Bad Request when I hit your app locally (no proxy involved) from the host where it’s running:

$ curl -s -i http://<local IP>:8000/api/health/ | head -4
HTTP/1.1 400 Bad Request
Server: gunicorn
Date: Mon, 17 Aug 2026 14:56:35 GMT
Connection: close

But it’s fine if I pass the host header:

$ curl -H "Host: <your appr>.fly.dev" -s -i http://<local IP>:8000/api/health/ | head -4
HTTP/1.1 200 OK
Server: gunicorn
Date: Mon, 17 Aug 2026 14:57:10 GMT
Connection: close

Make sure you configure your health checks with the proper Host header via http_service.checks.headers.

This INDEED was the problem. Thank you soooooo much. I had been debugging for days during my free time. My freak toml was wrong… geez.