familynido/deploy/nginx/default.conf
Pablo Fernández a308041d59 Initial commit
FamilyNido — a self-hosted PWA for a single household: shared calendar,
chores, meals, school agenda, health records and a family wall. One
instance per family, deployable with `docker compose` on any home
server.

Stack: .NET 10 (ASP.NET Core Minimal APIs) + EF Core 10 + PostgreSQL 16
on the backend, Angular 21 (standalone, signals, zoneless) + Tailwind
CSS v4 on the frontend, SignalR for realtime, optional OIDC alongside
local credentials, integration via a versioned `/api/v1/**` public API.

See README.md for the module overview and how to deploy.
2026-05-13 00:23:14 +02:00

135 lines
6 KiB
Text
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

server {
listen 8080;
listen [::]:8080;
server_name _;
root /usr/share/nginx/html;
index index.html;
# We sit behind Traefik on a different scheme/port. nginx defaults to
# `absolute_redirect on`, which would rewrite a `return 302 /es/;` to an
# absolute `http://<server_name>:8080/es/` — and the browser would then
# follow that to the internal port, bypassing the proxy.
# Sending only the path lets the browser resolve it against the public
# URL it actually used (https://familia.example.com/...).
absolute_redirect off;
# Bigger proxy buffers for the API responses. The /signin-oidc 302 returns
# a session cookie chunked across `familynido.session*` cookies, easily
# 610 KB of headers. With the default 48 KB buffer nginx would abort
# with `upstream sent too big header` and surface a 502.
proxy_buffer_size 16k;
proxy_buffers 8 16k;
proxy_busy_buffers_size 32k;
# Bigger *request* header buffers. The OIDC callback receives a long
# `?state=...` plus the browser's Cookie header (session + correlation
# cookies + sometimes orphan nonces from prior failed attempts). The
# default 4×8K trips the moment any of those grow, returning a stark
# 400 "Request Header Or Cookie Too Large" before the request even
# reaches the backend.
large_client_header_buffers 8 32k;
# Allow image uploads up to ~12 MB. The API enforces its own 10 MB cap
# (FilesOptions.MaxImageBytes); we pad slightly so a borderline file gets
# nginx out of the way and surfaces the API's clean validation error
# instead of a blunt 413 with no body. iOS HEIC photos routinely sit
# in the 38 MB range and would otherwise fail silently.
client_max_body_size 12m;
# Gzip text assets; PWA manifest explicitly included.
gzip on;
gzip_comp_level 6;
gzip_min_length 1024;
gzip_types text/plain text/css application/javascript application/json
application/manifest+json image/svg+xml text/xml;
# Aggressive caching for hashed bundle files (Angular outputHashing=all).
location ~* \.(?:js|css|woff2?|ttf|otf|eot|ico|svg|png|jpg|jpeg|gif|webp|avif)$ {
expires 1y;
add_header Cache-Control "public, immutable";
try_files $uri =404;
}
# Service worker registration script and config must never be cached.
location ~* (ngsw-worker\.js|ngsw\.json|safety-worker\.js|worker-basic\.min\.js|manifest\.webmanifest)$ {
add_header Cache-Control "no-cache, no-store, must-revalidate";
try_files $uri =404;
}
# Pass /api through to the backend container (name "api" in compose).
location /api/ {
proxy_pass http://api:8080;
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;
# Preserve the scheme Traefik announced to the outside world
# (https) instead of overwriting it with nginx's own $scheme (http).
proxy_set_header X-Forwarded-Proto $http_x_forwarded_proto;
proxy_read_timeout 60s;
}
# OIDC callback paths handled by the backend. Need the same forwarded
# headers as /api/ so the OIDC middleware validates the callback against
# the public host/scheme and the AuthenticationProperties round-trip works.
location = /signin-oidc {
proxy_pass http://api:8080;
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 $http_x_forwarded_proto;
}
location = /signout-callback-oidc {
proxy_pass http://api:8080;
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 $http_x_forwarded_proto;
}
location = /healthz {
access_log off;
return 200 "ok\n";
add_header Content-Type text/plain;
}
# ── Locale routing (i18n subpath layout) ────────────────────────────────
# Build emits two bundles: dist/.../browser/es/ and dist/.../browser/en/.
# The Dockerfile copies the entire `browser/` tree to /usr/share/nginx/html
# so the container ends up with /usr/share/nginx/html/{es,en}/index.html.
#
# Root visit: pick es or en from Accept-Language and redirect. Anyone
# bookmarked at /es/... or /en/... hits the SPA below directly.
location = / {
# Negotiate locale from Accept-Language. Defaults to es-ES (the
# source locale) for any browser that doesn't explicitly prefer
# English. Add more `if` branches as locales grow.
if ($http_accept_language ~* "^en") {
return 302 /en/;
}
return 302 /es/;
}
# SPA fallback per locale: any unknown path under /es/ or /en/ falls
# through to that bundle's index.html so the Angular router takes over.
location /es/ {
try_files $uri $uri/ /es/index.html;
}
location /en/ {
try_files $uri $uri/ /en/index.html;
}
# Catch-all for unprefixed paths. Anything we didn't match above (and
# that isn't /api/ or one of the OIDC callbacks) is a stray client
# request — typically a stale `returnUrl` like `/home` from before the
# /es//en/ split. Redirect it through to the Spanish bundle so the
# SPA can take over instead of nginx returning its default 404.
location / {
if ($http_accept_language ~* "^en") { return 302 /en$request_uri; }
return 302 /es$request_uri;
}
}