Fix guides / Replit: published app fails, health check or port errors
Your Replit app runs in Preview but publishing fails: fixing port and health check errors
The app runs fine in the Replit workspace Preview. You press Publish and it fails at the last step, keeps restarting, or goes live and immediately errors. Most of these failures come from five settings, none of which the Agent can see from inside Preview.
Symptoms
- Publishing fails at the final promote or health check step.
- Logs show the server started, but on
localhostor127.0.0.1. - Errors about a port you never configured, or about HTTP and HTTPS.
- The published app loads but API calls, login, or payments fail with undefined values.
Step 1: listen on 0.0.0.0 and the expected port
Published apps must listen on 0.0.0.0. Listening only on localhost works in some previews but not in production. Read the port from the environment when it is provided:
// Node / Express
const port = Number(process.env.PORT) || 5000;
app.listen(port, '0.0.0.0', () => console.log(`listening on ${port}`));
# Python / Flask with gunicorn (start command)
gunicorn --bind=0.0.0.0:5000 main:app
// Vite preview of a built front end
vite preview --host 0.0.0.0 --port 5000
Step 2: clean up the ports config
If your .replit file has a [[ports]] section, automatic port detection is turned off. Autoscale and Reserved VM deployments expose exactly one external port, and it must be 80, mapped to the port your server listens on:
[[ports]] localPort = 5000 externalPort = 80
Remove extra [[ports]] entries left over from dev servers, database tools, or old experiments. More than one exposed port, or the exposed one bound to localhost, makes publishing fail.
Step 3: add production secrets
Secrets set in the workspace do not automatically carry over to the published app. Open the Publishing pane and add every secret the app needs (database URL, Supabase keys, Stripe keys, API keys). Missing secrets cause errors about undefined values, failed API calls, login errors, and payment failures that never showed up in Preview.
Step 4: answer the health check within 5 seconds
Before going live, Replit sends a request to your home page. If it takes more than about five seconds, publishing can fail at the final step. Do not run migrations, seed data, or warm caches before calling listen. Start the server first and do slow work afterwards or on demand. A tiny route helps:
app.get('/health', (req, res) => res.send('ok'));
Step 5: do not redirect the health check to HTTPS
Many AI-written apps force HTTPS with middleware that reads X-Forwarded-Proto and defaults to http when the header is missing. Replit's internal health check calls your server directly without that header, gets redirected to HTTPS, and fails. Only redirect when the header is present and says http:
// Express
app.use((req, res, next) => {
if (req.headers['x-forwarded-proto'] === 'http') {
return res.redirect(301, `https://${req.headers.host}${req.url}`);
}
next();
});
Step 6: make sure the build and run commands are right
In the Publishing pane, check that the build command creates the production files (for example npm run build) and the run command starts a long-running server, not a one-off script. A process that exits is restarted again and again. Then open the Logs tab of the deployment and read the first error, not the last one.
Request a fix
Last updated 2026-10-19.