events { worker_connections 1024; } http { include /etc/nginx/mime.types; default_type application/octet-stream; sendfile on; keepalive_timeout 65; # Docker DNS resolver — forces request-time resolution for variable-based proxy_pass. # Without this, nginx resolves upstream hostnames at config load time, # which fails when Docker DNS (127.0.0.11) isn't ready yet on container start. resolver 127.0.0.11 valid=30s; # Dynamic upstream resolution via nginx variables. # Using $var in proxy_pass forces request-time resolution through the resolver. # Without this, 'host not found in upstream' crashes nginx when Docker DNS is slow. map $host $router_api_url { default http://harness-router:9000; } map $host $dashboard_ui_url { default http://harness-dashboard:3000; } map $host $litellm_backend_url { default http://harness-litellm:4000; } # Detect Cloudflare Tunnel requests (cloudflared always sets CF-Connecting-IP). # Direct LAN browser access to :4000 has no such header -> redirect to canonical https, # preventing the cross-origin localStorage footgun that traps the UI at the login page. map $http_cf_connecting_ip $is_cloudflared { default 1; # any non-empty value = request came through Cloudflare "" 0; # empty = direct access } # ════════════════════════════════════════════════════════════════ # Server :80 — existing harness entrypoint # dashboard (/), router API (/v1/, /admin/, /stream, /api/, /metrics), # router fallback, LiteLLM UI via /litellm/ prefix, health # ════════════════════════════════════════════════════════════════ server { listen 80; # Authentik OIDC subrequest location /authentik/auth { internal; proxy_pass https://auth.sysloggh.net/outpost.goauthentik.io/auth/nginx; proxy_pass_request_body off; proxy_set_header Content-Length ""; proxy_set_header X-Original-URL $scheme://$http_host$request_uri; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } # 2-Layer: /v1/ → router (existing keys work unchanged) location /v1/ { proxy_pass $router_api_url; proxy_http_version 1.1; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header Authorization $http_authorization; proxy_connect_timeout 10s; proxy_read_timeout 600s; proxy_buffering off; error_page 502 503 = @router_fallback; } location @router_fallback { proxy_pass $router_api_url; proxy_http_version 1.1; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header Authorization $http_authorization; proxy_read_timeout 600s; } location /admin/ { proxy_pass $router_api_url; proxy_http_version 1.1; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header Authorization $http_authorization; proxy_read_timeout 600s; } location /stream { proxy_pass $router_api_url/stream; proxy_http_version 1.1; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; proxy_buffering off; } location /api/ { proxy_pass $router_api_url/; proxy_http_version 1.1; proxy_set_header Host $host; } # LiteLLM gateway access (agents with new virtual keys) location /litellm/v1/ { proxy_pass $litellm_backend_url/v1/; proxy_http_version 1.1; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header Authorization $http_authorization; proxy_connect_timeout 10s; proxy_read_timeout 600s; proxy_buffering off; } # LiteLLM UI static assets location /litellm-asset-prefix/ { proxy_pass $litellm_backend_url/litellm-asset-prefix/; proxy_http_version 1.1; proxy_set_header Host $host; } # LiteLLM Admin UI via /litellm/ prefix (LAN/internal access on :80) location /litellm/ { proxy_pass $litellm_backend_url/; 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; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; proxy_read_timeout 86400s; proxy_buffering off; proxy_redirect http://$host/ /litellm/; proxy_redirect https://$host/ /litellm/; } location /dashboard/ { proxy_pass $dashboard_ui_url/; proxy_http_version 1.1; proxy_set_header Host $host; } # Dedicated /openapi.json block — must come before catch-all / # LiteLLM serves valid openapi: 3.1.0 JSON at this path internally, # but the catch-all location / strips the URI path. This block # preserves the full path so the spec JSON is returned instead of # the Swagger UI SPA HTML. location /openapi.json { proxy_pass $litellm_backend_url/openapi.json; proxy_http_version 1.1; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header Authorization $http_authorization; proxy_connect_timeout 10s; proxy_read_timeout 600s; proxy_buffering off; } location / { proxy_pass $litellm_backend_url/; proxy_http_version 1.1; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header Authorization $http_authorization; proxy_connect_timeout 10s; proxy_read_timeout 600s; proxy_buffering off; } location /metrics/ { proxy_pass $router_api_url/metrics/; proxy_http_version 1.1; proxy_set_header Host $host; } location /metrics/circuit-breaker { proxy_pass $router_api_url/metrics/circuit-breaker; proxy_http_version 1.1; proxy_set_header Host $host; } location /router/ { proxy_pass $router_api_url/; proxy_http_version 1.1; proxy_set_header Host $host; } location /health/unified { proxy_pass $router_api_url/health/unified; proxy_http_version 1.1; proxy_set_header Host $host; } location /health { proxy_pass $router_api_url/health; proxy_http_version 1.1; proxy_set_header Host $host; } } # ════════════════════════════════════════════════════════════════ # Server :4000 — external LiteLLM entrypoint (cloudflared target) # Fixes the /ui redirect: forces correct Host + scheme so LiteLLM # builds external URLs instead of leaking 192.168.68.116:4000. # LiteLLM itself is now bound to 127.0.0.1 only (see compose). # ════════════════════════════════════════════════════════════════ server { listen 4000; # Canonical external identity — overrides whatever Host cloudflared sends proxy_set_header Host litellm.sysloggh.net; proxy_set_header X-Forwarded-Host litellm.sysloggh.net; proxy_set_header X-Forwarded-Proto https; proxy_set_header X-Forwarded-Port 443; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Real-IP $remote_addr; # /ui → /ui/ — absolute redirect (cloudflared rewrites Host to origin IP, # so a relative return would be absolutized to http://192.168.68.116:4000/). location = /ui { return 301 https://litellm.sysloggh.net/ui/; } # LiteLLM Admin UI + WebSocket location /ui/ { proxy_pass $litellm_backend_url/ui/; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; proxy_read_timeout 86400s; proxy_buffering off; proxy_redirect $litellm_backend_url/ https://litellm.sysloggh.net/; proxy_redirect http://litellm.sysloggh.net:4000/ https://litellm.sysloggh.net/; } # UI static assets location /litellm-asset-prefix/ { proxy_pass $litellm_backend_url/litellm-asset-prefix/; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; } # SSO callback location /sso/ { proxy_pass $litellm_backend_url/sso/; proxy_http_version 1.1; proxy_read_timeout 600s; } # API location /v1/ { proxy_pass $litellm_backend_url/v1/; proxy_http_version 1.1; proxy_set_header Authorization $http_authorization; proxy_read_timeout 600s; proxy_buffering off; } # Key management + admin API location /key/ { proxy_pass $litellm_backend_url/key/; proxy_http_version 1.1; proxy_set_header Authorization $http_authorization; } location /user/ { proxy_pass $litellm_backend_url/user/; proxy_http_version 1.1; proxy_set_header Authorization $http_authorization; } location /model/ { proxy_pass $litellm_backend_url/model/; proxy_http_version 1.1; proxy_set_header Authorization $http_authorization; } location /team/ { proxy_pass $litellm_backend_url/team/; proxy_http_version 1.1; proxy_set_header Authorization $http_authorization; } # Health location /health { proxy_pass $litellm_backend_url/health; proxy_http_version 1.1; } # Root + everything else → LiteLLM (UI index, /configs, /spend, etc.) location / { proxy_pass $litellm_backend_url/; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; proxy_read_timeout 86400s; proxy_buffering off; proxy_redirect http://192.168.68.116:4000/ https://litellm.sysloggh.net/; proxy_redirect https://192.168.68.116:4000/ https://litellm.sysloggh.net/; } } }