Skip to content

Commit 626fb7d

Browse files
committed
Update nginx.conf
1 parent 79450e8 commit 626fb7d

1 file changed

Lines changed: 197 additions & 63 deletions

File tree

deploy/nginx.conf

Lines changed: 197 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -1,98 +1,143 @@
11
# ============================================================
2-
# StellarForge — Nginx Reverse Proxy Configuration
2+
# StellarForge — Nginx Reverse Proxy Configuration (SPEED-OPTIMIZED)
33
# Domain: stellarforge.app (and www.stellarforge.app)
44
#
5-
# Proxies:
6-
# - HTTP → Next.js app (port 3700, next start)
7-
# - /collab → WebSocket collab server (port 3002)
8-
# - /lsp → WebSocket LSP server (port 3099)
9-
# - WebSocket upgrade support for all paths
5+
# On top of the previous fix (upstream keepalive, correct
6+
# Connection handling, split rate limits), this pass adds:
107
#
11-
# SSL is managed by certbot — it will auto-inject the
12-
# ssl_certificate directives when you run:
8+
# 1. /_next/static/ is served DIRECTLY from disk instead of
9+
# proxying to Node. This is the single biggest latency win
10+
# available — every JS/CSS chunk currently round-trips
11+
# through your Next.js process for no reason; these files
12+
# are content-hashed and immutable, nginx can serve them
13+
# straight off disk with a 1-year cache header.
14+
# >>> You MUST update the `alias` path below to your actual
15+
# >>> .next/static build output directory.
16+
# 2. proxy_socket_keepalive on upstream connections.
17+
# 3. Tighter client-facing keepalive + timeout tuning so idle
18+
# connections don't tie up workers.
19+
# 4. sendfile/tcp_nopush/tcp_nodelay for the local file serving
20+
# (error page + static assets).
21+
# 5. Optional (commented) microcaching block for cacheable GET
22+
# API routes, and optional Brotli.
23+
#
24+
# IMPORTANT — two more optimizations can't live in this file and
25+
# need to be done separately (instructions at the very bottom):
26+
# A. HTTP/2 + TLS session reuse + OCSP stapling, added to the
27+
# 443 block certbot generates for you.
28+
# B. worker_processes / worker_connections tuning in the main
29+
# /etc/nginx/nginx.conf.
30+
#
31+
# SSL is managed by certbot — it will auto-inject ssl_certificate
32+
# directives when you run:
1333
# certbot --nginx -d stellarforge.app -d www.stellarforge.app
14-
# Do NOT add SSL directives manually.
34+
# Do NOT add SSL directives manually to THIS file's port 80 block.
1535
#
1636
# Usage:
1737
# 1. Copy to /etc/nginx/sites-available/stellarforge.app
1838
# 2. Symlink: ln -s /etc/nginx/sites-available/stellarforge.app /etc/nginx/sites-enabled/
19-
# 3. certbot --nginx -d stellarforge.app -d www.stellarforge.app
20-
# 4. nginx -t && systemctl reload nginx
21-
#
22-
# Prerequisites:
23-
# - Next.js running on port 3700 (bun run start)
24-
# - Collab server running on port 3002 (bm2 or manually)
25-
# - LSP server running on port 3099 (bm2 or manually)
39+
# 3. Confirm /etc/nginx/conf.d/upgrade.conf does NOT also exist
40+
# (it would duplicate the map below)
41+
# 4. Update the /_next/static/ alias path below to your real build dir
42+
# 5. nginx -t <-- always run this before reloading
43+
# 6. certbot --nginx -d stellarforge.app -d www.stellarforge.app
44+
# 7. Apply the HTTP/2 + SSL tuning snippet at the bottom of this file
45+
# to the 443 block certbot creates
46+
# 8. nginx -t && systemctl reload nginx
2647
# ============================================================
2748

