upstream backend { server backend:3000; } upstream frontend { server frontend:5173; } server { listen 80; server_name localhost; # Exact match for bare /api (no trailing slash). # nginx's `location /api/` below requires a trailing slash, so a request for # GET /api would fall through to the Vite proxy, which then forwards it to # the backend — arriving as an unmatched path that New Relic registers as # the phantom "Expressjs/GET/api$" transaction bucket. # This exact-match block catches it first and proxies it directly to the # backend, where AppController's @Get() handler returns a clean 200. location = /api { proxy_pass http://backend; proxy_http_version 1.1; 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; } # API requests -> NestJS backend location /api/ { proxy_pass http://backend; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection 'upgrade'; 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_cache_bypass $http_upgrade; } # AI endpoints now return immediately (async processing in background) # No special timeout needed — kept for documentation purposes # Everything else -> Vite dev server (frontend) location / { proxy_pass http://frontend; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection 'upgrade'; 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_cache_bypass $http_upgrade; } }