Nginx
Jspreadsheet Server listens on one port and speaks both HTTP (REST API) and WebSocket (real-time sync) on it. Nginx sits in front for TLS termination, domain routing and rate limiting. The WebSocket upgrade must be configured, because without the Upgrade/Connection headers the Socket.IO handshake falls back or fails.
Dedicated subdomain
In the recommended layout the server owns a whole hostname. This is the configuration used in the deployment guide:
Server block with WebSocket upgrade
server {
listen 443 ssl;
server_name sheets.example.com;
ssl_certificate /etc/letsencrypt/live/sheets.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/sheets.example.com/privkey.pem;
client_max_body_size 100M;
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;
proxy_read_timeout 3600s;
}
}
Read timeout for idle WebSockets
Set proxy_read_timeout explicitly: idle WebSocket connections are long-lived, and the default 60s would disconnect inactive collaborators.
Path prefix on an existing site
To mount the server under a path (e.g. https://example.com/sheets/) alongside an existing application:
Location block with prefix stripping
location /sheets/ {
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;
proxy_read_timeout 3600s;
}
The trailing slash on proxy_pass strips the /sheets prefix before forwarding.
Client configuration
Point the client at the prefixed URL:
const remote = client.connect({
url: 'https://example.com/sheets',
auth: { token },
});
Rate limiting
The server serializes operations per document, but auth-failure storms and anonymous Formify submissions are best throttled at the proxy:
Throttle zone for the API
limit_req_zone $binary_remote_addr zone=sheets_api:10m rate=30r/s;
location /api/ {
limit_req zone=sheets_api burst=60 nodelay;
proxy_pass http://127.0.0.1:3000;
# ... same proxy headers as above
}
Notes
Use TLS in production
Tokens travel in the WebSocket handshake query string and in Bearer headers. See the security checklist.
Configure CORS on the server
Set the allowed origins in the server's config.cors option rather than adding Access-Control-Allow-Origin headers in Nginx, because setting both produces invalid duplicate headers.
Multiple instances
Do not round-robin. One server process owns each live document, so upstream balancing must route by document guid. The topology is explained in Scaling.
What's Next?
- Deployment: the full production setup this proxy fronts.
- Scaling: guid routing across multiple instances.