# Прод-конфиг веба: статика + прокси API на контейнер api внутри сети # compose (строка api резолвится docker- DNS). Порт наружу — 80, TLS # терминалирует перед этим nginx (балансировщик / Caddy / Traefik). # # Отличия от dev nginx.conf: # - proxy_pass http://api:8080 вместо host.docker.internal:8080; # - security-заголовки (nosniff / frame / referrer) поверх HSTS. # # Хост, который не относится к нашему origin (Host-заголовок чужой), # nginx отдаёт сюда же — server_name _; выделенного виртуального хоста # под чужой домен нет, это осознанно. # Зоны лимитов: 10 запросов/сек на IP и до 20 одновременных соединений. limit_req_zone $binary_remote_addr zone=perip:1m rate=10r/s; limit_conn_zone $binary_remote_addr zone=addr:1m; server { listen 80; server_name _; root /usr/share/nginx/html; index index.html; gzip on; gzip_types text/css application/javascript application/json; gzip_min_length 1024; # HSTS браузер игнорирует по HTTP, а с TLS заставит держать сайт # только по https; includeSubDomains — API и статика на одном домене. # ВНИМАНИЕ: add_header на уровне server перекрывается ЛЮБЫМ # add_header внутри location — поэтому в location / заголовки # продублированы явно. add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always; add_header X-Content-Type-Options "nosniff" always; add_header X-Frame-Options "SAMEORIGIN" always; add_header Referrer-Policy "strict-origin-when-cross-origin" always; # burst — на пачку файлов при первой загрузке страницы, сверх # лимита — сразу 429, а не неопределённое ожидание. limit_req zone=perip burst=30 nodelay; limit_req_status 429; limit_conn addr 20; limit_conn_status 429; # API на том же origin (BASE='' в js/api.js): проксируем API-пути # бэкенда, остальное — статика. Regex-локация важнее prefix /. # X-Forwarded-For ПЕРЕЗАПИСЫВАЕМ ($remote_addr), а не дополняем: # gin берёт левый элемент и по нему считает лимиты и client_ip в # логах — дописанный клиентом мусор останется левым элементом. location ~ ^/(auth|users|projects|objects|tickets|invitations|recurring|ping|db-ping|swagger)(/|$) { proxy_pass http://api:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $remote_addr; proxy_set_header X-Forwarded-Proto $scheme; # Фотоотчёт: до 10 МБ + запас на multipart-обвязку. client_max_body_size 12m; } location / { try_files $uri $uri/ /index.html; # Кэш мешает отладке: отдаём всегда свежее. # Дублируем заголовки: add_header в location перекрывает серверный. add_header Cache-Control "no-cache"; add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always; add_header X-Content-Type-Options "nosniff" always; add_header X-Frame-Options "SAMEORIGIN" always; add_header Referrer-Policy "strict-origin-when-cross-origin" always; } }