Fix Render “Port scan timeout reached, no open ports detected”
Explore with AI
Port scan timeout reached, no open ports detected. Bind your service to at least one port. If you don't need to receive traffic on any port, create a background worker instead. Render's deploy waits for your web service to open a port on host 0.0.0.0, and fails the deploy when it never sees one. Usually the server is bound to localhost or 127.0.0.1, ignores the PORT variable (10000 by default), or crashes or hangs before it listens. Bind to 0.0.0.0:$PORT, or deploy code that takes no traffic as a background worker.
On this page
- What “no open ports detected” means on Render
- Read the runtime log before changing anything
- Cause 1: the server binds to localhost or 127.0.0.1
- Cause 2: the server ignores PORT or picks a reserved port
- Cause 3: the app crashes or hangs before it listens
- Cause 4: a worker is deployed as a web service
- Cause 5: the Docker image has nothing to run
- Confirming Render found the port
- Stopping the port scan from failing again
- Finding the failed port scan with Polylane
- Common questions
The build log is green, the start command runs, and then the deploy just waits. Lines like ==> No open ports detected, continuing to scan... repeat in the log. Eventually the deploy gives up with “Port scan timeout reached, no open ports detected” and is marked as failed. If an earlier deploy succeeded, it keeps serving traffic. If this is the first deploy, the service never goes live.
Render is telling you it started your process and never saw it accept connections. The message also suggests a way out: if the service doesn’t need incoming traffic, it shouldn’t be a web service at all.
What “no open ports detected” means on Render
A Render web service is reachable at its onrender.com subdomain, but your process isn’t exposed to the internet directly. Render’s load balancer receives each request and forwards it to your service on one port. The web services docs set the contract: every web service must bind to a port on host 0.0.0.0 to serve HTTP requests, and the default expected port is 10000. If Render fails to detect a bound port, the deploy fails and shows an error in the logs.
Render’s boot and port binding tutorial describes how a deploy goes live. The build succeeds, Render runs your start command, and a port scanner waits for a TCP listener on 0.0.0.0. Only when it finds one does Render move on to health checks and route traffic to the new deploy. While it waits, it logs the “continuing to scan” line. When it stops waiting, the deploy fails with the message above.
The tutorial also explains why a server that works on your laptop can fail here. Render’s port scanner runs in a separate network namespace from your process. A server listening only on a loopback address is invisible to it.
Read the runtime log before changing anything
Open the failed deploy in the dashboard and read the lines between ==> Running '<your start command>' and the timeout. You can also stream them with the Render CLI:
render logs --resources srv-abc123 --tail
Look for three things:
- Does your app print a listen line at all? No output after the start command points at a crash, a hang or a Docker image with nothing to run.
- Which address does it name?
localhost,127.0.0.1or[::1]is a loopback bind. - Which port does it name? Compare it with
PORT, which is10000unless you’ve set it yourself.
The tutorial gives a quick way to read the listen line:
Server listening on http://localhost:3000: loopback and a port other thanPORT, so both are wrong.Server listening on http://0.0.0.0:3000: right interface, port other thanPORT.Server listening on http://localhost:10000: right port, loopback interface.
Then match your case to one of the causes below. They’re ordered by how often people hit them.
Cause 1: the server binds to localhost or 127.0.0.1
How to tell it’s yours: your app logs a listen line, and the address in it is localhost, 127.0.0.1 or the IPv6 loopback [::1]. A typical log:
==> Running 'npm start'
Server listening on http://localhost:3000
==> No open ports detected, continuing to scan...
==> No open ports detected, continuing to scan...
When Render can see the port on loopback, the timeout line names it. Deploy logs posted in GitHub issues show this form:
Port scan timeout reached, no open ports detected on 0.0.0.0. Detected open ports on localhost -- did you mean to bind one of these to 0.0.0.0?
Render’s tutorial names the usual sources: the Rails default, some Node servers in dev mode, and some Go HTTP libraries. It calls out an IPv6 version too: Puma can print Listening on tcp://[::1]:3000, which the scanner doesn’t see.
Fix: bind to 0.0.0.0 on PORT. Use the idiom for your server:
- Node with Express. Read
PORTand pass the host:const express = require("express"); const app = express(); const port = process.env.PORT || 10000; app.listen(port, "0.0.0.0", () => { console.log(`Listening on 0.0.0.0:${port}`); }); - Python with gunicorn. On Render’s native Python runtime, the default environment variables include
GUNICORN_CMD_ARGSset to--preload --access-logfile - --bind=0.0.0.0:10000. A plain start command such asgunicorn myapp.wsgibinds correctly. If you set the bind yourself, in a flag or ingunicorn.conf.py, keep it on all interfaces:gunicorn myproject.wsgi -b 0.0.0.0:$PORT - Python with uvicorn. Render’s FastAPI guide uses this start command:
uvicorn main:app --host 0.0.0.0 --port $PORT - Flask’s built-in server. Pass the host and port explicitly:
import os port = int(os.environ.get("PORT", 10000)) app.run(host="0.0.0.0", port=port) - Rails with Puma. Bind in the start command:
bundle exec rails server -b 0.0.0.0 -p $PORT - Go with net/http. Give the full address, with the host:
port := os.Getenv("PORT") if port == "" { port = "10000" } log.Fatal(http.ListenAndServe("0.0.0.0:"+port, nil))
Before pushing, run the same start command locally with PORT=10000 and check the listen line names 0.0.0.0:10000.
Cause 2: the server ignores PORT or picks a reserved port
How to tell it’s yours: the listen line shows 0.0.0.0, but the port is hardcoded, or it’s one of Render’s reserved ports.
The web services docs recommend binding your HTTP server to the port in PORT. Its default value is 10000 for all web services, and you can override it in the Render Dashboard. Render says that if you bind to a different port, it is usually able to detect and use it. Usually isn’t always, and the tutorial’s examples show a hardcoded 3000 among the failures. Binding to PORT removes the guesswork.
Three ports are reserved by Render and can’t be used: 18012, 18013 and 19099.
Watch out for a service that opens several listeners. Render forwards public traffic to only one HTTP port per web service. The tutorial shows the log for that case:
==> Detected service running on port 10000
==> Detected service running on port 8080
That isn’t a failure on its own, but make sure the public HTTP server is the one on PORT.
Fix: let PORT drive the port.
- Remove hardcoded ports from code and start commands. Read
PORTinstead, as in the snippets above. - If you need a specific port, set the
PORTenvironment variable on the service to that number. Render’s environment variables reference describesPORTas the port your HTTP server binds to. - Move any listener off
18012,18013and19099. - Keep extra listeners, such as a metrics or admin endpoint, on other ports, and keep the public server on
PORT.
Cause 3: the app crashes or hangs before it listens
How to tell it’s yours: after ==> Running '...' the log shows a stack trace, a missing environment variable, or a database error, then no listen line. Or the log shows output from something that never finishes, such as a migration or a console.
The tutorial gives two examples of a start command that never reaches the server:
==> Running 'rails console'
Loading production environment (Rails 7.1.0)
==> No open ports detected, continuing to scan...
==> Running 'rails db:migrate && rails server'
==> Migrating database
==> No open ports detected, continuing to scan...
In the first, the start command is an interactive console that waits for input forever. In the second, the server only starts after the migration, so a slow migration holds the port closed.
Fix: start the server, and only the server.
- Fix the error at the end of the log. Render’s troubleshooting guide lists the usual ones: missing environment variables, an invalid start command, and missing modules.
- Move one-off work into the pre-deploy command. The deploys docs say it runs after the build and before the start command, on a separate instance from your running service. In a Blueprint:
The pre-deploy command is available for paid web services, private services and background workers.services: - type: web name: api runtime: ruby buildCommand: bundle install preDeployCommand: bundle exec rails db:migrate startCommand: bundle exec rails server -b 0.0.0.0 -p $PORT - Make the start command a long-running server. A REPL, a one-off script or
datewill never open a port.
Cause 4: a worker is deployed as a web service
How to tell it’s yours: the code is a queue consumer, a bot, a scheduler or another loop that never serves HTTP. It runs fine, logs its own work, and the deploy still fails the port scan.
The message names this case itself: if you don’t need to receive traffic on any port, create a background worker instead. Render’s background workers docs describe them as services that run continuously, like a web service or a private service, but don’t receive any incoming network traffic. They usually poll a task queue, with Celery, Sidekiq, BullMQ, Asynq or similar.
The private services docs draw the line between the two internal types:
- If the service binds to at least one port and receives private network traffic, create a private service. It isn’t reachable from the public internet and gets no
onrender.comsubdomain. - Otherwise, create a background worker. Workers can make network requests but can’t receive them.
Fix: deploy it as the right service type.
- Create a new Background Worker in the dashboard from the same repo and branch, with the same build and start commands and environment variables.
- Or declare it in your Blueprint with
type: worker:services: - type: worker name: queue-worker runtime: python buildCommand: pip install -r requirements.txt startCommand: celery -A tasks worker --loglevel=info - If other services call it over the private network, use
type: pservand bind it to at least one port. - Once the new service is healthy, delete the old web service so it stops failing deploys.
Cause 5: the Docker image has nothing to run
How to tell it’s yours: the service uses the Docker runtime, the image builds, and then there’s no application output at all.
The troubleshooting guide says a Dockerfile must include a CMD or ENTRYPOINT directive, because Render uses one of them to run your app. Without both, the deploy might appear to hang indefinitely. The Docker docs also describe a Docker Command field under Advanced that replaces the Dockerfile’s CMD. A stale or wrong value there runs the wrong thing, or nothing that listens.
Fix: give the container a server to start.
- Add a
CMDthat starts the server on0.0.0.0andPORT. The exec form passes signals to your app:FROM node:20-alpine WORKDIR /app COPY package*.json ./ RUN npm ci COPY . . CMD ["node", "server.js"] - Check the Docker Command field in the service settings. Leave it empty to use the Dockerfile’s
CMD, or set the full command. For several commands, Render’s docs pass them to/bin/sh -c:/bin/sh -c python manage.py migrate && gunicorn myapp.wsgi:application --bind 0.0.0.0:10000 - In a Blueprint, the same setting is
dockerCommand, which defaults to the Dockerfile’sCMD.
Confirming Render found the port
Redeploy and read the log again. Render’s tutorial shows the lines you want after the fix:
==> Detected service running on port 10000
==> Your service is live 🎉
Then call the service from outside:
curl -sS -o /dev/null -w "%{http_code}\n" https://your-service.onrender.com/
A 200 (or whatever your root route returns) means the load balancer can reach your process. If the deploy now finds the port but still fails, the problem has moved to health checks. Render’s health checks docs say a default TCP check passes when the instance accepts the connection within five seconds, and an HTTP check passes on a 2xx or 3xx response within five seconds.
Stopping the port scan from failing again
- Bind to
0.0.0.0:$PORTeverywhere. Put it in the code or the start command, never in a local-only config file. - Test the exact start command locally with
PORT=10000, and check the listen line before you push. - Keep the start command to one long-running server. Migrations go in the pre-deploy command.
- Pick the service type on purpose. Web services for public HTTP, private services for internal traffic, background workers for everything that only makes outbound calls.
- Turn on deploy failure notifications. Render’s notifications can email you or post to Slack when a build or deploy fails, so a failed port scan on an auto-deploy doesn’t go unnoticed.
Finding the failed port scan with Polylane
Once you connect a Render account, Polylane queries its logs and metrics directly and turns Render alerts into issues. It investigates each one and cites the log line behind every claim, such as the listen line that names 127.0.0.1.
Running on Render? See how Polylane monitors Render in production.
Common questions.
What port does Render expect a web service to use?
Render sets PORT to 10000 for every web service by default, and you can override it as an environment variable in the dashboard. Your server should read PORT and bind to host 0.0.0.0. Render is usually able to detect a different port, but binding to PORT is the documented recommendation.
Why does my app say it's listening but Render finds no open port?
It is almost always listening on a loopback address such as localhost, 127.0.0.1 or [::1]. Render's tutorial explains that its port scanner runs in a separate network namespace from your process, so a loopback listener is invisible to it. Change the bind address to 0.0.0.0 and redeploy.
Which ports can't I use on Render?
Render reserves ports 18012, 18013 and 19099, and they can't be used. A service can have at most 75 open ports. Render forwards public traffic to only one HTTP port per web service, so bind the public server to PORT and use any other ports for private network traffic.
My service doesn't serve HTTP. How do I stop this error?
Deploy it as a different service type. Render's docs say a background worker runs continuously but receives no incoming network traffic, which suits queue consumers such as Celery, Sidekiq or BullMQ. If other services need to call it over the private network, create a private service, which must still bind to at least one port.
Does gunicorn on Render bind to 0.0.0.0 by default?
On Render's native Python runtime, Render sets GUNICORN_CMD_ARGS to --preload --access-logfile - --bind=0.0.0.0:10000, so a plain gunicorn start command binds correctly. If you pass your own --bind, make it 0.0.0.0:$PORT. A bind to 127.0.0.1 in a config file or flag leads straight to this error.
Can a health check cause “no open ports detected”?
No. The port scan happens before health checks matter, and Render's tutorial shows the scanner waiting for a TCP listener first. Once a port is found, a failing HTTP health check is a different problem. Render counts an HTTP check as passing when it gets a 2xx or 3xx response within five seconds.
How do I run migrations without blocking the port scan?
Move them into the pre-deploy command, which runs after the build and before the start command on a separate instance. The start command should then only start the server. The pre-deploy command is available for paid web services, private services and background workers.
Sources
- Web Services: port binding (Render Docs)
- Background Workers (Render Docs)
- Private Services (Render Docs)
- Private Network: port restrictions (Render Docs)
- Default Environment Variables (Render Docs)
- Docker on Render (Render Docs)
- Deploying on Render: deploy steps (Render Docs)
- Health Checks (Render Docs)
- Troubleshooting Your Deploy (Render Docs)
- Deploy a FastAPI App (Render Docs)
- Your First Render Deploy (Render Docs)
- Render CLI reference (Render Docs)
- Blueprint YAML reference (Render Docs)
- Notifications (Render Docs)
- Boot and port binding (Render Tutorials)
- Polylane documentation
- Polylane full content
Boris Tane is the founder of Polylane. He previously founded Baselime, observability for the future of the cloud, which Cloudflare acquired. At Cloudflare he built and led the Workers observability team.
Related
- How to Monitor a Django App on Render
Monitor a Django app on Render: JSON logs, a real health check, failure alerts, log and metrics streams, OpenTelemetry under Gunicorn and Celery checks.
- Fix Railway “Application failed to respond” (502)
Why Railway returns “Application failed to respond” with a 502, how to tell a wrong host, port or target port from a crash or overload, and how to fix each.
- Fix “Durable Object reset because its code was updated”
Why a deploy makes Cloudflare Durable Objects throw “reset because its code was updated”, and how to retry safely, keep clients connected and lose no state.
- Kubernetes CrashLoopBackOff: causes and fixes
What CrashLoopBackOff means in Kubernetes, how to read the exit code and events behind it, and the fix for each common cause, from bad config to failing probes.
- Kubernetes OOMKilled (exit code 137): causes and fixes
Why Kubernetes reports OOMKilled with exit code 137, how to tell a limit kill from a node eviction or a plain SIGKILL, and how to size memory so it stops.