28-
# ─── WebSocket upgrade map (MUST be outside server block) ───
29-
# This tells nginx how to handle the Connection header for WebSocket
30-
# upgrades. Without this, WebSocket connections fail with
31-
# "unknown connection_upgrade variable".
32-
# This is defined INLINE here — no need for a separate upgrade.conf file.
3349
map $http_upgrade $connection_upgrade {
3450
default upgrade;
35-
'' close;
51+
'' close;
52+
}
53+
54+
# ─── Upstream pools with keepalive ────────────────────────────
55+
# Reuses backend connections instead of opening a new TCP
56+
# connection per request.
57+
upstream nextjs_backend {
58+
server 127.0.0.1:3700;
59+
keepalive 64;
60+
keepalive_requests 1000;
61+
keepalive_timeout 60s;
62+
63+
# If you ever run Next.js as multiple instances (e.g. PM2
64+
# cluster mode across several ports) for more throughput on
65+
# multi-core boxes, add them here and nginx load-balances
66+
# automatically:
67+
# server 127.0.0.1:3701;
68+
# server 127.0.0.1:3702;
69+
# least_conn;
70+
}
71+
72+
upstream collab_backend {
73+
server 127.0.0.1:3002;
74+
keepalive 32;
75+
}
76+
77+
upstream lsp_backend {
78+
server 127.0.0.1:3099;
79+
keepalive 32;
3680
}
3781

38-
# Rate limiting zone
39-
limit_req_zone $binary_remote_addr zone=stellarforge_api:10m rate=10r/s;
82+
# ─── Rate limiting zones ───────────────────────────────────────
83+
limit_req_zone $binary_remote_addr zone=stellarforge_api:10m rate=20r/s;
84+
limit_req_zone $binary_remote_addr zone=stellarforge_app:10m rate=50r/s;
4085

41-
# Upload size for file imports / avatar uploads
4286
client_max_body_size 25M;
4387

