Files
potato-mesh/deploy/nginx.example.conf
T

110 lines
5.2 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Copyright © 2025-26 l5yth & contributors
# Licensed under the Apache License, Version 2.0 (see LICENSE)
#
# Example nginx reverse proxy for a bare-metal PotatoMesh web deployment.
# Terminates TLS, forwards to the Puma app, and serves /assets/ straight from
# disk with SPEC AV4-aligned cache headers. Copy the relevant blocks into your
# nginx config, replace every <PLACEHOLDER>, then `nginx -t && systemctl reload nginx`.
#
# DOCKER NOTE: the container image bakes assets into /app/public with no host
# volume, so the `location /assets/` disk-serving block below does NOT apply to
# the Compose/GHCR deployment. There, get immutable asset caching from the app
# itself (see https://github.com/l5yth/potato-mesh/issues/870) or bind-mount
# web/public into an nginx sidecar. The TLS + proxy parts apply to any deployment.
# ---------------------------------------------------------------------------
# http { } context — place alongside your other http-level directives
# ---------------------------------------------------------------------------
# Persistent connections to the app so each request doesn't reopen a TCP socket.
# Requires `proxy_http_version 1.1` + `proxy_set_header Connection ""` below.
upstream potatomesh {
server 127.0.0.1:41447; # <APP_HOST:PORT> — match the app's PORT / -p flag
keepalive 32;
}
# Silences the "could not build optimal types_hash" warning once gzip_types grows.
types_hash_max_size 2048;
# ---------------------------------------------------------------------------
# server { } context
# ---------------------------------------------------------------------------
server {
listen 443 ssl;
listen [::]:443 ssl;
http2 on;
server_name <mesh.example.org>;
client_max_body_size 50m;
# --- TLS (Certbot-managed example) -------------------------------------
# Certbot's options-ssl-nginx.conf sets `ssl_session_tickets off` (forward
# secrecy) and its own ssl_session_cache. Do NOT add `ssl_session_tickets on;`
# in this block — the include already sets it, and a second one is a fatal
# duplicate-directive error. Leaving tickets off means TLS 1.3 can't resume
# (~1 RTT on cold connections); only enable it with rotated
# ssl_session_ticket_key files if that RTT genuinely matters to you.
ssl_certificate /etc/letsencrypt/live/<mesh.example.org>/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/<mesh.example.org>/privkey.pem;
include /etc/letsencrypt/options-ssl-nginx.conf;
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
# --- Static assets straight from disk (bare-metal only — see DOCKER NOTE) --
# Versioned JS/CSS carry ?v=<APP_VERSION> and the whole module graph is
# remapped via <script type="importmap"> (SPEC AV2–AV4), so they are safe to
# cache immutably for a year. Images/icons/fonts are NOT versioned, so they
# keep a short TTL + revalidation (AV4: a stale logo is cosmetic).
#
# The nginx worker user (http / www-data) must be able to traverse to and
# read this directory. Verify with:
# namei -l <root>/assets/styles/base.css # every parent needs o+x
location /assets/ {
root /opt/potato-mesh/web/public; # <path to web/public>; /assets/x → <root>/assets/x
access_log off;
gzip on;
gzip_vary on;
gzip_comp_level 5;
gzip_types text/css text/javascript application/javascript image/svg+xml;
# Optional: precompress *.js/*.css at release time and add `gzip_static on;`
# for max compression at zero per-request CPU.
# Versioned executable/style assets → immutable for a year.
location ~* \.(?:js|mjs|css)$ {
add_header Cache-Control "public, max-age=31536000, immutable";
add_header Access-Control-Allow-Origin "*" always; # add_header is not inherited into nested locations
}
# Unversioned images/icons/fonts → short TTL, always revalidate.
location ~* \.(?:png|svg|ico|jpe?g|gif|webp|woff2?)$ {
add_header Cache-Control "public, max-age=3600, must-revalidate";
add_header Access-Control-Allow-Origin "*" always;
}
}
# --- Everything else → the Puma app ------------------------------------
location / {
proxy_pass http://potatomesh;
proxy_http_version 1.1; # required for upstream keepalive
proxy_set_header Connection ""; # clear hop-by-hop header so sockets are reused
# The app derives its public scheme/host (INSTANCE_DOMAIN, metadata,
# sitemap, federation links) from these — set them or vanity/OpenGraph
# URLs resolve to the wrong scheme/host.
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;
# Live-update SSE (GET /api/events) holds a long-lived streaming response.
proxy_read_timeout 3600;
proxy_send_timeout 3600;
}
}
# --- Redirect plain HTTP to HTTPS ------------------------------------------
server {
listen 80;
listen [::]:80;
server_name <mesh.example.org>;
return 301 https://$host$request_uri;
}