nginx.conf 2.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778
  1. # nginx server block for bchanot.fr static site.
  2. # Container listens on port 80; host port is configured via docker-compose
  3. # (PORT env var). A host-level reverse proxy (nginx, Traefik, Caddy) should
  4. # terminate TLS and proxy_pass to http://127.0.0.1:${PORT}.
  5. server {
  6. listen 80;
  7. listen [::]:80;
  8. server_name _;
  9. root /usr/share/nginx/html;
  10. index index.html;
  11. # Security headers. HSTS is intentionally NOT set here — leave it to the
  12. # outer reverse proxy that terminates TLS, otherwise it may be sent over
  13. # plain HTTP between proxy and container.
  14. add_header X-Content-Type-Options "nosniff" always;
  15. add_header X-Frame-Options "SAMEORIGIN" always;
  16. add_header Referrer-Policy "strict-origin-when-cross-origin" always;
  17. add_header Permissions-Policy "geolocation=(), microphone=(), camera=(), payment=()" always;
  18. # CSP: inline CSS + JS are allowed (project convention), fonts from Google.
  19. add_header Content-Security-Policy "default-src 'self'; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; font-src 'self' https://fonts.gstatic.com; script-src 'self' 'unsafe-inline'; img-src 'self' data:; base-uri 'self'; form-action 'self'; frame-ancestors 'self'" always;
  20. # Forwarded headers — trust the upstream reverse proxy.
  21. real_ip_header X-Forwarded-For;
  22. set_real_ip_from 0.0.0.0/0;
  23. # Compression.
  24. gzip on;
  25. gzip_vary on;
  26. gzip_min_length 1024;
  27. gzip_proxied any;
  28. gzip_comp_level 6;
  29. gzip_types
  30. text/plain
  31. text/css
  32. text/html
  33. text/javascript
  34. application/javascript
  35. application/json
  36. application/xml
  37. application/pdf
  38. image/svg+xml;
  39. # Long cache for the PDF (regenerated rarely, content-hash not used).
  40. location ~* \.pdf$ {
  41. expires 7d;
  42. add_header Cache-Control "public, max-age=604800";
  43. }
  44. # Short cache for HTML so content updates land fast.
  45. location ~* \.html$ {
  46. expires 1h;
  47. add_header Cache-Control "public, max-age=3600, must-revalidate";
  48. }
  49. # Long cache for favicon + image assets (rarely change).
  50. location ~* \.(?:ico|svg|png|jpg|jpeg|gif|webp)$ {
  51. expires 30d;
  52. add_header Cache-Control "public, max-age=2592000, immutable";
  53. access_log off;
  54. }
  55. # Logs to stdout/stderr (default in nginx:alpine).
  56. access_log /var/log/nginx/access.log;
  57. error_log /var/log/nginx/error.log warn;
  58. # Block access to dotfiles (defense-in-depth — none are shipped anyway).
  59. location ~ /\. {
  60. deny all;
  61. return 404;
  62. }
  63. # Default: serve files, fall back to 404.
  64. location / {
  65. try_files $uri $uri/ =404;
  66. }
  67. }