Skip to main content

Deploy Fastify on Ubuntu with nginx

TL;DR

Complete guide to deploying a Fastify application on Ubuntu 24.04 with PM2 cluster mode and nginx reverse proxy.

Key facts

Topology
Fastify on Ubuntu 24.04

TL;DR

Fastify is the fastest Node.js web framework, but deploying it correctly requires attention to listen address binding, cluster mode compatibility, and production logger configuration. This guide walks through a complete Fastify deployment on Ubuntu with PM2 and nginx.

Prerequisites

  • Ubuntu 24.04 LTS with sudo access
  • Node.js 22 LTS installed
  • A domain name pointing to your server

Step 1 — Prepare the application

Clone and install your Fastify project:

sudo mkdir -p /var/www/fastifyapp && sudo chown $USER:$USER /var/www/fastifyapp
cd /var/www/fastifyapp
git clone git@github.com:yourorg/fastifyapp.git .
npm ci --production

Step 2 — Listen address binding

Fastify defaults to listening on 127.0.0.1, which is correct when nginx sits on the same machine. If you ever need the app reachable from other hosts directly, bind to 0.0.0.0. For a standard nginx reverse proxy setup, keep 127.0.0.1:

await fastify.listen({ port: 3000, host: '127.0.0.1' });

Binding to 0.0.0.0 when you don't need to exposes the Node.js process to the network — always bind to localhost and let nginx handle external traffic.

Step 3 — Production logger configuration

Fastify's built-in Pino logger is fast, but in production you should configure structured JSON output and route logs to files for aggregation:

import Fastify from 'fastify';

const fastify = Fastify({
  logger: {
    level: 'info',
    transport: {
      target: 'pino/file',
      options: { destination: '/var/log/fastify/app.log', mkdir: true },
    },
  },
});

Avoid pino-pretty in production — it adds overhead and makes log parsing harder. JSON logs are machine-readable and work with log aggregation tools out of the box.

Step 4 — Health check route

Register a lightweight health endpoint:

fastify.get('/health', async () => {
  return { status: 'ok', uptime: process.uptime() };
});

Step 5 — PM2 cluster mode

Fastify works well in PM2 cluster mode. Create ecosystem.config.js:

module.exports = {
  apps: [{
    name: 'fastifyapp',
    script: 'dist/server.js',
    instances: 'max',
    exec_mode: 'cluster',
    max_memory_restart: '512M',
    env_production: {
      NODE_ENV: 'production',
      PORT: 3000,
    },
    error_file: '/var/log/pm2/fastify-error.log',
    out_file: '/var/log/pm2/fastify-out.log',
    merge_logs: true,
    log_date_format: 'YYYY-MM-DD HH:mm:ss Z',
    wait_ready: true,
    listen_timeout: 8000,
    kill_timeout: 5000,
  }],
};

Add the ready signal in your server bootstrap after listen() resolves:

await fastify.listen({ port: 3000, host: '127.0.0.1' });
if (process.send) process.send('ready');

Handle graceful shutdown so in-flight requests drain before the worker exits:

const shutdown = async () => {
  await fastify.close();
  process.exit(0);
};
process.on('SIGINT', shutdown);
process.on('SIGTERM', shutdown);

Start and persist:

sudo mkdir -p /var/log/pm2 /var/log/fastify
pm2 start ecosystem.config.js --env production
pm2 startup systemd
pm2 save

Step 6 — nginx reverse proxy

sudo apt install -y nginx

Create /etc/nginx/sites-available/fastifyapp:

upstream fastify_backend {
    server 127.0.0.1:3000;
    keepalive 64;
}

server {
    listen 80;
    server_name example.com;

    location / {
        proxy_pass http://fastify_backend;
        proxy_http_version 1.1;
        proxy_set_header Connection "";
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_read_timeout 60s;
        proxy_connect_timeout 10s;
    }
}

Using keepalive 64 on the upstream and setting Connection "" keeps persistent connections between nginx and Fastify, reducing connection overhead and improving throughput.

sudo ln -s /etc/nginx/sites-available/fastifyapp /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginx

Step 7 — SSL and firewall

sudo apt install -y certbot python3-certbot-nginx
sudo certbot --nginx -d example.com
sudo ufw allow 'Nginx Full'
sudo ufw allow OpenSSH
sudo ufw enable

Monitoring

Fastify's Pino logger outputs structured JSON, which integrates cleanly with log aggregation platforms. Combine this with PM2's process metrics for a complete picture:

pm2 monit
pm2 logs fastifyapp --lines 50

With Reflex

Reflex monitors your Fastify cluster through PM2 metrics and nginx upstream health. It tracks request latency, error rates, memory usage per worker, and log patterns for anomaly detection. On deploy, Reflex runs a rolling PM2 reload, verifies the /health endpoint, and automatically rolls back if the new version fails. See How it works.