TL;DR
Complete guide to configuring nginx as a reverse proxy for Nuxt 3 with caching, compression, WebSocket support, and load balancing.
Key facts
- Topology
- Nuxt 3 behind nginx
TL;DR
nginx sits in front of your Nuxt 3 Nitro server to handle SSL termination, static asset caching, compression, and connection buffering. A properly configured nginx reverse proxy improves performance, security, and reliability for self-hosted Nuxt 3 applications.
Basic reverse proxy
The minimal setup proxies all traffic to Nitro:
upstream nuxt_backend {
server 127.0.0.1:3000;
keepalive 64;
}
server {
listen 443 ssl http2;
server_name example.com;
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
location / {
proxy_pass http://nuxt_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;
proxy_send_timeout 60s;
}
}
Proxy headers explained
X-Forwarded-For— passes the real client IP through the proxy chainX-Real-IP— the immediate client IP (without proxy chain)X-Forwarded-Proto— tells Nitro whether the original request was HTTP or HTTPS (critical for correct redirect URLs and cookieSecureflags)Host— preserves the original hostname for correct routingConnection ""— enables HTTP/1.1 keepalive between nginx and Nitro (reduces connection overhead)
Static asset caching
Nuxt 3 outputs hashed client bundles to _nuxt/. These are immutable — they can be cached indefinitely:
location /_nuxt/ {
alias /var/www/nuxt-app/.output/public/_nuxt/;
expires 365d;
add_header Cache-Control "public, immutable";
access_log off;
etag off;
}
location /favicon.ico {
alias /var/www/nuxt-app/.output/public/favicon.ico;
expires 30d;
access_log off;
}
Serving static assets directly from nginx (bypassing Nitro) reduces Node.js CPU usage significantly under load.
Gzip and Brotli compression
gzip on;
gzip_vary on;
gzip_proxied any;
gzip_comp_level 5;
gzip_min_length 1000;
gzip_types
text/plain
text/css
text/xml
application/json
application/javascript
application/xml+rss
application/atom+xml
image/svg+xml;
# Brotli (requires ngx_brotli module)
brotli on;
brotli_comp_level 4;
brotli_types text/plain text/css application/json application/javascript text/xml application/xml image/svg+xml;
Rate limiting
Protect your Nuxt SSR server from excessive requests:
limit_req_zone $binary_remote_addr zone=nuxt_limit:10m rate=30r/s;
server {
location / {
limit_req zone=nuxt_limit burst=50 nodelay;
proxy_pass http://nuxt_backend;
# ... headers ...
}
}
Multiple Nuxt instances (load balancing)
For high-traffic applications, run multiple Nitro processes across different ports or servers:
upstream nuxt_backend {
least_conn;
server 127.0.0.1:3000;
server 127.0.0.1:3001;
server 127.0.0.1:3002;
keepalive 64;
}
Or with PM2 cluster mode (recommended for single-server deployments), PM2 handles the load balancing internally and nginx only needs a single upstream entry.
WebSocket support (development only)
Nuxt 3's HMR uses WebSockets in development. This configuration is not needed in production but included for completeness if you proxy dev traffic through nginx:
location /_nuxt/hmr {
proxy_pass http://nuxt_backend;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
Security headers
add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "DENY" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Permissions-Policy "camera=(), microphone=(), geolocation=()" always;
Testing the configuration
sudo nginx -t
sudo systemctl reload nginx
curl -I https://example.com/
Verify headers include your security headers, correct cache-control on /_nuxt/ assets, and that the X-Forwarded-* headers reach your Nuxt application (check with useRequestHeaders() in a server route).
With Reflex
Reflex monitors nginx upstream health alongside your Nuxt 3 Nitro process. It tracks 502/504 error rates, upstream response times, and connection pool utilisation. When issues arise, Reflex can restart Nitro, adjust nginx configuration, and verify the proxy chain is healthy — providing unified observability across the reverse proxy and application layer. See How it works.