# Define cache for API responses proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=api_cache:10m max_size=100m inactive=60m use_temp_path=off; # Rate limiting (per IP) — must be at http level, not inside server block limit_req_zone $binary_remote_addr zone=api_limit:10m rate=20r/m; limit_req_zone $binary_remote_addr zone=dav_limit:10m rate=100r/m; server { listen 80; listen 443 ssl http2; server_name _; # TLS certs (you'll mount them into /etc/nginx/certs) ssl_certificate /etc/nginx/certs/fullchain.pem; ssl_certificate_key /etc/nginx/certs/privkey.pem; # Security headers add_header X-Content-Type-Options nosniff; add_header X-Frame-Options DENY; add_header X-XSS-Protection "1; mode=block"; add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self'; connect-src 'self'; frame-ancestors 'none';" always; # Serve static frontend root /usr/share/nginx/html; index index.html; # Cache immutable assets aggressively location ~ ^/_app/immutable/ { expires 1y; add_header Cache-Control "public, immutable"; } # Normal frontend routes (SPA fallback) location / { try_files $uri /index.html; } # Proxy API requests to backend location /api/ { limit_req zone=api_limit burst=5 nodelay; proxy_pass http://backend: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 $scheme; # Optional lightweight caching for API responses proxy_cache api_cache; proxy_cache_valid 200 1m; proxy_cache_valid any 10s; } # SabreDAV - CardDAV and CalDAV location /dav/ { limit_req zone=dav_limit burst=20 nodelay; # Important: rewrite to remove /dav prefix for PHP processing rewrite ^/dav/(.*)$ /server.php/$1 break; # Pass to PHP-FPM in sabredav container fastcgi_pass sabredav:9000; fastcgi_index server.php; fastcgi_split_path_info ^(.+\.php)(/.+)$; include fastcgi_params; fastcgi_param SCRIPT_FILENAME /var/www/dav/server.php; fastcgi_param PATH_INFO $fastcgi_path_info; fastcgi_param REQUEST_URI $request_uri; # Required for DAV fastcgi_param HTTPS $https if_not_empty; fastcgi_read_timeout 300; fastcgi_buffering off; # Disable caching for DAV proxy_cache off; add_header Cache-Control "no-store, no-cache, must-revalidate"; # Allow DAV methods if ($request_method = 'OPTIONS') { add_header 'Access-Control-Allow-Origin' '$http_origin'; add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS, PUT, DELETE, PROPFIND, PROPPATCH, REPORT, MKCOL, MOVE, COPY'; add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-None-Match,If-Modified-Since,Cache-Control,Content-Type,Range,Depth,Authorization,If-Match,Destination,Overwrite,Lock-Token,Timeout'; add_header 'Access-Control-Max-Age' 1728000; add_header 'Content-Type' 'text/plain; charset=utf-8'; add_header 'Content-Length' 0; return 204; } # Remove security headers that interfere with DAV add_header X-Content-Type-Options "" always; add_header X-Frame-Options "" always; add_header X-XSS-Protection "" always; } # Legacy CardDAV endpoint (backward compatibility) location /carddav/ { return 301 $scheme://$host/dav/addressbooks$request_uri; } # Legacy CalDAV endpoint (backward compatibility) location /caldav/ { return 301 $scheme://$host/dav/calendars$request_uri; } }