Put Nginx in front
The other perfectly reasonable reverse proxy. It offers precise control and enough semicolons to make a JavaScript formatter feel underemployed.
Use the same private backend
Use this instead of the Caddy setup. The notebook listens on loopback; Nginx owns the public ports. Run from the repository root:
# Run from the repository root with the notebook dependencies installed.
weft example_app.asgi:app --host 127.0.0.1 --port 8000 \
--lifespan on --forwarded-allow-ips 127.0.0.1The app trusts forwarding information from 127.0.0.1, not from arbitrary clients. Keep port 8000 private and forward the external scheme correctly.
Configure HTTPS and the proxy
Put these server blocks in a file included by Nginx’s http context. Replace the hostname and provision real certificate and key files at the configured paths first. The Nginx HTTPS guide covers TLS configuration. Unlike the Caddy example, these certificate paths do not cause certificates to appear by force of optimism.
# Include these server blocks inside nginx's http context.
# Replace the domain and certificate paths before enabling.
server {
listen 80;
server_name notes.example.com;
return 308 https://$host$request_uri;
}
server {
listen 443 ssl;
server_name notes.example.com;
ssl_certificate /etc/letsencrypt/live/notes.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/notes.example.com/privkey.pem;
client_max_body_size 1m;
client_body_timeout 15s;
location / {
proxy_pass http://127.0.0.1:8000;
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_connect_timeout 5s;
proxy_read_timeout 60s;
proxy_send_timeout 60s;
}
}proxy_pass has no URI suffix, so it preserves paths such as /api/notes. The forwarded client header is overwritten with the peer address for this single-proxy setup. Host and scheme match the public request.
client_max_body_size limits uploads before the application buffers them. The timeout values are starting points; choose values that match your workload.
Check the config before reloading
sudo nginx -t
sudo systemctl reload nginx
curl -i https://notes.example.com/api/healthCheck login through the public domain too. A 403 from the notebook’s origin check often means the forwarded scheme or Host is wrong. Don’t fix it by deleting the origin check and declaring victory.
Add a proxy rate limit when you need one
Nginx’s request limiter can share its zone across Nginx workers on one host. For an optional general API limit, add this declaration once in the http context:
limit_req_zone $binary_remote_addr zone=wred_clients:10m rate=10r/s;Then add the following inside the existing location / block, alongside the proxy directives:
limit_req zone=wred_clients burst=20 nodelay;
limit_req_status 429;These numbers are illustrative, not a capacity plan delivered by a stranger’s HTML page. This general limit covers static files too; split locations for API-only or tighter login limits and preserve the required proxy settings. See the request-limit module.
Let event streams reach the client
For the independent events example, buffering can hide incremental output. In a dedicated location /events, include your normal proxy settings and add:
proxy_buffering off;
proxy_read_timeout 300s;Choose a timeout longer than the interval between stream messages. Don’t disable buffering globally just because one endpoint needs it; ordinary responses have done nothing to deserve being dragged into this.
