Nginx Reverse Proxy
Nginx Reverse Proxy
Migration in progress.
api.legendary-arena.comand its PostgreSQL are moving off Render onto a self-hosted DigitalOcean Ubuntu droplet fronted by Cloudflare, perdocs/PLAN.md. Until decommission, Render stays warm as the rollback target.
Summary
This page stands up Nginx as the plain-HTTP front door to the game server, proxying api.legendary-arena.com to the app on the loopback interface. It is deliberately the port-80 stage; TLS and port 443 are layered on afterward by the Cloudflare TLS step, which rewrites the same site file.
Mechanics
The owning artifacts are infra/nginx/api.conf
(the template) and infra/scripts/20-nginx.ps1
(the installer, root-required, Set-StrictMode/stop). The config proxies everything to the app and carries the WebSocket upgrade headers:
server {
listen 80;
server_name api.legendary-arena.com;
location / {
proxy_pass http://127.0.0.1:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
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;
}
location = /health {
proxy_pass http://127.0.0.1:3000/health;
}
}
The installer runs in order:
Install. Installs the
nginxpackage.Render / template config. Renders
api.confinto/etc/nginx/sites-available/la-api.conf, substitutingserver_namefromDOMAIN(defaultapi.legendary-arena.com) and the upstream port fromUPSTREAM_PORT(default3000):DOMAIN = api.legendary-arena.com UPSTREAM_PORT = 3000 # proxy_pass http://127.0.0.1:3000Enable site. Symlinks the file into
sites-enabledand removes the packaged default site.Validate. Runs
nginx -t.Reload. Enables and reloads nginx.
The proxy_http_version 1.1 line plus Upgrade/Connection "upgrade" are what carry the Socket.IO WebSocket handshake. Per docs/PLAN.md
, the app is boardgame.io over Socket.IO listening on 127.0.0.1:3000, and WebSockets must pass the proxy — without these headers, gameplay dies silently even though plain HTTP requests look healthy.
Interactions
- Cloudflare TLS
— the
21step rewritesla-api.confto add the 443 server block and Origin Certificate; this page must land first. - Deploy Server
— provides the
127.0.0.1:3000upstream this proxy forwards to; without it the proxy returns 502. - UFW Firewall — must permit inbound 80 (and later 443) or the proxy is unreachable from the edge.
Edge Cases
- Missing upgrade headers → silent WebSocket failure. Drop
proxy_http_version 1.1or theUpgrade/Connectionheaders and Socket.IO gameplay fails quietly while ordinary HTTP still returns 200. - Order before TLS. This must precede the Cloudflare TLS step, which overwrites
la-api.confwith the 443 server block. Running them out of order loses the TLS config. - Upstream must be listening. If the app is not up on
127.0.0.1:3000, every proxied request 502s — deploy the server before validating. - Default site shadows
server_name. Removing the packaged default site is required; left in place it can capture requests ahead ofla-api.conf.
Execute
# Default install (api.legendary-arena.com, upstream 3000)
sudo pwsh -File infra/scripts/20-nginx.ps1
# With explicit overrides (documented defaults shown)
sudo DOMAIN=api.legendary-arena.com UPSTREAM_PORT=3000 pwsh -File infra/scripts/20-nginx.ps1
Verify
sudo nginx -t
# Expected: syntax is ok / test is successful
sudo systemctl status nginx --no-pager
# Expected: active (running)
curl -I http://127.0.0.1/health
# Expected: HTTP/1.1 200 OK
References
infra/nginx/api.conf— the proxy config template.infra/scripts/20-nginx.ps1— the owning installer artifact.docs/PLAN.md— app posture (Socket.IO on 127.0.0.1:3000, WebSockets through the proxy).content/web/nginx-reverse-proxy.md— source content for this page.- Cloudflare TLS — the follow-on step that adds 443.