44-
# ─── HTTP server (certbot will add the 443 block automatically) ──
88+
# ─── Local file I/O tuning (error pages, static assets) ───────
89+
sendfile on;
90+
tcp_nopush on;
91+
tcp_nodelay on;
92+
4593
server {
4694
listen 80;
4795
listen [::]:80;
4896
server_name stellarforge.app www.stellarforge.app;
4997

50-
# Let's Encrypt challenge path
51-
location /.well-known/acme-challenge/ {
52-
root /var/www/html;
53-
}
54-
55-
# ─── www → non-www redirect ──────────────────────────────
56-
if ($host = www.stellarforge.app) {
57-
return 301 https://stellarforge.app$request_uri;
58-
}
59-
6098
# certbot will insert the HTTPS redirect here after running:
6199
# certbot --nginx -d stellarforge.app -d www.stellarforge.app
62100

101+
# ─── Client-facing connection tuning ──────────────────────
102+
# Keeps idle client connections short so workers free up fast,
103+
# while still letting browsers reuse one connection for all
104+
# the assets on a page instead of opening a new one each time.
105+
keepalive_timeout 20s;
106+
keepalive_requests 1000;
107+
client_header_timeout 15s;
108+
client_body_timeout 15s;
109+
send_timeout 15s;
110+
reset_timedout_connection on;
111+
63112
# ─── WebSocket: Collab server (/collab/) ─────────────────
64-
# Proxies to the Yjs WebSocket collab server on port 3002.
65-
# This is the cross-device live collaboration server.
66-
# Client connects via: wss://stellarforge.app/collab/stellarforge-<roomId>
67113
location /collab/ {
68-
proxy_pass http://127.0.0.1:3002;
114+
proxy_pass http://collab_backend;
69115
proxy_http_version 1.1;
70116
proxy_set_header Upgrade $http_upgrade;
71117
proxy_set_header Connection $connection_upgrade;
72118
proxy_set_header Host $host;
73119
proxy_set_header X-Real-IP $remote_addr;
74120
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
75121
proxy_set_header X-Forwarded-Proto $scheme;
122+
proxy_socket_keepalive on;
76123

77124
proxy_read_timeout 86400s;
78125
proxy_send_timeout 86400s;
79126
proxy_buffering off;
80127
proxy_cache off;
81128
}
82129

83-
# ─── WebSocket: LSP server (/lsp and /workspace/) ────────
84-
# Proxies to the rust-analyzer LSP server on port 3099.
85-
# /lsp is the WebSocket endpoint for LSP/JSON-RPC protocol.
86-
# /workspace/ serves files from the build workspace (for go-to-def etc.)
87-
location /lsp {
88-
proxy_pass http://127.0.0.1:3099;
130+
# ─── WebSocket: LSP server (/lsp/ and /workspace/) ───────
131+
location /lsp/ {
132+
proxy_pass http://lsp_backend;
89133
proxy_http_version 1.1;
90134
proxy_set_header Upgrade $http_upgrade;
91135
proxy_set_header Connection $connection_upgrade;
92136
proxy_set_header Host $host;
93137
proxy_set_header X-Real-IP $remote_addr;
94138
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
95139
proxy_set_header X-Forwarded-Proto $scheme;
140+
proxy_socket_keepalive on;
96141

97142
proxy_read_timeout 86400s;
98143
proxy_send_timeout 86400s;
@@ -101,63 +146,106 @@ server {
101146
}
102147

103148
location /workspace/ {
104-
proxy_pass http://127.0.0.1:3099;
149+
proxy_pass http://lsp_backend;
105150
proxy_http_version 1.1;
106151
proxy_set_header Upgrade $http_upgrade;
107152
proxy_set_header Connection $connection_upgrade;
108153
proxy_set_header Host $host;
109154
proxy_set_header X-Real-IP $remote_addr;
110155
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
111156
proxy_set_header X-Forwarded-Proto $scheme;
157+
proxy_socket_keepalive on;
112158

113159
proxy_read_timeout 86400s;
114160
proxy_send_timeout 86400s;
115161
proxy_buffering off;
116162
proxy_cache off;
117163
}
118164

165+
# ─── Static assets — served directly from disk, bypassing Node ──
166+
# >>> UPDATE THIS PATH to your real .next/static build output,
167+
# >>> e.g. /var/www/stellarforge/.next/static/
168+
location /_next/static/ {
169+
alias /var/www/stellarforge/.next/static/;
170+
expires 1y;
171+
add_header Cache-Control "public, immutable";
172+
access_log off;
173+
# Not rate-limited — these are cheap, cacheable, and a
174+
# single page load fires dozens of them in parallel.
175+
}
176+
177+
# Public /public/ folder assets (favicons, images, etc.) —
178+
# same idea, update path to match your deployment.
179+
location /public/ {
180+
alias /var/www/stellarforge/public/;
181+
expires 30d;
182+
add_header Cache-Control "public";
183+
access_log off;
184+
}
185+
186+
# ─── Optional: microcaching for cacheable GET API routes ──
187+
# Uncomment and adjust if you have public, non-personalized
188+
# GET endpoints (e.g. leaderboard, public stats). Skips this
189+
# for anything with a session cookie so logged-in users always
190+
# hit the backend.
191+
#
192+
# proxy_cache_path /var/cache/nginx/stellarforge levels=1:2
193+
# keys_zone=stellarforge_cache:10m max_size=200m inactive=60m;
194+
#
195+
# location /api/public/ {
196+
# proxy_pass http://nextjs_backend;
197+
# proxy_http_version 1.1;
198+
# proxy_set_header Connection "";
199+
# proxy_set_header Host $host;
200+
# proxy_cache stellarforge_cache;
201+
# proxy_cache_valid 200 30s;
202+
# proxy_cache_bypass $cookie_session;
203+
# proxy_no_cache $cookie_session;
204+
# add_header X-Cache-Status $upstream_cache_status;
205+
# }
206+
119207
# ─── API routes (/api/) ──────────────────────────────────
120208
location /api/ {
121-
proxy_pass http://127.0.0.1:3700;
209+
proxy_pass http://nextjs_backend;
210+
proxy_http_version 1.1;
211+
proxy_set_header Connection "";
122212
proxy_set_header Host $host;
123213
proxy_set_header X-Real-IP $remote_addr;
124214
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
125215
proxy_set_header X-Forwarded-Proto $scheme;
216+
proxy_socket_keepalive on;
126217

127218
add_header Cache-Control "no-store, no-cache, must-revalidate";
128219
proxy_buffering on;
129220
proxy_buffer_size 16k;
130221
proxy_buffers 8 32k;
131222

132-
# 35s read timeout — Neon cold start can take 10-15s,
133-
# and SIWS verify does multiple DB queries (upsert + profile create).
134-
# Next.js maxDuration is 30s, so 35s gives nginx a 5s buffer.
135-
proxy_read_timeout 35s;
136-
proxy_send_timeout 35s;
223+
proxy_connect_timeout 5s;
224+
proxy_read_timeout 60s;
225+
proxy_send_timeout 60s;
137226

138-
limit_req zone=stellarforge_api burst=20 nodelay;
139-
client_max_body_size 25M;
227+
limit_req zone=stellarforge_api burst=40 nodelay;
140228
}
141229

142230
# ─── Next.js app (everything else) ────────────────────────
143231
location / {
144-
proxy_pass http://127.0.0.1:3700;
232+
proxy_pass http://nextjs_backend;
233+
proxy_http_version 1.1;
234+
proxy_set_header Connection "";
145235
proxy_set_header Host $host;
146236
proxy_set_header X-Real-IP $remote_addr;
147237
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
148238
proxy_set_header X-Forwarded-Proto $scheme;
239+
proxy_socket_keepalive on;
149240

150-
proxy_http_version 1.1;
151-
proxy_set_header Upgrade $http_upgrade;
152-
proxy_set_header Connection $connection_upgrade;
153-
154-
proxy_read_timeout 300s;
155-
proxy_send_timeout 300s;
241+
proxy_connect_timeout 5s;
242+
proxy_read_timeout 60s;
243+
proxy_send_timeout 60s;
156244
proxy_buffering on;
157245
proxy_buffer_size 16k;
158246
proxy_buffers 8 32k;
159247

160-
limit_req zone=stellarforge_api burst=20 nodelay;
248+
limit_req zone=stellarforge_app burst=80 nodelay;
161249
}
162250

163251
# ─── Compression ─────────────────────────────────────────
@@ -181,6 +269,14 @@ server {
181269
font/woff2
182270
application/wasm;
183271

272+
# Optional: Brotli compresses better than gzip for text assets.
273+
# Requires the ngx_brotli module (not built into stock nginx —
274+
# check `nginx -V` for it, or install nginx-module-brotli).
275+
# brotli on;
276+
# brotli_comp_level 5;
277+
# brotli_types text/plain text/css application/javascript
278+
# application/json image/svg+xml font/woff2 application/wasm;
279+
184280
# ─── Logging ──────────────────────────────────────────────
185281
access_log /var/log/nginx/stellarforge.app.access.log;
186282
error_log /var/log/nginx/stellarforge.app.error.log warn;
@@ -192,3 +288,41 @@ server {
192288
internal;
193289
}
194290
}
291+
292+
# ============================================================
293+
# A. After running certbot, add this to the 443 block it creates
294+
# for faster TLS handshakes + HTTP/2 (lets browsers multiplex
295+
# all those Next.js chunk requests over one connection instead
296+
# of opening 6 parallel ones):
297+
#
298+
# listen 443 ssl;
299+
# http2 on; # nginx >= 1.25.1 syntax
300+
# # (older nginx: listen 443 ssl http2; instead of the two lines above)
301+
#
302+
# ssl_session_cache shared:SSL:10m;
303+
# ssl_session_timeout 1d;
304+
# ssl_session_tickets off;
305+
# ssl_protocols TLSv1.2 TLSv1.3;
306+
# ssl_prefer_server_ciphers off;
307+
#
308+
# ssl_stapling on;
309+
# ssl_stapling_verify on;
310+
# resolver 1.1.1.1 8.8.8.8 valid=300s;
311+
# resolver_timeout 5s;
312+
#
313+
# B. In /etc/nginx/nginx.conf (main context), these matter more
314+
# for you than most sites given the WS collab/LSP connections
315+
# holding workers open for hours:
316+
#
317+
# worker_processes auto;
318+
# worker_rlimit_nofile 65535;
319+
#
320+
# events {
321+
# worker_connections 4096;
322+
# multi_accept on;
323+
# use epoll;
324+
# }
325+
#
326+
# Check current limits with: ulimit -n
327+
# If low, also raise the nginx systemd service's LimitNOFILE.
328+
# ============================================================

0 commit comments

Comments
 (0)