The nginx catch-all location / used proxy_pass $litellm_backend_url/ which stripped the URI path, causing /openapi.json to return the Swagger UI HTML page instead of the actual OpenAPI spec JSON. This broke the Swagger UI rendering with "Unable to render this definition" error. Fix: Add a dedicated location /openapi.json block before the catch-all / that preserves the full path, so LiteLLM returns its valid openapi: 3.1.0 spec JSON at the public endpoint.
299 lines
11 KiB
Nginx Configuration File
299 lines
11 KiB
Nginx Configuration File
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/;
|
|
}
|
|
}
|
|
}
|