Configure env Variables, Reverse Proxy Trust, and Zero-Downtime Restarts
Production Deployment
Prepare a Koa app for production by reading config from environment variables, enabling app.proxy for X-Forwarded headers, and rolling out updates with zero downtime using PM2 or a process manager.
What you'll learn
- Read PORT and other config from environment variables with fallbacks
- Enable app.proxy=true so ctx.ip and ctx.protocol reflect the real client
- Perform a zero-downtime rolling restart with PM2 reload
Deploying Koa to production requires a small set of deliberate choices: where config comes from, how the app behaves behind a reverse proxy, and how you push updates without dropping connections.
Environment Configuration
Never hard-code port numbers, database URLs, or API keys. Read them from
process.env with documented fallbacks:
// config.js
export const config = {
port: Number(process.env.PORT ?? 3000),
nodeEnv: process.env.NODE_ENV ?? "development",
databaseUrl: process.env.DATABASE_URL,
jwtSecret: process.env.JWT_SECRET,
}; // server.js
import { createServer } from "node:http";
import app from "./app.js";
import { config } from "./config.js";
const server = createServer(app.callback());
server.listen(config.port, () => {
console.log(`Listening on :${config.port} [${config.nodeEnv}]`);
}); Use a .env file locally with a library like dotenv or Node 20’s
--env-file flag, and set real variables in your hosting platform’s dashboard
or secrets manager for production.
Trusting a Reverse Proxy
When NGINX, Caddy, or a cloud load balancer sits in front of Koa, the real
client IP and protocol arrive in X-Forwarded-For and X-Forwarded-Proto
headers. Tell Koa to trust them:
const app = new Koa();
app.proxy = true; // enables trust for X-Forwarded-* headers With app.proxy = true:
ctx.ipreturns the first value ofX-Forwarded-For(the real client IP).ctx.protocolreturns the value ofX-Forwarded-Proto(httporhttps).
Only enable this when a trusted proxy controls those headers. If your server is directly internet-facing, malicious clients can spoof their IP by setting the header themselves.
NGINX Configuration Snippet
server {
listen 80;
server_name example.com;
location / {
proxy_pass http://127.0.0.1:3000;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
} Zero-Downtime Restarts with PM2
pm2 reload sends SIGINT to one worker at a time, waits for it to finish
in-flight requests (graceful shutdown), then starts a replacement before moving
to the next worker.
pm2 start server.js -i max --name koa-api
pm2 reload koa-api # rolling restart, zero downtime Handle SIGINT in your server to drain connections before exiting:
process.on("SIGINT", async () => {
console.log("Shutting down gracefully…");
server.close(() => {
console.log("HTTP server closed");
process.exit(0);
});
// Close DB pools, flush logs, etc.
}); Up Next
Package your Koa app in a minimal Docker image with a multi-stage build.
Containerising with Docker →