CoreDNS at 10.43.0.10 returns SERVFAIL for demo.flow-master.ai in this environment. Including it in the resolver list caused nginx to query it first, get SERVFAIL, and return 502 before falling back to 1.1.1.1. Pinning to public resolvers eliminates the cold miss. 300s TTL caches the result. Restored $variable form for proxy_pass because the static-hostname form failed pod readiness (DNS query at config-load time hits the same SERVFAIL).
87 lines
3.8 KiB
Nginx Configuration File
87 lines
3.8 KiB
Nginx Configuration File
map $sent_http_content_type $csp_header {
|
|
default "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; font-src 'self' data: https://fonts.gstatic.com; img-src 'self' data: blob: https://*.tile.openstreetmap.org https://unpkg.com; connect-src 'self' https://demo.flow-master.ai https://canvas.flow-master.ai wss://canvas.flow-master.ai; frame-ancestors 'none'; base-uri 'self'; form-action 'self'; object-src 'none'";
|
|
}
|
|
|
|
server {
|
|
listen 80;
|
|
server_name _;
|
|
root /usr/share/nginx/html;
|
|
index index.html;
|
|
|
|
# Hardening — every response carries these.
|
|
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
|
|
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=(self), geolocation=(self), payment=()" always;
|
|
add_header Content-Security-Policy $csp_header always;
|
|
add_header Cross-Origin-Opener-Policy "same-origin" always;
|
|
add_header Cross-Origin-Resource-Policy "same-origin" always;
|
|
add_header X-Robots-Tag "noindex, nofollow" always;
|
|
|
|
# Use PUBLIC DNS resolvers exclusively for upstream resolution. The
|
|
# k3s CoreDNS at 10.43.0.10 returns SERVFAIL for external hostnames
|
|
# like demo.flow-master.ai in this environment, which previously
|
|
# caused intermittent 502s when nginx queried CoreDNS first. Pinning
|
|
# to Cloudflare + Google means demo.flow-master.ai resolves the same
|
|
# way it does from the public internet.
|
|
resolver 1.1.1.1 8.8.8.8 valid=300s ipv6=off;
|
|
|
|
# Reverse-proxy /api to the demo backend so live mode is same-origin.
|
|
# Uses $variable form so nginx queries the PUBLIC resolver above per
|
|
# request (cached 5 minutes). Combined with proxy_next_upstream this
|
|
# tolerates a transient upstream failure with a single client-visible
|
|
# latency penalty.
|
|
location /api/ {
|
|
set $upstream_demo "https://demo.flow-master.ai";
|
|
proxy_pass $upstream_demo;
|
|
proxy_set_header Host demo.flow-master.ai;
|
|
proxy_set_header X-Forwarded-Proto https;
|
|
proxy_set_header X-Forwarded-Host $host;
|
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
proxy_ssl_server_name on;
|
|
proxy_ssl_name demo.flow-master.ai;
|
|
proxy_http_version 1.1;
|
|
proxy_connect_timeout 8s;
|
|
proxy_send_timeout 30s;
|
|
proxy_read_timeout 30s;
|
|
proxy_buffering off;
|
|
proxy_next_upstream error timeout http_502 http_503 http_504;
|
|
proxy_next_upstream_tries 3;
|
|
proxy_next_upstream_timeout 10s;
|
|
}
|
|
|
|
# In-cluster LLM proxy. Returns 503 when no provider key is configured,
|
|
# so the frontend's deterministic fallback fires gracefully.
|
|
location /internal/canvas-llm/ {
|
|
set $upstream_llm "http://canvas-llm-proxy.demo.svc.cluster.local:8080";
|
|
proxy_pass $upstream_llm;
|
|
proxy_set_header Host $host;
|
|
proxy_set_header X-Forwarded-Proto https;
|
|
proxy_http_version 1.1;
|
|
proxy_read_timeout 60s;
|
|
proxy_buffering off;
|
|
}
|
|
|
|
# SPA fallback to index.html for client-side routing.
|
|
location / {
|
|
try_files $uri $uri/ /index.html;
|
|
add_header Cache-Control "no-cache" always;
|
|
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
|
|
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=(self), geolocation=(self), payment=()" always;
|
|
add_header Content-Security-Policy $csp_header always;
|
|
}
|
|
|
|
# Hashed static assets cache forever.
|
|
location /assets/ {
|
|
try_files $uri =404;
|
|
add_header Cache-Control "public, max-age=31536000, immutable" always;
|
|
}
|
|
|
|
# Hide nginx version on errors.
|
|
server_tokens off;
|
|
}
|