| name | debug-workflow-ports-issues |
| description | Diagnose and fix workflow port problems — DIDNT_OPEN_A_PORT, a workflow that times out waiting on a port, or a blank preview while the server seems to be running. Read this before restarting a failed workflow a second time. Not for code crashes, stack traces, or build failures; fix those first. |
Debugging workflow port issues
DIDNT_OPEN_A_PORT means the platform never saw the port the workflow waits on (waitForPort) become available. A crashed server is only one cause, and usually not the one. The others:
- the server bound a different port than
waitForPort (most common);
- the server bound
127.0.0.1 instead of 0.0.0.0, so it isn't detected;
- the port opened but was never forwarded (governed by
.replit [[ports]] and, for artifacts, the application router);
- the server crashed or failed to start.
In the first three the server is healthy and curl localhost:<port> works, so don't assume the code is broken until you've checked. Restarting just reruns the same command and reproduces the same outcome — after two failed restarts with DIDNT_OPEN_A_PORT, stop and diagnose.
Diagnose
-
Find the real port and confirm the server is up. Read the workflow's recent logs for the line the framework prints, e.g. Local: http://localhost:5173/, Listening on 0.0.0.0:8000, ready on port 3000. Then from a shell:
(ss -tlnp 2>/dev/null || netstat -tlnp 2>/dev/null) | grep LISTEN
curl -sS -o /dev/null -w "%{http_code}\n" http://localhost:<waitForPort>
-
Compare the bound port to waitForPort. This is the most common cause: the dev server came up on 5173/3000/8080 or a random high port while the workflow waits on something else.
-
Check the bind address. If the only listeners are on 127.0.0.1, the server won't be detected — it must bind 0.0.0.0.
-
Read .replit (and, for artifact services, the relevant artifact.toml) for the configured ports and any [[ports]] mappings.
Fix, in priority order
A. Port mismatch — make the server bind the waited-for port. Make the server honor the port it's given rather than chasing it. Most frameworks read $PORT or take a flag:
- Vite:
vite --port $PORT --strictPort (or server.port from process.env.PORT in vite.config). --strictPort fails loudly instead of silently moving to the next port.
- Next.js:
next dev -p $PORT.
- Express/Node:
app.listen(process.env.PORT).
- Python (Flask/uvicorn/etc.): bind
int(os.environ["PORT"]), host 0.0.0.0.
If you can't change the bind port, set waitForPort to the port the server actually uses (only valid for non-artifact workflows — see below).
B. Localhost bind — switch to 0.0.0.0. Add the framework's host flag (--host 0.0.0.0, --hostname 0.0.0.0, host="0.0.0.0").
C. Artifact services — fix the command, don't reconfigure. Artifact services (artifacts/<slug>: <service>) own managed workflows that inject PORT, BASE_PATH, and proxy routing. Don't call configureWorkflow to replace them and don't hardcode a port. The dev command in artifact.toml must respect the injected $PORT (e.g. vite --port $PORT --strictPort --host 0.0.0.0). After fixing it, restart with the WorkflowsRestart tool using the exact service name.
Adding a [[workflows]]/run entry to .replit (or calling configureWorkflow) for an artifact is almost always wrong: it creates a conflicting legacy workflow that omits the injected PORT/BASE_PATH/routing. Fix the artifact's dev command instead.
D. [[ports]] mappings must be complete across artifacts. Artifact ports are normally routed by the application router and need no [[ports]] entries. But if .replit already has any, every artifact service's port must be represented — a partial list is a known failure shape, where the unmapped artifacts fail to forward and time out with DIDNT_OPEN_A_PORT. Either all artifact ports are mapped, or none are.
E. Multiple ports / second listener. Some dev servers open more than one port (a separate HMR/inspector port, or helpers a frontend spawns). Make sure the one the workflow waits on is the served HTTP port and binds 0.0.0.0 on $PORT. If a framework opens an extra port you don't control, pin the served port with --port $PORT --strictPort so it can't drift.
After a fix, restart once and confirm readiness. Don't enter another restart loop.
When the server is healthy but the port still won't open
If the logs show it listening on the correct waitForPort on 0.0.0.0, curl localhost:<port> returns 200, and it still fails DIDNT_OPEN_A_PORT across restarts, this is a platform-side forwarding problem that more restarts or code changes won't fix. Tell the user what you verified — server up on the right port and address, but the platform isn't forwarding it — so they can escalate, and move on.