diff --git a/docs/astro.config.mjs b/docs/astro.config.mjs index 8262c802..6584c811 100644 --- a/docs/astro.config.mjs +++ b/docs/astro.config.mjs @@ -132,6 +132,7 @@ export default defineConfig({ { label: '用户支持', translations: { en: 'User Support' }, slug: 'support' }, { label: '选择部署方式', translations: { en: 'Choose a Deployment' }, slug: 'install/choose-path' }, { label: 'Docker Compose 安装', translations: { en: 'Install with Docker Compose' }, slug: 'install/docker-compose' }, + { label: 'Nginx 反向代理', translations: { en: 'Nginx Reverse Proxy' }, slug: 'install/nginx' }, { label: 'Helm 部署', translations: { en: 'Helm Deployment' }, slug: 'install/helm' }, { label: '生产部署清单', translations: { en: 'Production Checklist' }, slug: 'install/production-checklist' }, ], diff --git a/docs/src/content/docs/en/install/nginx.mdx b/docs/src/content/docs/en/install/nginx.mdx new file mode 100644 index 00000000..642cb324 --- /dev/null +++ b/docs/src/content/docs/en/install/nginx.mdx @@ -0,0 +1,104 @@ +--- +title: Nginx Reverse Proxy +description: Proxy SyncTV HTTP APIs, Web UI, media responses, and WebSocket through Nginx without proxy caching or response buffering. +--- + +This configuration limits Nginx to TLS termination, HTTP forwarding, and WebSocket upgrades. Every HTTP response, including APIs, Web UI, HLS, and media proxy responses, flows directly from SyncTV to the client. Nginx does not add a second response cache or buffer upstream responses. + +The example assumes: + +- SyncTV listens on `127.0.0.1:8080`. +- The public hostname is `synctv.example.com`. +- TLS certificates exist under `/etc/nginx/tls/`. + +When Nginx runs in a container, replace the upstream address with the SyncTV service name on the same container network, such as `synctv:8080`. + +## Configuration + +```nginx +map $http_upgrade $connection_upgrade { + default upgrade; + '' close; +} + +upstream synctv_http { + server 127.0.0.1:8080; + keepalive 32; +} + +server { + listen 80; + server_name synctv.example.com; + + return 308 https://$host$request_uri; +} + +server { + listen 443 ssl; + server_name synctv.example.com; + + ssl_certificate /etc/nginx/tls/fullchain.pem; + ssl_certificate_key /etc/nginx/tls/privkey.pem; + + # SyncTV owns response caching. Nginx must stream upstream responses + # immediately and must not create a second cache layer. + proxy_cache off; + proxy_buffering off; + + # Long-lived media responses must not be terminated by the proxy while + # SyncTV is still serving data. + proxy_read_timeout 24h; + proxy_send_timeout 24h; + + location ^~ /ws/ { + proxy_pass http://synctv_http; + proxy_http_version 1.1; + + 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_set_header Upgrade $http_upgrade; + proxy_set_header Connection $connection_upgrade; + } + + location / { + proxy_pass http://synctv_http; + proxy_http_version 1.1; + + 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_set_header Connection ""; + } +} +``` + +`proxy_cache off` overrides proxy caching inherited from the Nginx `http` block or another parent configuration. `proxy_buffering off` forwards bytes as Nginx receives them, preventing live streams and large media responses from entering Nginx response buffers or temporary files first. + +Do not hide or replace the `Cache-Control` header returned by SyncTV. That header still controls browser and client caching. The configuration above disables only Nginx's own proxy cache. + +`proxy_request_buffering` controls client request bodies, not upstream responses. Keep its default value to avoid tying up SyncTV connections for slow uploads. Set `proxy_request_buffering off` separately only when a deployment explicitly requires streaming large request bodies into SyncTV. + +## Trusted Proxies + +When Nginx and SyncTV run on the same host, trust only loopback addresses: + +```yaml +server: + trusted_proxies: + - "127.0.0.1/32" + - "::1/128" +``` + +For a container network, replace these entries with the actual Nginx container subnet. Do not configure `0.0.0.0/0` or `::/0`. + +## Validate and Reload + +```bash +nginx -t +systemctl reload nginx +``` + +After reloading, verify sign-in, room WebSocket connections, HLS playback, low-latency media playback, and seeking in large files. Media responses should reach the client as soon as the backend starts sending them. diff --git a/docs/src/content/docs/en/install/production-checklist.mdx b/docs/src/content/docs/en/install/production-checklist.mdx index daed7ded..f45b8414 100644 --- a/docs/src/content/docs/en/install/production-checklist.mdx +++ b/docs/src/content/docs/en/install/production-checklist.mdx @@ -34,7 +34,7 @@ OPAQUE, Provider credentials, and the email outbox use separate long-lived secre | Check | Acceptance criteria | | --- | --- | -| HTTP entrypoint | Reverse proxy or Ingress supports WebSocket upgrade. | +| HTTP entrypoint | The [Nginx reverse proxy](../nginx/) or Ingress supports WebSocket upgrades; Nginx proxy caching and response buffering are disabled. | | CORS | When frontend and API use different origins, `server.cors_allowed_origins` contains only real origins. | | Trusted proxies | `server.trusted_proxies` contains only proxy networks you control. | | gRPC | Public gRPC uses separate Service/Ingress resources from HTTP. | diff --git a/docs/src/content/docs/en/operations/troubleshooting.mdx b/docs/src/content/docs/en/operations/troubleshooting.mdx index 5ea7d2de..8928beae 100644 --- a/docs/src/content/docs/en/operations/troubleshooting.mdx +++ b/docs/src/content/docs/en/operations/troubleshooting.mdx @@ -57,6 +57,7 @@ Start with the first error. Later errors are often cascading failures. | WebSocket 401/403 | Expired token/ticket, missing room membership, or rejected origin | Request a new ticket and verify membership/origin | | OAuth2 callback/state error | Redirect mismatch or multi-replica deployment without shared Redis | Match provider registration and configure Redis for replicas | | WebAuthn/passkey failure | `rp_origin` does not match the real HTTPS origin | Fix `webauthn.rp_origin` and `webauthn.rp_id` | +| Slow video startup or steadily increasing live-stream latency | Nginx is caching or buffering upstream responses | Disable `proxy_cache` and `proxy_buffering` as shown in [Nginx Reverse Proxy](../../install/nginx/) | | Range seek or proxy playback failure | Provider headers are incomplete or upstream lacks Range | Check Provider headers and upstream `Accept-Ranges` | | Intermittent HLS 404 in multi-replica mode | Publisher node unreachable or HLS storage model mismatch | Use publisher-node proxy, `shared_file`, or S3 | diff --git a/docs/src/content/docs/install/nginx.mdx b/docs/src/content/docs/install/nginx.mdx new file mode 100644 index 00000000..4ffc73e9 --- /dev/null +++ b/docs/src/content/docs/install/nginx.mdx @@ -0,0 +1,104 @@ +--- +title: Nginx 反向代理 +description: 使用 Nginx 代理 SyncTV HTTP API、Web UI、媒体响应和 WebSocket,并关闭代理缓存与响应缓冲。 +--- + +这份配置让 Nginx 只负责 TLS 终止、HTTP 转发和 WebSocket Upgrade。所有 HTTP 响应,包括 API、Web UI、HLS 和媒体代理,都直接从 SyncTV 流向客户端。Nginx 不建立第二层响应缓存,也不缓冲后端响应。 + +示例假设: + +- SyncTV 监听 `127.0.0.1:8080`。 +- 公网域名是 `synctv.example.com`。 +- TLS 证书已经存在于 `/etc/nginx/tls/`。 + +Nginx 在容器中运行时,将 upstream 地址改为同一容器网络中的 SyncTV 服务名,例如 `synctv:8080`。 + +## 配置 + +```nginx +map $http_upgrade $connection_upgrade { + default upgrade; + '' close; +} + +upstream synctv_http { + server 127.0.0.1:8080; + keepalive 32; +} + +server { + listen 80; + server_name synctv.example.com; + + return 308 https://$host$request_uri; +} + +server { + listen 443 ssl; + server_name synctv.example.com; + + ssl_certificate /etc/nginx/tls/fullchain.pem; + ssl_certificate_key /etc/nginx/tls/privkey.pem; + + # SyncTV owns response caching. Nginx must stream upstream responses + # immediately and must not create a second cache layer. + proxy_cache off; + proxy_buffering off; + + # Long-lived media responses must not be terminated by the proxy while + # SyncTV is still serving data. + proxy_read_timeout 24h; + proxy_send_timeout 24h; + + location ^~ /ws/ { + proxy_pass http://synctv_http; + proxy_http_version 1.1; + + 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_set_header Upgrade $http_upgrade; + proxy_set_header Connection $connection_upgrade; + } + + location / { + proxy_pass http://synctv_http; + proxy_http_version 1.1; + + 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_set_header Connection ""; + } +} +``` + +`proxy_cache off` 会覆盖从 Nginx `http` 或其他上层配置继承的代理缓存。`proxy_buffering off` 让 Nginx 收到后端数据后立即向客户端发送,避免实时流和大媒体响应先进入 Nginx 内存缓冲区或临时文件。 + +不要隐藏或重写 SyncTV 返回的 `Cache-Control`。该响应头仍负责浏览器和客户端缓存行为;上面的配置只关闭 Nginx 自身的代理缓存。 + +`proxy_request_buffering` 控制客户端请求体,不控制后端响应。保持其默认值可以避免慢速上传长时间占用 SyncTV 连接。只有明确需要把大请求体边接收边传给 SyncTV 时,才单独设置 `proxy_request_buffering off`。 + +## 可信代理 + +Nginx 与 SyncTV 在同一主机时,只信任 loopback 地址: + +```yaml +server: + trusted_proxies: + - "127.0.0.1/32" + - "::1/128" +``` + +使用容器网络时,将这些值替换为实际 Nginx 容器网段。不要配置 `0.0.0.0/0` 或 `::/0`。 + +## 检查并重载 + +```bash +nginx -t +systemctl reload nginx +``` + +重载后验证登录、WebSocket 房间连接、HLS 播放、低延迟媒体播放和大文件拖动。媒体响应应该在后端开始发送后立即到达客户端。 diff --git a/docs/src/content/docs/install/production-checklist.mdx b/docs/src/content/docs/install/production-checklist.mdx index 15582e3b..facdf7bb 100644 --- a/docs/src/content/docs/install/production-checklist.mdx +++ b/docs/src/content/docs/install/production-checklist.mdx @@ -34,7 +34,7 @@ OPAQUE、Provider credential 和 email outbox 使用各自的长期 secret。备 | 检查项 | 验收标准 | | --- | --- | -| HTTP 入口 | 反向代理或 Ingress 支持 WebSocket upgrade。 | +| HTTP 入口 | [Nginx 反向代理](../nginx/)或 Ingress 支持 WebSocket Upgrade;Nginx 代理缓存和响应缓冲已关闭。 | | CORS | 前端和 API 不同 origin 时,`server.cors_allowed_origins` 只包含真实 origin。 | | 可信代理 | `server.trusted_proxies` 只包含你控制的代理网段。 | | gRPC | 对外暴露 gRPC 时,HTTP 和 gRPC 使用独立 Service/Ingress。 | diff --git a/docs/src/content/docs/operations/troubleshooting.mdx b/docs/src/content/docs/operations/troubleshooting.mdx index 608a31aa..4a8fccd3 100644 --- a/docs/src/content/docs/operations/troubleshooting.mdx +++ b/docs/src/content/docs/operations/troubleshooting.mdx @@ -57,6 +57,7 @@ kubectl -n synctv logs deploy/synctv -f | WebSocket 401/403 | token/ticket 失效、不是房间成员或 origin 被拒绝 | 重新申请 ticket,确认成员和 origin | | OAuth2 callback/state 错误 | redirect 不一致或多副本没有共享 Redis | 对齐 provider 注册值,多副本配置 Redis | | WebAuthn/passkey 失败 | `rp_origin` 和真实 HTTPS origin 不一致 | 修正 `webauthn.rp_origin` 和 `webauthn.rp_id` | +| 视频首帧很慢或实时流延迟持续增加 | Nginx 正在缓存或缓冲后端响应 | 按 [Nginx 反向代理](../../install/nginx/)关闭 `proxy_cache` 和 `proxy_buffering` | | Range seek 或代理播放失败 | Provider header 不完整或上游不支持 Range | 检查 Provider header 和上游 `Accept-Ranges` | | 多副本 HLS 间歇 404 | publisher 节点不可达或 HLS 存储模型不一致 | 使用 publisher-node proxy、`shared_file` 或 S3 |