EthioDeploy/Docs
HomePricing
Overview

Overview

  • Getting started
  • How deploys work

Frameworks

  • Framework guides

Configuration

  • Web services & background workers
  • Environment variables
  • Addons
  • Database & Redis tools
  • Branch deployments
  • Custom domains
  • Analytics

Domains

  • Buying a domain

Agent mode

  • Agent mode
  • Agent plans & credit

CLI

  • Command-line tool

Account

  • Team access
  • Billing

Help

  • Troubleshooting
  • Still stuck?
Deploy guides
  • Next.js
  • Node.js
  • Python
  • SvelteKit
  • Django
  • FastAPI
  • Flask
  • Express
  • Laravel
  • Go
  • Lovable
  • Bolt
  • Firebase Studio
  • Claude Code & Cursor
Compare
  • AI coding agent
  • Hosting in Ethiopia
  • vs Heroku
  • vs Vercel
  • vs Render
  • vs Railway
  • vs Fly.io
  • vs Netlify
  • Heroku alternatives
Deploy guidesExpress

Deploy an Express app

Express apps deploy with npm start. Listen on PORT, turn on trust proxy for HTTPS cookies, and keep sessions out of memory so logins survive a deploy.

Last updated October 23, 2026

Detected by
package.json
Install
npm ci / pnpm / yarn (from your lockfile)
Build
npm run build (if you have one)
Start
npm start

These are the defaults. Override any of them in Settings → Build.

Listen on process.env.PORT

We set PORT and send traffic there. A hardcoded app.listen(3000) happens to match today, but read the variable so a change never breaks your app: app.listen(process.env.PORT || 3000). Don't pass "localhost" as the host; leave it out so Node listens on every interface.

Prepare your app

// package.json
"scripts": {
  "start": "node index.js"
}

// index.js
const express = require("express");
const app = express();

app.set("trust proxy", 1);
app.get("/healthz", (req, res) => res.send("ok"));

app.listen(process.env.PORT || 3000);

We build on Node 22 unless engines.node in package.json asks for another version. For TypeScript, add a build script (tsc) and start the compiled output (node dist/index.js). The Node.js guide covers TypeScript and monorepos in more detail.

trust proxy and secure cookies

HTTPS ends at the platform's proxy, so your app sees plain HTTP. With app.set("trust proxy", 1), Express reads X-Forwarded-Proto: req.secure is true and cookies marked secure are actually sent.

Without it, express-session silently drops a secure session cookie and users can never stay logged in, and express-rate-limit logs an ERR_ERL_UNEXPECTED_X_FORWARDED_FOR validation error.

Health check

Every deploy requests your health check path (/ by default) and only goes live once it returns a 2xx or 3xx status. The platform keeps checking it after that. If / returns 404 or needs a login, add a route like /healthz that returns 200 and set it as the Health Check Path in Settings. Otherwise the deploy fails and the previous version keeps serving.

Sessions that survive a deploy

express-session's default MemoryStore lives in the process, so every deploy logs everyone out. Store sessions in a Redis or Postgres addon:

const session = require("express-session");
const { RedisStore } = require("connect-redis");
const { createClient } = require("redis");

const redis = createClient({ url: process.env.REDIS_URL });
redis.connect();

app.use(session({
  store: new RedisStore({ client: redis }),
  secret: process.env.SESSION_SECRET,
  resave: false,
  saveUninitialized: false,
  cookie: { secure: true },
}));

Add a database

Add a Postgres addon and DATABASE_URL is injected automatically. The database is on a private network, so no SSL options are needed:

const { Pool } = require("pg");
const pool = new Pool({ connectionString: process.env.DATABASE_URL });

Run migrations (Prisma, Drizzle, Knex) from the Build command or in front of the start command, for example npx prisma migrate deploy && node index.js.

Graceful shutdown

On each deploy the new container starts first, and the old one is stopped once the new one is healthy. Close the server on SIGTERM so requests in flight can finish:

const server = app.listen(process.env.PORT || 3000);
process.on("SIGTERM", () => server.close(() => process.exit(0)));

Deploy

  1. Sign in with GitHub and install the EthioDeploy app on the repo.
  2. Click “New Project” and pick the repo.
  3. Watch the build in the dashboard. When the health check passes, your app is live at <project-slug>.ethiodeploy.com. Every push to the default branch redeploys, and the old version keeps serving until the new one is healthy.

Troubleshooting

TypeError: Missing parameter name at index 1: *

Cause: Express 5 changed route path syntax, so a bare * wildcard is no longer valid.

Fix: Name the wildcard: app.get("/*splat", …), or "/{*splat}" to also match the root.

Users are logged out on every request or every deploy

Cause: trust proxy is off (secure cookie dropped) or sessions live in memory.

Fix: Set trust proxy and use a Redis or Postgres session store.

Deploy fails: GET / returned 404 (or 401)

Cause: / doesn't return a 2xx or 3xx status, so the health check never passes.

Fix: Add a /healthz route and set it as the Health Check Path.

Health check times out

Cause: The server listens on localhost or a different port.

Fix: Call app.listen(process.env.PORT) without a host.

Cannot find module 'dist/index.js'

Cause: The TypeScript build didn't run or outputs somewhere else.

Fix: Add a build script and match the path in start.

Still stuck? Check the deployment's build log in the dashboard, or see Troubleshooting.

Questions

Can I host an Express app for free?

Yes. The Free plan runs one project with a 512 MB Postgres or Redis at no cost. It sleeps after 30 minutes without traffic and wakes on the next request. Pro ($10/month) is always on with 1 GB of memory.

Do WebSockets and Socket.IO work?

Yes. The app runs as one long-lived process behind the proxy, so WebSocket upgrades work without extra setup.

Can I run a Discord or Telegram bot with Express?

If the bot only uses webhooks, run it as a normal web service. For a long-polling bot with no HTTP server, set the service type to Background Worker, which skips the health check and never sleeps. Workers on the Free plan count against its usage quota.

Ready to deploy? Sign in with GitHub and pick your repo. No Dockerfile, no YAML.

Other framework guides

Next.jsNode.jsPythonSvelteKitLovableBoltClaude Code & CursorDjangoFastAPIFlaskLaravelGoFirebase Studio

See the full docs for custom domains, env vars, addons, and billing.

On this page

  • Prepare your app
  • trust proxy and secure cookies
  • Health check
  • Sessions that survive a deploy
  • Add a database
  • Graceful shutdown
  • Deploy
  • Troubleshooting
  • Questions

Product

  • Pricing
  • Documentation
  • AI coding agent
  • Agent mode docs
  • Domains
  • Hosting in Ethiopia

Deploy guides

  • Next.js
  • Node.js
  • Python
  • SvelteKit
  • Django
  • FastAPI
  • Flask
  • Express
  • Laravel
  • Go
  • Lovable
  • Bolt
  • Firebase Studio
  • Claude Code & Cursor

Compare

  • EthioDeploy vs Heroku
  • EthioDeploy vs Vercel
  • EthioDeploy vs Render
  • EthioDeploy vs Railway
  • EthioDeploy vs Fly.io
  • EthioDeploy vs Netlify
  • Heroku alternatives

Company

  • About
  • Support
  • Contact
  • Terms
  • Privacy

EthioDeploy: agentic first development and deployment, started in Ethiopia, built for developers everywhere.