It works on my machine…
Production servers restart, scale to several copies, and sit behind load balancers that ask "are you healthy?". A few conventions make Node apps behave well there.
Production habits
import express from "express";
const app = express();
const started = Date.now();
app.get("/health", (req, res) => {
res.json({ status: "ok", uptimeSeconds: Math.round((Date.now() - started) / 1000) });
});
const server = app.listen(Number(process.env.PORT ?? 3000), () => {
console.log(JSON.stringify({ level: "info", msg: "server started", port: 3000 }));
});
process.on("SIGTERM", () => {
console.log(JSON.stringify({ level: "info", msg: "shutting down" }));
server.close(() => process.exit(0));
});
console.log(await (await fetch("http://localhost:3000/health")).json());- Run with NODE_ENV=production; a process manager (PM2, systemd) or the platform restarts crashed apps.
- Use every CPU core with several instances (PM2 cluster mode, containers) — Node is single-threaded per process.
- Docker: package the app with its exact Node version; deploy the same image everywhere (DevOps course).
- Platforms: Render, Railway, Fly.io, AWS, or a VPS behind a reverse proxy like Nginx.
FROM node:24-slim WORKDIR /app COPY package*.json ./ RUN npm ci --omit=dev COPY . . ENV NODE_ENV=production EXPOSE 3000 CMD ["node", "server.js"]