Skip to main content

Nuxt 3 + nginx reverse proxy — complete setup

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 chain
  • X-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 cookie Secure flags)
  • Host — preserves the original hostname for correct routing
  • Connection "" — 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.