worker_processes auto; events { worker_connections 1024; } http { include /etc/nginx/mime.types; default_type application/octet-stream; # JSON access log for Alloy to tail → Loki + metric extraction log_format json_log escape=json '{' '"time":"$time_iso8601",' '"remote_addr":"$remote_addr",' '"client_ip":"$http_fly_client_ip",' '"request_method":"$request_method",' '"request_uri":"$request_uri",' '"status":$status,' '"body_bytes_sent":$body_bytes_sent,' '"request_time":$request_time,' '"upstream_response_time":"$upstream_response_time",' '"upstream_cache_status":"$upstream_cache_status",' '"http_host":"$http_host",' '"http_user_agent":"$http_user_agent"' '}'; access_log /var/log/nginx/access.json.log json_log; # Rate limiting zones — define per-service zones as needed limit_req_zone $binary_remote_addr zone=general:10m rate=10r/s; # Proxy cache: 200MB, evict after 24h of no access proxy_cache_path /tmp/cache levels=1:2 keys_zone=services:10m max_size=200m inactive=24h; # MagicDNS resolver — using a variable in proxy_pass defers upstream DNS # resolution to request time (not config time). Results are cached for # 30s per worker to avoid per-request DNS lookups. resolver 100.100.100.100 valid=30s; resolver_timeout 5s; # --- docs.eblu.me (static site) --- server { listen 8080; server_name docs.eblu.me; limit_req zone=general burst=20 nodelay; # Serve a friendly error page when upstreams are unreachable # (indri offline, Tailscale tunnel down, emergency shutoff, etc.) # proxy_cache_use_stale still takes priority when cached content exists. error_page 502 503 504 /error.html; location = /error.html { root /usr/share/nginx/html; internal; } location / { set $upstream_docs https://docs.tail8d86e.ts.net; proxy_pass $upstream_docs$request_uri; proxy_ssl_verify off; proxy_ssl_server_name on; proxy_intercept_errors on; # Cache aggressively — static site only. # Do NOT use these settings for dynamic services. proxy_cache services; proxy_cache_valid 200 1d; proxy_cache_valid 404 1m; proxy_cache_use_stale error timeout updating; proxy_cache_lock on; # Prevent cache-busting: ignore query strings and # client cache-control headers. # Safe for static sites; breaks dynamic services. proxy_cache_key $host$uri; proxy_ignore_headers Cache-Control Set-Cookie; add_header X-Cache-Status $upstream_cache_status; add_header X-Clacks-Overhead "GNU Terry Pratchett" always; } } # --- cv.eblu.me (static site) --- server { listen 8080; server_name cv.eblu.me; limit_req zone=general burst=20 nodelay; error_page 502 503 504 /error.html; location = /error.html { root /usr/share/nginx/html; internal; } location / { set $upstream_cv https://cv.tail8d86e.ts.net; proxy_pass $upstream_cv$request_uri; proxy_ssl_verify off; proxy_ssl_server_name on; proxy_intercept_errors on; proxy_cache services; proxy_cache_valid 200 1d; proxy_cache_valid 404 1m; proxy_cache_use_stale error timeout updating; proxy_cache_lock on; proxy_cache_key $host$uri; proxy_ignore_headers Cache-Control Set-Cookie; add_header X-Cache-Status $upstream_cache_status; add_header X-Clacks-Overhead "GNU Terry Pratchett" always; } } # Catch-all: reject unknown hosts, but serve health check server { listen 8080 default_server; location /healthz { return 200 "ok\n"; } location /stub_status { stub_status; allow 127.0.0.1; deny all; } location / { return 444; } } }