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.