# Edge reverse proxy — single entry point (replaces Caddy). # Optional upstreams use Docker DNS resolver so nginx starts even if a module is disabled. map $http_upgrade $connection_upgrade { default upgrade; '' close; } # Reflect browser Origin for cross-origin API calls (web app on :3004, API on :80). map $http_origin $cors_allow_origin { default $http_origin; '' '*'; } server { listen 80; server_name ${DOMAIN}; client_max_body_size 10G; # --- ultid /api/v1/* (before OpenWebUI catch-alls; ^~ beats prefix /api/) --- location ^~ /api/v1/mail/ { resolver 127.0.0.11 valid=10s ipv6=off; set $ultid_upstream ultid:8080; proxy_hide_header Access-Control-Allow-Origin; proxy_hide_header Access-Control-Allow-Methods; proxy_hide_header Access-Control-Allow-Headers; proxy_hide_header Access-Control-Expose-Headers; proxy_hide_header Access-Control-Max-Age; proxy_hide_header Access-Control-Allow-Credentials; proxy_hide_header Vary; add_header Access-Control-Allow-Origin $cors_allow_origin always; add_header Access-Control-Allow-Methods "GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS" always; add_header Access-Control-Allow-Headers "Accept, Authorization, Content-Type, Idempotency-Key, Origin, X-Requested-With, X-Trace-Id" always; add_header Access-Control-Expose-Headers "X-Trace-Id" always; add_header Access-Control-Max-Age 300 always; add_header Vary Origin always; proxy_pass http://$ultid_upstream; 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 ${FORWARDED_PROTO}; } location ^~ /api/v1/migration/ { resolver 127.0.0.11 valid=10s ipv6=off; set $ultid_upstream ultid:8080; proxy_hide_header Access-Control-Allow-Origin; proxy_hide_header Access-Control-Allow-Methods; proxy_hide_header Access-Control-Allow-Headers; proxy_hide_header Access-Control-Expose-Headers; proxy_hide_header Access-Control-Max-Age; proxy_hide_header Access-Control-Allow-Credentials; proxy_hide_header Vary; add_header Access-Control-Allow-Origin $cors_allow_origin always; add_header Access-Control-Allow-Methods "GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS" always; add_header Access-Control-Allow-Headers "Accept, Authorization, Content-Type, Idempotency-Key, Origin, X-Requested-With, X-Trace-Id" always; add_header Access-Control-Expose-Headers "X-Trace-Id" always; add_header Access-Control-Max-Age 300 always; add_header Vary Origin always; proxy_pass http://$ultid_upstream; 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 ${FORWARDED_PROTO}; } location ^~ /api/v1/admin/ { resolver 127.0.0.11 valid=10s ipv6=off; set $ultid_upstream ultid:8080; proxy_hide_header Access-Control-Allow-Origin; proxy_hide_header Access-Control-Allow-Methods; proxy_hide_header Access-Control-Allow-Headers; proxy_hide_header Access-Control-Expose-Headers; proxy_hide_header Access-Control-Max-Age; proxy_hide_header Access-Control-Allow-Credentials; proxy_hide_header Vary; add_header Access-Control-Allow-Origin $cors_allow_origin always; add_header Access-Control-Allow-Methods "GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS" always; add_header Access-Control-Allow-Headers "Accept, Authorization, Content-Type, Idempotency-Key, Origin, X-Requested-With, X-Trace-Id" always; add_header Access-Control-Expose-Headers "X-Trace-Id" always; add_header Access-Control-Max-Age 300 always; add_header Vary Origin always; proxy_pass http://$ultid_upstream; 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 ${FORWARDED_PROTO}; } location ^~ /api/v1/drive/ { resolver 127.0.0.11 valid=10s ipv6=off; set $ultid_upstream ultid:8080; proxy_hide_header Access-Control-Allow-Origin; proxy_hide_header Access-Control-Allow-Methods; proxy_hide_header Access-Control-Allow-Headers; proxy_hide_header Access-Control-Expose-Headers; proxy_hide_header Access-Control-Max-Age; proxy_hide_header Access-Control-Allow-Credentials; proxy_hide_header Vary; add_header Access-Control-Allow-Origin $cors_allow_origin always; add_header Access-Control-Allow-Methods "GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS" always; add_header Access-Control-Allow-Headers "Accept, Authorization, Content-Type, Idempotency-Key, Origin, X-Requested-With, X-Trace-Id" always; add_header Access-Control-Expose-Headers "X-Trace-Id" always; add_header Access-Control-Max-Age 300 always; add_header Vary Origin always; proxy_pass http://$ultid_upstream; 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 ${FORWARDED_PROTO}; } location ^~ /api/v1/office/ { resolver 127.0.0.11 valid=10s ipv6=off; set $ultid_upstream ultid:8080; proxy_hide_header Access-Control-Allow-Origin; proxy_hide_header Access-Control-Allow-Methods; proxy_hide_header Access-Control-Allow-Headers; proxy_hide_header Access-Control-Expose-Headers; proxy_hide_header Access-Control-Max-Age; proxy_hide_header Access-Control-Allow-Credentials; proxy_hide_header Vary; add_header Access-Control-Allow-Origin $cors_allow_origin always; add_header Access-Control-Allow-Methods "GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS" always; add_header Access-Control-Allow-Headers "Accept, Authorization, Content-Type, Idempotency-Key, Origin, X-Requested-With, X-Trace-Id" always; add_header Access-Control-Expose-Headers "X-Trace-Id" always; add_header Access-Control-Max-Age 300 always; add_header Vary Origin always; proxy_pass http://$ultid_upstream; 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 ${FORWARDED_PROTO}; } location ^~ /api/v1/richtext/ { resolver 127.0.0.11 valid=10s ipv6=off; set $ultid_upstream ultid:8080; proxy_hide_header Access-Control-Allow-Origin; proxy_hide_header Access-Control-Allow-Methods; proxy_hide_header Access-Control-Allow-Headers; proxy_hide_header Access-Control-Expose-Headers; proxy_hide_header Access-Control-Max-Age; proxy_hide_header Access-Control-Allow-Credentials; proxy_hide_header Vary; add_header Access-Control-Allow-Origin $cors_allow_origin always; add_header Access-Control-Allow-Methods "GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS" always; add_header Access-Control-Allow-Headers "Accept, Authorization, Content-Type, Idempotency-Key, Origin, X-Requested-With, X-Trace-Id" always; add_header Access-Control-Expose-Headers "X-Trace-Id" always; add_header Access-Control-Max-Age 300 always; add_header Vary Origin always; proxy_pass http://$ultid_upstream; 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 ${FORWARDED_PROTO}; } location ^~ /api/v1/ultidraw/ { resolver 127.0.0.11 valid=10s ipv6=off; set $ultid_upstream ultid:8080; proxy_hide_header Access-Control-Allow-Origin; proxy_hide_header Access-Control-Allow-Methods; proxy_hide_header Access-Control-Allow-Headers; proxy_hide_header Access-Control-Expose-Headers; proxy_hide_header Access-Control-Max-Age; proxy_hide_header Access-Control-Allow-Credentials; proxy_hide_header Vary; add_header Access-Control-Allow-Origin $cors_allow_origin always; add_header Access-Control-Allow-Methods "GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS" always; add_header Access-Control-Allow-Headers "Accept, Authorization, Content-Type, Idempotency-Key, Origin, X-Requested-With, X-Trace-Id" always; add_header Access-Control-Expose-Headers "X-Trace-Id" always; add_header Access-Control-Max-Age 300 always; add_header Vary Origin always; proxy_pass http://$ultid_upstream; 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 ${FORWARDED_PROTO}; } location ^~ /api/v1/ai/mcp { resolver 127.0.0.11 valid=10s ipv6=off; set $ultid_upstream ultid:8080; proxy_hide_header Access-Control-Allow-Origin; proxy_hide_header Access-Control-Allow-Methods; proxy_hide_header Access-Control-Allow-Headers; proxy_hide_header Access-Control-Expose-Headers; proxy_hide_header Access-Control-Max-Age; proxy_hide_header Access-Control-Allow-Credentials; proxy_hide_header Vary; add_header Access-Control-Allow-Origin $cors_allow_origin always; add_header Access-Control-Allow-Methods "GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS" always; add_header Access-Control-Allow-Headers "Accept, Authorization, Content-Type, Idempotency-Key, Mcp-Session-Id, Origin, X-Requested-With, X-Trace-Id, X-Ulti-Token, X-OpenWebUI-User-Email, X-OpenWebUI-User-Id, X-OpenWebUI-User-Name, X-OpenWebUI-User-Role" always; add_header Access-Control-Expose-Headers "Mcp-Session-Id, X-Trace-Id" always; add_header Access-Control-Max-Age 300 always; add_header Vary Origin always; proxy_pass http://$ultid_upstream; proxy_http_version 1.1; proxy_set_header Host $host; proxy_set_header Cookie $http_cookie; proxy_set_header Authorization $http_authorization; proxy_set_header X-Ulti-Token $http_x_ulti_token; proxy_set_header X-OpenWebUI-User-Email $http_x_openwebui_user_email; proxy_set_header X-OpenWebUI-User-Id $http_x_openwebui_user_id; proxy_set_header X-OpenWebUI-User-Name $http_x_openwebui_user_name; proxy_set_header X-OpenWebUI-User-Role $http_x_openwebui_user_role; 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 ${FORWARDED_PROTO}; proxy_buffering off; proxy_cache off; proxy_read_timeout 86400s; proxy_send_timeout 86400s; } location ^~ /api/v1/ai/ { resolver 127.0.0.11 valid=10s ipv6=off; set $ultid_upstream ultid:8080; proxy_hide_header Access-Control-Allow-Origin; proxy_hide_header Access-Control-Allow-Methods; proxy_hide_header Access-Control-Allow-Headers; proxy_hide_header Access-Control-Expose-Headers; proxy_hide_header Access-Control-Max-Age; proxy_hide_header Access-Control-Allow-Credentials; proxy_hide_header Vary; add_header Access-Control-Allow-Origin $cors_allow_origin always; add_header Access-Control-Allow-Methods "GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS" always; add_header Access-Control-Allow-Headers "Accept, Authorization, Content-Type, Idempotency-Key, Mcp-Session-Id, Origin, X-Requested-With, X-Trace-Id, X-Ulti-Token, X-OpenWebUI-User-Email, X-OpenWebUI-User-Id, X-OpenWebUI-User-Name, X-OpenWebUI-User-Role" always; add_header Access-Control-Expose-Headers "Mcp-Session-Id, X-Trace-Id" always; add_header Access-Control-Max-Age 300 always; add_header Vary Origin always; proxy_pass http://$ultid_upstream; proxy_http_version 1.1; proxy_set_header Host $host; proxy_set_header Cookie $http_cookie; proxy_set_header Authorization $http_authorization; proxy_set_header X-Ulti-Token $http_x_ulti_token; proxy_set_header X-OpenWebUI-User-Email $http_x_openwebui_user_email; proxy_set_header X-OpenWebUI-User-Id $http_x_openwebui_user_id; proxy_set_header X-OpenWebUI-User-Name $http_x_openwebui_user_name; proxy_set_header X-OpenWebUI-User-Role $http_x_openwebui_user_role; 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 ${FORWARDED_PROTO}; proxy_buffering off; proxy_cache off; proxy_read_timeout 86400s; proxy_send_timeout 86400s; } location ^~ /api/v1/users/me { resolver 127.0.0.11 valid=10s ipv6=off; set $ultid_upstream ultid:8080; proxy_hide_header Access-Control-Allow-Origin; proxy_hide_header Access-Control-Allow-Methods; proxy_hide_header Access-Control-Allow-Headers; proxy_hide_header Access-Control-Expose-Headers; proxy_hide_header Access-Control-Max-Age; proxy_hide_header Access-Control-Allow-Credentials; proxy_hide_header Vary; add_header Access-Control-Allow-Origin $cors_allow_origin always; add_header Access-Control-Allow-Methods "GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS" always; add_header Access-Control-Allow-Headers "Accept, Authorization, Content-Type, Idempotency-Key, Origin, X-Requested-With, X-Trace-Id" always; add_header Access-Control-Expose-Headers "X-Trace-Id" always; add_header Access-Control-Max-Age 300 always; add_header Vary Origin always; proxy_pass http://$ultid_upstream; 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 ${FORWARDED_PROTO}; } location ^~ /api/v1/calendar { resolver 127.0.0.11 valid=10s ipv6=off; set $ultid_upstream ultid:8080; proxy_hide_header Access-Control-Allow-Origin; proxy_hide_header Access-Control-Allow-Methods; proxy_hide_header Access-Control-Allow-Headers; proxy_hide_header Access-Control-Expose-Headers; proxy_hide_header Access-Control-Max-Age; proxy_hide_header Access-Control-Allow-Credentials; proxy_hide_header Vary; add_header Access-Control-Allow-Origin $cors_allow_origin always; add_header Access-Control-Allow-Methods "GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS" always; add_header Access-Control-Allow-Headers "Accept, Authorization, Content-Type, Idempotency-Key, Origin, X-Requested-With, X-Trace-Id" always; add_header Access-Control-Expose-Headers "X-Trace-Id" always; add_header Access-Control-Max-Age 300 always; add_header Vary Origin always; proxy_pass http://$ultid_upstream; 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 ${FORWARDED_PROTO}; } location ^~ /api/v1/contacts/ { resolver 127.0.0.11 valid=10s ipv6=off; set $ultid_upstream ultid:8080; proxy_hide_header Access-Control-Allow-Origin; proxy_hide_header Access-Control-Allow-Methods; proxy_hide_header Access-Control-Allow-Headers; proxy_hide_header Access-Control-Expose-Headers; proxy_hide_header Access-Control-Max-Age; proxy_hide_header Access-Control-Allow-Credentials; proxy_hide_header Vary; add_header Access-Control-Allow-Origin $cors_allow_origin always; add_header Access-Control-Allow-Methods "GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS" always; add_header Access-Control-Allow-Headers "Accept, Authorization, Content-Type, Idempotency-Key, Origin, X-Requested-With, X-Trace-Id" always; add_header Access-Control-Expose-Headers "X-Trace-Id" always; add_header Access-Control-Max-Age 300 always; add_header Vary Origin always; proxy_pass http://$ultid_upstream; 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 ${FORWARDED_PROTO}; } location ^~ /api/v1/meet/ { resolver 127.0.0.11 valid=10s ipv6=off; set $ultid_upstream ultid:8080; proxy_hide_header Access-Control-Allow-Origin; proxy_hide_header Access-Control-Allow-Methods; proxy_hide_header Access-Control-Allow-Headers; proxy_hide_header Access-Control-Expose-Headers; proxy_hide_header Access-Control-Max-Age; proxy_hide_header Access-Control-Allow-Credentials; proxy_hide_header Vary; add_header Access-Control-Allow-Origin $cors_allow_origin always; add_header Access-Control-Allow-Methods "GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS" always; add_header Access-Control-Allow-Headers "Accept, Authorization, Content-Type, Idempotency-Key, Origin, X-Requested-With, X-Trace-Id" always; add_header Access-Control-Expose-Headers "X-Trace-Id" always; add_header Access-Control-Max-Age 300 always; add_header Vary Origin always; proxy_pass http://$ultid_upstream; 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 ${FORWARDED_PROTO}; } location ^~ /api/v1/photos/ { resolver 127.0.0.11 valid=10s ipv6=off; set $ultid_upstream ultid:8080; proxy_hide_header Access-Control-Allow-Origin; proxy_hide_header Access-Control-Allow-Methods; proxy_hide_header Access-Control-Allow-Headers; proxy_hide_header Access-Control-Expose-Headers; proxy_hide_header Access-Control-Max-Age; proxy_hide_header Access-Control-Allow-Credentials; proxy_hide_header Vary; add_header Access-Control-Allow-Origin $cors_allow_origin always; add_header Access-Control-Allow-Methods "GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS" always; add_header Access-Control-Allow-Headers "Accept, Authorization, Content-Type, Idempotency-Key, Origin, X-Requested-With, X-Trace-Id" always; add_header Access-Control-Expose-Headers "X-Trace-Id" always; add_header Access-Control-Max-Age 300 always; add_header Vary Origin always; proxy_pass http://$ultid_upstream; 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 ${FORWARDED_PROTO}; } location ^~ /api/v1/search { resolver 127.0.0.11 valid=10s ipv6=off; set $ultid_upstream ultid:8080; proxy_hide_header Access-Control-Allow-Origin; proxy_hide_header Access-Control-Allow-Methods; proxy_hide_header Access-Control-Allow-Headers; proxy_hide_header Access-Control-Expose-Headers; proxy_hide_header Access-Control-Max-Age; proxy_hide_header Access-Control-Allow-Credentials; proxy_hide_header Vary; add_header Access-Control-Allow-Origin $cors_allow_origin always; add_header Access-Control-Allow-Methods "GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS" always; add_header Access-Control-Allow-Headers "Accept, Authorization, Content-Type, Idempotency-Key, Origin, X-Requested-With, X-Trace-Id" always; add_header Access-Control-Expose-Headers "X-Trace-Id" always; add_header Access-Control-Max-Age 300 always; add_header Vary Origin always; proxy_pass http://$ultid_upstream; 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 ${FORWARDED_PROTO}; } # OpenWebUI trusted-header signin — ultid injects X-Ulti-User-* (auth_request + POST body deadlocks) location = /api/v1/auths/signin { resolver 127.0.0.11 valid=10s ipv6=off; set $ultid_upstream ultid:8080; proxy_hide_header Access-Control-Allow-Origin; proxy_hide_header Access-Control-Allow-Methods; proxy_hide_header Access-Control-Allow-Headers; proxy_hide_header Access-Control-Expose-Headers; proxy_hide_header Access-Control-Max-Age; proxy_hide_header Access-Control-Allow-Credentials; proxy_hide_header Vary; add_header Access-Control-Allow-Origin $cors_allow_origin always; add_header Access-Control-Allow-Methods "GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS" always; add_header Access-Control-Allow-Headers "Accept, Authorization, Content-Type, Idempotency-Key, Origin, X-Requested-With, X-Trace-Id" always; add_header Access-Control-Expose-Headers "X-Trace-Id" always; add_header Access-Control-Max-Age 300 always; add_header Vary Origin always; proxy_pass http://$ultid_upstream/api/v1/ai/embed-signin; proxy_http_version 1.1; proxy_set_header Host $host; proxy_set_header Cookie $http_cookie; proxy_set_header Authorization $http_authorization; 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 ${FORWARDED_PROTO}; } # --- OpenWebUI API at site root (prod SPA: WEBUI_BASE_URL="" → fetch("/api/…")) --- # No auth_request here: POST + auth_request deadlocks (embed-auth timeout → 500). # Ultimail gate is /ai/ HTML; OpenWebUI API auth uses its own JWT (signin via embed-signin). location ~ ^/api/(config|changelog|version|webhook|usage|models|embeddings|message|chat|tasks)(/|$) { resolver 127.0.0.11 valid=10s ipv6=off; set $openwebui_upstream openwebui:8080; proxy_pass http://$openwebui_upstream; 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 ${FORWARDED_PROTO}; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection $connection_upgrade; proxy_set_header Accept-Encoding ""; sub_filter_once off; sub_filter_types application/json text/plain; sub_filter 'UltiAI (Open WebUI)' 'UltiAI'; sub_filter 'Open WebUI' 'UltiAI'; proxy_read_timeout 86400s; proxy_send_timeout 86400s; } location ~ ^/api/v1/(auths|channels|chats|notes|models|knowledge|prompts|tools|skills|memories|folders|groups|files|functions|evaluations|analytics|utils|terminals|automations|calendars|scim|pipelines|tasks|images|audio|retrieval|configs|users|chat|embeddings|messages)(/|$) { resolver 127.0.0.11 valid=10s ipv6=off; set $openwebui_upstream openwebui:8080; proxy_pass http://$openwebui_upstream; 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 ${FORWARDED_PROTO}; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection $connection_upgrade; proxy_read_timeout 86400s; proxy_send_timeout 86400s; } # ultid API fallback (must stay after ^~ /api/auth/ — mail OIDC routes) location /api/ { resolver 127.0.0.11 valid=10s ipv6=off; set $ultid_upstream ultid:8080; proxy_hide_header Access-Control-Allow-Origin; proxy_hide_header Access-Control-Allow-Methods; proxy_hide_header Access-Control-Allow-Headers; proxy_hide_header Access-Control-Expose-Headers; proxy_hide_header Access-Control-Max-Age; proxy_hide_header Access-Control-Allow-Credentials; proxy_hide_header Vary; add_header Access-Control-Allow-Origin $cors_allow_origin always; add_header Access-Control-Allow-Methods "GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS" always; add_header Access-Control-Allow-Headers "Accept, Authorization, Content-Type, Idempotency-Key, Origin, X-Requested-With, X-Trace-Id" always; add_header Access-Control-Expose-Headers "X-Trace-Id" always; add_header Access-Control-Max-Age 300 always; add_header Vary Origin always; proxy_pass http://$ultid_upstream; 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 ${FORWARDED_PROTO}; } # OpenWebUI socket.io (ultid uses /ws without /socket.io) location ^~ /ws/socket.io { resolver 127.0.0.11 valid=10s ipv6=off; set $openwebui_upstream openwebui:8080; proxy_pass http://$openwebui_upstream; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection $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 ${FORWARDED_PROTO}; proxy_buffering off; proxy_cache off; proxy_read_timeout 86400s; proxy_send_timeout 86400s; } # OpenWebUI socket.io under /ai prefix (SvelteKit base /ai — avoid embed-auth on /ai/) location ^~ /ai/ws/socket.io { resolver 127.0.0.11 valid=10s ipv6=off; set $openwebui_upstream openwebui:8080; rewrite ^/ai/ws/socket.io(.*)$ /ws/socket.io$1 break; proxy_pass http://$openwebui_upstream; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection $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 ${FORWARDED_PROTO}; proxy_buffering off; proxy_cache off; proxy_read_timeout 86400s; proxy_send_timeout 86400s; } location /ws { resolver 127.0.0.11 valid=10s ipv6=off; set $ultid_ws_upstream ultid:8080; proxy_hide_header Access-Control-Allow-Origin; add_header Access-Control-Allow-Origin $cors_allow_origin always; add_header Vary Origin always; proxy_pass http://$ultid_ws_upstream; 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 ${FORWARDED_PROTO}; } # OpenWebUI Ollama/OpenAI proxy mounts (SPA uses /ollama, /openai at root) location ^~ /ollama/ { resolver 127.0.0.11 valid=10s ipv6=off; set $openwebui_upstream openwebui:8080; proxy_pass http://$openwebui_upstream; 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 ${FORWARDED_PROTO}; } location ^~ /openai/ { resolver 127.0.0.11 valid=10s ipv6=off; set $openwebui_upstream openwebui:8080; proxy_pass http://$openwebui_upstream; 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 ${FORWARDED_PROTO}; } # TipTap / Hocuspocus — proxy WS without redirect (301 breaks upgrade) location /collab { resolver 127.0.0.11 valid=10s ipv6=off; set $hocuspocus_upstream host.docker.internal:1234; rewrite ^/collab/?(.*)$ /$1 break; proxy_pass http://$hocuspocus_upstream; 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 ${FORWARDED_PROTO}; proxy_read_timeout 86400s; proxy_send_timeout 86400s; } # Ultimail OIDC post-login — before Authentik /auth/ (path collision) location ^~ /auth/complete { resolver 127.0.0.11 valid=10s ipv6=off; set $mail_upstream ${MAIL_FRONTEND_UPSTREAM}; proxy_pass http://$mail_upstream; 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 ${FORWARDED_PROTO}; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection $connection_upgrade; } # Authentik brand CSS — bind-mount on authentik-server; must not sit behind CF 4h cache. location = /auth/static/dist/custom.css { resolver 127.0.0.11 valid=10s ipv6=off; set $authentik_upstream authentik-server:9000; proxy_pass http://$authentik_upstream; 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 ${FORWARDED_PROTO}; proxy_hide_header Cache-Control; proxy_hide_header X-Frame-Options; proxy_hide_header Content-Security-Policy; add_header Cache-Control "no-cache, must-revalidate" always; add_header Content-Security-Policy "frame-ancestors 'self' http://localhost:3004 http://127.0.0.1:3004" always; } location = /auth/static/dist/custom.js { resolver 127.0.0.11 valid=10s ipv6=off; set $authentik_upstream authentik-server:9000; proxy_pass http://$authentik_upstream; 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 ${FORWARDED_PROTO}; proxy_hide_header Cache-Control; proxy_hide_header X-Frame-Options; proxy_hide_header Content-Security-Policy; add_header Cache-Control "no-cache, must-revalidate" always; add_header Content-Security-Policy "frame-ancestors 'self' http://localhost:3004 http://127.0.0.1:3004" always; } location /auth/ { resolver 127.0.0.11 valid=10s ipv6=off; set $authentik_upstream authentik-server:9000; proxy_pass http://$authentik_upstream; 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 ${FORWARDED_PROTO}; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection $connection_upgrade; # sub_filter ne fonctionne pas sur les réponses gzip — désactiver la compression amont. proxy_set_header Accept-Encoding ""; proxy_read_timeout 86400; proxy_hide_header X-Frame-Options; proxy_hide_header Content-Security-Policy; # Permet l’embed du portail Authentik dans la suite (même host + dev Next :3004). add_header Content-Security-Policy "frame-ancestors 'self' http://localhost:3004 http://127.0.0.1:3004" always; sub_filter_once off; sub_filter_types text/html; sub_filter '' ''; } location /meet/ { resolver 127.0.0.11 valid=10s ipv6=off; set $jitsi_upstream jitsi-web; rewrite ^/meet/(.*)$ /$1 break; proxy_pass http://$jitsi_upstream; 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 ${FORWARDED_PROTO}; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection $connection_upgrade; proxy_read_timeout 86400; # Jitsi serves root-relative assets (/css, /libs) — prefix for subpath /meet/ proxy_set_header Accept-Encoding ""; sub_filter_once off; sub_filter_types text/html; sub_filter '