Production Deployment

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.

5 min read Level 3/5 #koa#production#deployment
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.ip returns the first value of X-Forwarded-For (the real client IP).
  • ctx.protocol returns the value of X-Forwarded-Proto (http or https).

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 →