-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathnginx.conf
More file actions
108 lines (96 loc) · 5.08 KB
/
nginx.conf
File metadata and controls
108 lines (96 loc) · 5.08 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
# nginx config for the unified instanode.dev site.
#
# - Serves all React routes through index.html (SPA fallback).
# - Forces correct Content-Type for llms.txt / llms-full.txt (text/plain).
# - Caches static assets aggressively but never the entry HTML.
# - Proxies /api/, /auth/, /claim, /db/new, /cache/new, /nosql/new, /queue/new,
# /storage/new, /webhook/new, and /.well-known/ to api.instanode.dev so
# the dashboard's same-origin fetches work in production.
server {
listen 80;
listen [::]:80;
server_name _;
root /usr/share/nginx/html;
index index.html;
# Don't expose server version.
server_tokens off;
# ── Security headers (B6-P2-12, 2026-05-20) ─────────────────────────
# Defence-in-depth: CSP + X-Frame-Options + Referrer-Policy on every
# response. Inline + unsafe-eval allowed because:
# - Vite's prerender emits inline <script> blocks for SSR hydration
# - the New Relic browser agent boots inline at the top of <head>
# - JSON-LD blog metadata is inline <script type="application/ld+json">
# Connect-src allows the agent API + NR ingest so dashboard fetches
# and RUM beacons aren't broken. add_header inherits down — it must
# be set at server-level OR repeated in every block, otherwise nested
# location blocks lose it. We set it here once for that reason.
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval' https://js-agent.newrelic.com https://bam.nr-data.net https://checkout.razorpay.com; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' data:; connect-src 'self' https://api.instanode.dev https://*.nr-data.net https://*.newrelic.com https://api.razorpay.com wss://api.instanode.dev; frame-src https://checkout.razorpay.com; object-src 'none'; base-uri 'self'; form-action 'self' https://api.instanode.dev;" always;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Permissions-Policy "geolocation=(), microphone=(), camera=()" always;
# Compression.
gzip on;
gzip_types text/plain text/css application/javascript application/json image/svg+xml;
gzip_min_length 1024;
# ── llms.txt / llms-full.txt — must be served as text/plain ──────────
location = /llms.txt {
default_type text/plain;
add_header Cache-Control "public, max-age=300, must-revalidate";
}
location = /llms-full.txt {
default_type text/plain;
add_header Cache-Control "public, max-age=300, must-revalidate";
}
location = /robots.txt {
default_type text/plain;
add_header Cache-Control "public, max-age=3600, must-revalidate";
}
location = /sitemap.xml {
default_type application/xml;
add_header Cache-Control "public, max-age=3600, must-revalidate";
}
# ── API proxy to api.instanode.dev ──────────────────────────────────
# The dashboard uses same-origin fetches; here we forward them
# server-side so the browser never sees the cross-origin host.
location ~ ^/(api|auth|claim|db|cache|nosql|queue|storage|webhook)(/|$) {
proxy_pass https://api.instanode.dev;
proxy_http_version 1.1;
proxy_set_header Host api.instanode.dev;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_ssl_server_name on;
proxy_read_timeout 300s;
}
location ^~ /.well-known/ {
proxy_pass https://api.instanode.dev;
proxy_http_version 1.1;
proxy_set_header Host api.instanode.dev;
proxy_ssl_server_name on;
}
# ── Static asset caching ────────────────────────────────────────────
location /assets/ {
# Vite emits hashed filenames; cache forever.
add_header Cache-Control "public, max-age=31536000, immutable";
try_files $uri =404;
}
location ~* \.(?:png|jpg|jpeg|svg|webp|ico|woff2?)$ {
add_header Cache-Control "public, max-age=86400";
try_files $uri =404;
}
# ── SPA fallback ────────────────────────────────────────────────────
# Every unmatched route falls through to index.html so React Router
# owns it. Critical: index.html itself must NOT be cached.
location / {
add_header Cache-Control "no-cache, no-store, must-revalidate";
add_header Pragma "no-cache";
add_header Expires "0";
try_files $uri $uri/ /index.html;
}
# Health endpoint for k8s probes.
location = /healthz {
access_log off;
default_type text/plain;
return 200 "ok\n";
}
}