emby.wiki · 文档
反向代理Emby(Windows Server)
使用管理员 PowerShell、Nginx for Windows、win-acme 和计划任务配置 Emby 反向代理、HTTPS 与自动续期。
以下教程从一台刚安装完成的 Windows Server 开始,使用管理员 PowerShell、Nginx for Windows、Windows Firewall、win-acme 和 Task Scheduler 配置 Emby 反向代理。Emby 的安装与媒体库建设不在本文范围内;配置入口服务器之前,必须先准备正常工作的 Emby 源站。
1. Windows 版 Nginx 的限制#
NGINX 官方仍将 Windows 版本视为 beta。其连接处理方式、性能和扩展性存在限制;虽然可以启动多个 worker,官方说明实际上只有一个 worker 执行工作,且不支持 UDP / QUIC。Windows Server 可以部署本教程的反向代理,但长期运行、高并发或高流量生产环境通常更适合 Linux。
Windows 官方 ZIP 是控制台程序,不会自动注册为 Windows Service。本教程采用 Task Scheduler 实现开机启动,使用 win-acme 的计划任务自动续期,并在续期后先检查配置再 reload。
下载 Nginx 时使用官方 Windows 文档推荐的 latest mainline release;win-acme 从官方 Releases 动态查询最新发行版。教程不固定旧版本号。软件要求应以部署时的 NGINX Windows 文档 和 win-acme 系统要求 为准;不要在已停止维护的 Windows 系统上继续套用旧软件包。
2. 参数准备与管理员 PowerShell#
通过 RDP 或已经配置的 SSH 登录 Windows Server,然后以 Run as administrator / 以管理员身份运行 打开 Windows PowerShell。以下写文件和网络诊断命令兼容 Windows PowerShell 5.1;不要求额外安装 GUI 面板。
whoami
net session
Get-ComputerInfo |
Select-Object WindowsProductName, WindowsVersion, OsArchitecture
whoami 显示当前身份,但本身不能证明窗口已经提升权限。net session 若返回 Access is denied.,应重新以管理员身份启动;若提示 Server 服务未启动,则不能只凭这一结果判断权限。可以直接检查当前令牌:
$Identity = [Security.Principal.WindowsIdentity]::GetCurrent()
$Principal = [Security.Principal.WindowsPrincipal]::new($Identity)
$Principal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
结果应为 True。需要更完整的系统信息时再运行 systeminfo。本教程下载示例使用 x64 ZIP;确认 OsArchitecture 为 64 位,并核对软件当前系统要求。
统一参数如下,域名不带协议、路径或端口:
| 参数 | 含义 | 同机示例 | 两台服务器示例 |
|---|---|---|---|
YOUR_DOMAIN | 指向 Nginx 公网入口的域名 | emby.example.com | emby.example.com |
EMBY_HOST | Nginx 可访问的 Emby 地址 | 127.0.0.1 | 10.0.0.20 |
EMBY_PORT | Emby HTTP 端口 | 8096 | 8096 |
YOUR_EMAIL | ACME 账户联系邮箱 | 自己的有效邮箱 | 自己的有效邮箱 |
在同一个管理员 PowerShell 会话中设置实际值:
$YOUR_DOMAIN = 'emby.example.com'
$EMBY_HOST = '127.0.0.1'
$EMBY_PORT = 8096
$YOUR_EMAIL = 'admin@example.com'
必须把域名和邮箱改为自己的值;分离部署把 $EMBY_HOST 改为 10.0.0.20 等真实私网地址。重新打开 PowerShell 后需要重新设置这些变量。${EMBY_HOST} 的花括号用于避免 URI 中紧随变量的冒号被 PowerShell 误解为作用域语法。
后面的 Nginx 模板使用 YOUR_DOMAIN、EMBY_HOST、EMBY_PORT 占位符,由 .Replace() 明确替换。Nginx 原生变量 $host、$remote_addr、$http_upgrade、$request_uri 必须原样保留。
3. 验证 Emby 源站#
先确认 Emby 本身正常,再配置 Nginx。 源站没有响应时,增加反向代理不能解决源站问题。
同机部署先测试 8096 和 HTTP:
Test-NetConnection 127.0.0.1 -Port 8096
Invoke-WebRequest -Uri 'http://127.0.0.1:8096/' -UseBasicParsing
TcpTestSucceeded 应为 True,HTTP 应返回 Emby 页面或正常重定向。端口不同应改为实际值。
两台服务器时,在 Windows Nginx 服务器 上使用已经设置的参数:
Test-NetConnection $EMBY_HOST -Port $EMBY_PORT
Invoke-WebRequest -Uri "http://${EMBY_HOST}:$EMBY_PORT/" -UseBasicParsing
以下监听检查只针对运行命令的本机。Emby 在另一台 Windows 服务器上时,应在那台源站执行:
$Connections = Get-NetTCPConnection -LocalPort 8096 -State Listen `
-ErrorAction SilentlyContinue
$Connections | Select-Object LocalAddress, LocalPort, State, OwningProcess
$PortProcessIds = @($Connections.OwningProcess | Sort-Object -Unique)
if ($PortProcessIds.Count -gt 0) {
Get-Process -Id $PortProcessIds
}
没有监听结果时,检查 Emby 是否启动、实际端口是否改变。127.0.0.1 只允许本机访问,分离部署应监听可达的私网地址。0.0.0.0 表示所有 IPv4 接口监听,必须另用防火墙限制来源。不要给 $PID 或 $Pid 赋值:PowerShell 不区分变量名大小写,$PID 是系统自动变量。
4. DNS 与端口检查#
域名 A 记录应指向 Windows Nginx 服务器的公网 IPv4,而不是 Emby 的私网地址。若 DNS 服务提供代理 / CDN 开关,本教程的直连部署使用“仅 DNS”;启用代理后需要额外核对访问路径、流媒体限制和来源 IP。
Resolve-DnsName $YOUR_DOMAIN
Resolve-DnsName $YOUR_DOMAIN -Type A
Resolve-DnsName $YOUR_DOMAIN -Type AAAA -ErrorAction SilentlyContinue
没有可用 IPv6 时,不应保留错误 AAAA。错误记录可能导致部分客户端优先连接错误地址,也会影响 ACME 验证。提供 IPv6 服务必须同时保证地址可达、防火墙和安全组放行,并增加对应 Nginx IPv6 监听。
安装前检查 80 / 443 的监听与进程:
$Listeners = Get-NetTCPConnection -State Listen -ErrorAction SilentlyContinue |
Where-Object { $_.LocalPort -in 80, 443 }
$Listeners | Select-Object LocalAddress, LocalPort, OwningProcess
$PortProcessIds = @($Listeners.OwningProcess | Sort-Object -Unique)
if ($PortProcessIds.Count -gt 0) {
Get-Process -Id $PortProcessIds
}
常见占用包括 IIS、Apache、旧 Nginx 和其他 Web Server。先确认用途,再调整端口或服务;不要直接杀进程。若 PID 为 4 / System,可能是 HTTP.sys 占用,可用 netsh http show servicestate 辅助识别实际服务。
Get-Service W3SVC -ErrorAction SilentlyContinue
如果服务器原本承载 IIS 网站,不要直接停用 W3SVC。 只有明确确认 IIS 不再使用、停止服务不会影响原有网站时,才执行:
Stop-Service W3SVC
Set-Service W3SVC -StartupType Disabled
5. 下载和安装 Nginx#
从 NGINX 官方下载页面 确认当前 mainline 的 Windows ZIP 版本,将下面的 CURRENT_VERSION 替换为该版本。不要从第三方重打包网站下载。
$NginxVersion = 'CURRENT_VERSION'
if ($NginxVersion -eq 'CURRENT_VERSION') {
throw 'Set NginxVersion to the current official Windows mainline version.'
}
if (Test-Path 'C:\nginx') {
throw 'C:\nginx already exists. Inspect and back it up before continuing.'
}
New-Item -ItemType Directory -Path 'C:\Temp' -Force | Out-Null
Invoke-WebRequest -UseBasicParsing `
-Uri "https://nginx.org/download/nginx-$NginxVersion.zip" `
-OutFile "C:\Temp\nginx-$NginxVersion.zip"
Expand-Archive -Path "C:\Temp\nginx-$NginxVersion.zip" `
-DestinationPath 'C:\' -ErrorAction Stop
Move-Item -Path "C:\nginx-$NginxVersion" -Destination 'C:\nginx' `
-ErrorAction Stop
Get-ChildItem 'C:\nginx'
最终应存在 C:\nginx\nginx.exe,以及 conf、html、logs 目录。安装命令刻意不覆盖已有目录;升级已有实例需要单独备份配置和证书。
若旧 Windows PowerShell 下载时报 Could not create SSL/TLS secure channel,可在当前会话启用 TLS 1.2 后重试;不要跳过证书验证:
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
Nginx 配置中的 Windows 路径使用正斜杠,例如 C:/nginx/html、C:/nginx/certs/emby.example.com-chain.pem。PowerShell 命令中的 Windows 文件路径仍可使用反斜杠。
首次测试并启动默认配置:
Set-Location 'C:\nginx'
.\nginx.exe -t
if ($LASTEXITCODE -ne 0) { throw 'Nginx configuration test failed.' }
.\nginx.exe
if ($LASTEXITCODE -ne 0) { throw 'Nginx start failed.' }
Get-Process nginx
tasklist /fi "imagename eq nginx.exe"
Invoke-WebRequest -Uri 'http://127.0.0.1/' -UseBasicParsing
本机应能访问默认页面。通常可看到一个 master 和一个 worker;不能仅凭出现两个 nginx.exe 就判断重复启动。启动失败先查第 17 节的错误日志。
6. Windows Firewall 与云安全组#
在管理员 PowerShell 中建立 HTTP / HTTPS 入站规则。以下示例适用于新服务器;重复执行前先检查同名规则,避免反复创建:
New-NetFirewallRule -Name 'EmbyNginxHTTP' `
-DisplayName 'Nginx HTTP 80' -Direction Inbound `
-Protocol TCP -LocalPort 80 -Action Allow -Profile Any
New-NetFirewallRule -Name 'EmbyNginxHTTPS' `
-DisplayName 'Nginx HTTPS 443' -Direction Inbound `
-Protocol TCP -LocalPort 443 -Action Allow -Profile Any
Get-NetFirewallRule -DisplayName 'Nginx HTTP 80', 'Nginx HTTPS 443' |
Select-Object Name, DisplayName, Enabled, Direction, Action, Profile
Get-NetConnectionProfile
Profile Any 让这些指定端口规则适用于 Domain / Private / Public 网络配置文件;它不表示关闭防火墙。既有规则若绑定了错误 profile,并且确实需要对当前网络生效,可修改:
Set-NetFirewallRule -DisplayName 'Nginx HTTP 80' -Profile Any
Set-NetFirewallRule -DisplayName 'Nginx HTTPS 443' -Profile Any
云平台安全组 / 云防火墙也必须独立检查。Windows Firewall 放行并不保证公网一定可达。
| 入站 TCP 端口 | 用途 | 来源范围 |
|---|---|---|
| 80 | HTTP 和 HTTP-01 验证 | 对外服务所需公网来源 |
| 443 | HTTPS | 对外服务所需公网来源 |
| 3389,或实际 RDP 端口 | 远程管理 | 尽可能限制为自己的管理 IP / VPN |
| 22,或实际 SSH 端口 | 仅在已启用 SSH 时使用 | 尽可能限制为自己的管理 IP / VPN |
不要把管理端口长期无限制暴露给整个互联网,也不要默认放行公网 8096。云服务器有 NAT 或位于路由器之后时,80 / 443 应转发到 Nginx 服务器。IPv6 服务需同时检查 IPv6 规则。不要用关闭整个 Windows Firewall 的方式排查。
7. 配置 HTTP 反向代理#
先备份,再写入 C:\nginx\conf\nginx.conf。以下完整配置从开始就保留 ACME 路径,减少后续重复修改。
使用 单引号 Here-String @' ... '@,避免 PowerShell 展开 Nginx 的 $host 等变量。Windows PowerShell 5.1 的 Set-Content -Encoding UTF8 会写入 BOM;本教程使用 .NET 明确写入 UTF-8 无 BOM,兼容不同 PowerShell 版本。Here-String 的结束标记 '@ 必须独占一行且顶格。
$BackupPath = 'C:\nginx\conf\nginx.conf.' + (Get-Date -Format 'yyyyMMdd-HHmmss') + '.bak'
Copy-Item 'C:\nginx\conf\nginx.conf' $BackupPath -ErrorAction Stop
$NginxConfig = @'
worker_processes 1;
error_log logs/error.log;
pid logs/nginx.pid;
events {
worker_connections 1024;
}
http {
include mime.types;
default_type application/octet-stream;
access_log logs/access.log;
sendfile on;
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
server {
listen 80;
server_name YOUR_DOMAIN;
client_max_body_size 0;
location /.well-known/acme-challenge/ {
root C:/nginx/html;
default_type text/plain;
try_files $uri =404;
}
location / {
proxy_pass http://EMBY_HOST:EMBY_PORT;
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 X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Port $server_port;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_read_timeout 3600s;
proxy_send_timeout 3600s;
proxy_buffering off;
proxy_request_buffering off;
}
}
}
'@
$NginxConfig = $NginxConfig.Replace('YOUR_DOMAIN', $YOUR_DOMAIN)
$NginxConfig = $NginxConfig.Replace('EMBY_HOST', $EMBY_HOST)
$NginxConfig = $NginxConfig.Replace('EMBY_PORT', [string]$EMBY_PORT)
[IO.File]::WriteAllText('C:\nginx\conf\nginx.conf', $NginxConfig,
[Text.UTF8Encoding]::new($false))
& 'C:\nginx\nginx.exe' -t -p C:/nginx/ -c conf/nginx.conf
if ($LASTEXITCODE -ne 0) { throw 'Nginx configuration test failed. Do not reload.' }
& 'C:\nginx\nginx.exe' -s reload -p C:/nginx/ -c conf/nginx.conf
if ($LASTEXITCODE -ne 0) { throw 'Nginx reload failed. Check the error log.' }
测试应出现 syntax is ok 和 test is successful。PowerShell 执行原生命令时,非零退出码不一定自动抛出异常,因此示例明确检查 $LASTEXITCODE。-p C:/nginx/ 固定工作前缀,-c conf/nginx.conf 固定配置,避免从其他目录执行时找错文件。
任何后续修改都遵循:备份与修改 → nginx -t 成功 → reload。配置测试失败时,先修复或恢复备份,不执行 reload。
管理命令如下;停止命令仅在确实需要停机时执行:
# Graceful shutdown: stop accepting new connections and finish existing work.
& 'C:\nginx\nginx.exe' -s quit -p C:/nginx/ -c conf/nginx.conf
# Immediate shutdown: may interrupt active playback.
& 'C:\nginx\nginx.exe' -s stop -p C:/nginx/ -c conf/nginx.conf
正常修改配置使用 reload,不需要先 stop。手工启动阶段在同一管理员身份下管理;迁移到 SYSTEM 开机任务后的管理方式见第 14 节。
8. 验证 HTTP 与 WebSocket#
Test-NetConnection $YOUR_DOMAIN -Port 80
Invoke-WebRequest -Uri "http://$YOUR_DOMAIN/" -UseBasicParsing
DNS 尚未生效时,可先在 Nginx 本机按 Host 测试虚拟主机:
Invoke-WebRequest -Uri 'http://127.0.0.1/' `
-Headers @{ Host = $YOUR_DOMAIN } -UseBasicParsing
本机成功后还应从另一台外部设备访问 http://emby.example.com,验证公网路径。只在服务器本机访问不能证明云安全组和公网入口正确。
完整配置已包含 WebSocket 所需设置:
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
Upgrade 和 Connection 属于需要显式转发的 hop-by-hop 头;map 在有 Upgrade 请求时传递 upgrade,普通请求则为 close。可在浏览器开发者工具的 Network / WS 中观察实际 WebSocket 连接,成功升级通常返回 101 Switching Protocols。普通 HTTP 页面能打开并不代表 WebSocket 已验证。
流媒体配置还包含:
proxy_read_timeout 3600s;
proxy_send_timeout 3600s;
proxy_buffering off;
proxy_request_buffering off;
这些 timeout 主要限制相邻读写操作之间的等待,不是限制一部视频只能播放一小时。关闭缓冲用于流式传输;不随意重写 Range 请求。分别验证登录、封面、字幕、视频播放和 seek,再进行长时间播放测试。
9. Emby 后台设置#
在 Emby 管理后台的 Network / 网络 设置中核对以下项目,名称可能随版本和语言变化,以实际界面与 Emby 官方网络说明 为准。
| 设置 | 目标值 |
|---|---|
| External Domain / 外部域名 | YOUR_DOMAIN 的实际值,例如 emby.example.com |
| Public HTTPS Port / 公共 HTTPS 端口 | 443 |
| Secure Connection Mode / 安全连接模式 | Handled by reverse proxy,或对应译文 |
| 本地 HTTP 端口 | Emby 实际监听端口,示例 8096 |
External Domain 不填写 https://、路径或端口。TLS 在 Nginx 终止,上游通过本机或可信私网 HTTP 连接。若当前 Emby 版本提供反向代理信任 / 代理头设置,应只信任实际代理地址,不盲目信任任意公网来源。保存设置后按当前版本要求重启 Emby,并重新确认源站响应。
10. 准备 HTTPS 与 ACME#
Windows 使用原生 ACME 客户端 win-acme。它支持命令行、filesystem HTTP 验证、PEM 文件输出和续期后运行脚本,适合向 Nginx 提供证书。
先建立 webroot 的挑战目录及测试文件:
New-Item -ItemType Directory `
-Path 'C:\nginx\html\.well-known\acme-challenge' -Force | Out-Null
[IO.File]::WriteAllText('C:\nginx\html\.well-known\acme-challenge\test',
'test', [Text.Encoding]::ASCII)
$ChallengeResponse = Invoke-WebRequest `
-Uri "http://$YOUR_DOMAIN/.well-known/acme-challenge/test" `
-UseBasicParsing
$ChallengeResponse.StatusCode
$ChallengeResponse.Content
第 7 节已经包含以下 location,无需另外把不完整片段覆盖整个配置文件:
location /.well-known/acme-challenge/ {
root C:/nginx/html;
default_type text/plain;
try_files $uri =404;
}
root 会将请求 URI 拼到 C:/nginx/html,所以 webroot 参数应为 C:\nginx\html,而不是直接指定 acme-challenge 子目录。
还必须从 外部网络 请求同一个测试 URL,确认 HTTP 200、正文为 test,而不是 Emby 登录页、404 或拦截页。检查 DNS、AAAA、80 入站规则和端口转发。
挑战测试失败时,不要继续申请证书。 本教程使用 HTTP-01,公网 TCP 80 必须能够到达挑战文件。最终 HTTPS 配置也会保留这一路径,以供自动续期。
11. 安装并配置 win-acme#
从 win-acme 官方网站 和 官方 GitHub Releases 获取安装包。官方入门文档推荐大多数 64 位用户使用 x64 trimmed ZIP,并解压到永久目录,示例为 C:\Program Files\win-acme。
以下脚本动态查询 latest release,匹配当前官方的 x64 trimmed ZIP 命名。若将来名称改变、无匹配或出现多个匹配,脚本停止并列出文件名,应到官方 Releases 核对,不能随便选择第一个文件。
$Release = Invoke-RestMethod `
-Uri 'https://api.github.com/repos/win-acme/win-acme/releases/latest'
$Assets = @($Release.assets | Where-Object {
$_.name -match '^win-acme\..*\.x64\.trimmed\.zip$'
})
if ($Assets.Count -ne 1) {
$Release.assets | Select-Object name
throw 'Cannot select one official x64 trimmed ZIP. Check the release assets.'
}
$Asset = $Assets[0]
$Release.tag_name
$Asset.name
if (Test-Path 'C:\Program Files\win-acme\wacs.exe') {
throw 'win-acme already exists. Inspect its configuration before upgrading.'
}
New-Item -ItemType Directory -Path 'C:\Temp' -Force | Out-Null
New-Item -ItemType Directory -Path 'C:\Program Files\win-acme' -Force | Out-Null
Invoke-WebRequest -Uri $Asset.browser_download_url -UseBasicParsing `
-OutFile 'C:\Temp\win-acme.zip'
Expand-Archive -Path 'C:\Temp\win-acme.zip' `
-DestinationPath 'C:\Program Files\win-acme' -ErrorAction Stop
Test-Path 'C:\Program Files\win-acme\wacs.exe'
New-Item -ItemType Directory -Path 'C:\nginx\certs' -Force | Out-Null
Test-Path 应返回 True。不要把 win-acme 放在会被清理的临时目录。证书目录位于网站 webroot 之外,私钥不能通过 HTTP 提供;使用 icacls C:\nginx\certs 检查权限,确保无关用户没有读取私钥或修改证书的权限,管理员和运行任务的 SYSTEM 必须具备所需访问权限。
在首次签发前注册 reload 脚本#
先建立脚本。以下内容使用 ASCII,路径固定为本教程目录,不包含需要中文编码的命令:
@'
@echo off
"C:\nginx\nginx.exe" -t -p C:/nginx/ -c conf/nginx.conf
if errorlevel 1 exit /b 1
"C:\nginx\nginx.exe" -s reload -p C:/nginx/ -c conf/nginx.conf
if errorlevel 1 exit /b 1
exit /b 0
'@ | Set-Content -Path 'C:\nginx\reload-nginx.cmd' -Encoding ASCII
证书签发与续期都通过 installation script plugin 调用该脚本。仅创建 .cmd 文件不会让 win-acme 自动运行它;必须在签发命令中明确配置 --installation script --script ...。
签发并保存 PEM#
确认 Nginx 正在运行、挑战文件公网可访问、四项参数均已设置,然后执行:
Set-Location 'C:\Program Files\win-acme'
.\wacs.exe `
--source manual `
--host $YOUR_DOMAIN `
--validationmode http-01 `
--validation filesystem `
--webroot 'C:\nginx\html' `
--store pemfiles `
--pemfilespath 'C:\nginx\certs' `
--pemfilesname $YOUR_DOMAIN `
--installation script `
--script 'C:\nginx\reload-nginx.cmd' `
--emailaddress $YOUR_EMAIL `
--accepttos
if ($LASTEXITCODE -ne 0) { throw 'win-acme failed. Inspect its output and logs.' }
Get-ChildItem 'C:\nginx\certs'
--accepttos 表示接受所选 ACME 服务的条款,执行前应阅读并确认接受。上述参数已按 win-acme CLI、filesystem validation、PEM store 和 installation script 官方文档核对。
--pemfilesname 固定输出前缀为实际域名。当前 PEM store 命名如下,以 Get-ChildItem 的实际结果为最终依据:
| 文件 | 内容 | Nginx 用途 |
|---|---|---|
YOUR_DOMAIN-crt.pem | 网站证书 | 仅叶证书,不能代替完整证书链 |
YOUR_DOMAIN-key.pem | 私钥 | ssl_certificate_key |
YOUR_DOMAIN-chain.pem | 网站证书与中间证书链 | ssl_certificate |
YOUR_DOMAIN-chain-only.pem | 不含网站证书的中间链 | 不作为本教程的 ssl_certificate |
例如实际文件为 emby.example.com-chain.pem 和 emby.example.com-key.pem。确认二者存在后才能写入 HTTPS 配置。如果首次签发成功但 installation script 失败,应修复脚本或 Nginx 运行状态,再从 win-acme 的续期管理中核对该续期项是否完整保存,不反复盲目签发。
12. 最终 HTTPS Nginx 配置#
以下完整模板包括 HTTP-01 路径、其他 HTTP 请求跳转 HTTPS、TLS 1.2 / 1.3、完整代理头、WebSocket 和流媒体配置。证书名称按上一节确定的前缀生成。
先确认文件存在并备份 HTTP 配置,再写入无 BOM 配置:
$ChainPath = "C:\nginx\certs\$YOUR_DOMAIN-chain.pem"
$KeyPath = "C:\nginx\certs\$YOUR_DOMAIN-key.pem"
if (-not (Test-Path $ChainPath) -or -not (Test-Path $KeyPath)) {
throw 'Certificate chain or private key is missing. Check actual PEM filenames.'
}
$BackupPath = 'C:\nginx\conf\nginx.conf.http.' + (Get-Date -Format 'yyyyMMdd-HHmmss') + '.bak'
Copy-Item 'C:\nginx\conf\nginx.conf' $BackupPath -ErrorAction Stop
$NginxConfig = @'
worker_processes 1;
error_log logs/error.log;
pid logs/nginx.pid;
events {
worker_connections 1024;
}
http {
include mime.types;
default_type application/octet-stream;
access_log logs/access.log;
sendfile on;
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
server {
listen 80;
server_name YOUR_DOMAIN;
location /.well-known/acme-challenge/ {
root C:/nginx/html;
default_type text/plain;
try_files $uri =404;
}
location / {
return 301 https://$host$request_uri;
}
}
server {
listen 443 ssl;
server_name YOUR_DOMAIN;
ssl_certificate C:/nginx/certs/YOUR_DOMAIN-chain.pem;
ssl_certificate_key C:/nginx/certs/YOUR_DOMAIN-key.pem;
ssl_protocols TLSv1.2 TLSv1.3;
client_max_body_size 0;
location / {
proxy_pass http://EMBY_HOST:EMBY_PORT;
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 X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Port $server_port;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_read_timeout 3600s;
proxy_send_timeout 3600s;
proxy_buffering off;
proxy_request_buffering off;
}
}
}
'@
$NginxConfig = $NginxConfig.Replace('YOUR_DOMAIN', $YOUR_DOMAIN)
$NginxConfig = $NginxConfig.Replace('EMBY_HOST', $EMBY_HOST)
$NginxConfig = $NginxConfig.Replace('EMBY_PORT', [string]$EMBY_PORT)
[IO.File]::WriteAllText('C:\nginx\conf\nginx.conf', $NginxConfig,
[Text.UTF8Encoding]::new($false))
& 'C:\nginx\nginx.exe' -t -p C:/nginx/ -c conf/nginx.conf
if ($LASTEXITCODE -ne 0) { throw 'HTTPS configuration test failed. Do not reload.' }
& 'C:\nginx\nginx.exe' -s reload -p C:/nginx/ -c conf/nginx.conf
if ($LASTEXITCODE -ne 0) { throw 'Nginx reload failed. Check the error log.' }
若实际证书前缀与域名不同,应同时更改文件检查与两条 ssl_certificate 路径。不要只改其中一处。使用 IPv6 时,在两个 server 中分别增加 listen [::]:80; 和 listen [::]:443 ssl;,且先完成可达性和防火墙检查。
client_max_body_size 0 取消 Nginx 的请求体大小限制,便于 Emby 功能使用;有特定上传策略时可以设置合理上限。TLS 证书校验失败应修复域名、证书链或系统时间,不使用忽略证书错误作为长期方案。
13. 验证 HTTPS#
Test-NetConnection $YOUR_DOMAIN -Port 443
Invoke-WebRequest -Uri "https://$YOUR_DOMAIN/" -UseBasicParsing
HTTP 跳转检查应关闭自动跟随重定向,并读取状态与 Location。以下方式适用于 Windows PowerShell 5.1,不用 -ErrorAction SilentlyContinue 掩盖重定向或网络错误:
$Request = [Net.HttpWebRequest]::Create("http://$YOUR_DOMAIN/")
$Request.AllowAutoRedirect = $false
$Response = $Request.GetResponse()
try {
[int]$Response.StatusCode
$Response.Headers['Location']
} finally {
$Response.Close()
}
预期为 301,Location 指向实际域名的 HTTPS 地址。再次请求挑战测试文件,应保持 HTTP 200 和正文 test,不会跳到 Emby:
Invoke-WebRequest `
-Uri "http://$YOUR_DOMAIN/.well-known/acme-challenge/test" `
-UseBasicParsing
从外部浏览器和手机 Emby 客户端测试:登录、封面、字幕、直接播放、转码播放、seek、WebSocket 与长连接。浏览器应显示正确域名及有效完整证书链。最终上线前必须完成外部测试,不能把 TCP 443 可达等同于播放正常。
14. Nginx 开机自动启动#
使用系统启动任务,使 Nginx 在没有管理员登录的情况下运行。先创建启动脚本,增加配置测试与已运行检查,避免再次启动 master:
@'
@echo off
tasklist /fi "imagename eq nginx.exe" /nh | find /i "nginx.exe" >nul
if not errorlevel 1 exit /b 0
"C:\nginx\nginx.exe" -t -p C:/nginx/ -c conf/nginx.conf
if errorlevel 1 exit /b 1
"C:\nginx\nginx.exe" -p C:/nginx/ -c conf/nginx.conf
if errorlevel 1 exit /b 1
exit /b 0
'@ | Set-Content -Path 'C:\nginx\start-nginx.cmd' -Encoding ASCII
if (Get-ScheduledTask -TaskName 'Nginx' -ErrorAction SilentlyContinue) {
throw 'A task named Nginx already exists. Inspect it before replacing.'
}
$Action = 'C:\nginx\start-nginx.cmd'
schtasks.exe /Create /TN 'Nginx' /SC ONSTART /RU SYSTEM /RL HIGHEST /TR $Action
if ($LASTEXITCODE -ne 0) { throw 'Nginx startup task creation failed.' }
schtasks.exe /Query /TN 'Nginx' /V /FO LIST
不默认添加 /F 覆盖同名任务。脚本的进程检查假设本机只运行本教程这一套 Nginx;多套实例应分别使用不同前缀、PID 路径和明确的实例检查。
将手工实例迁移到 SYSTEM#
安排维护窗口后,在启动过 Nginx 的管理员会话中优雅退出;长时间播放连接可能推迟退出。确认全部相关进程退出后,才运行任务:
& 'C:\nginx\nginx.exe' -s quit -p C:/nginx/ -c conf/nginx.conf
if ($LASTEXITCODE -ne 0) { throw 'Graceful shutdown failed. Inspect before proceeding.' }
Get-Process nginx -ErrorAction SilentlyContinue
上一条进程检查仍有输出时,等待现有连接结束并再次检查,不重复启动、不强制杀进程。无相关进程后执行:
schtasks.exe /Run /TN 'Nginx'
if ($LASTEXITCODE -ne 0) { throw 'Cannot start the Nginx task.' }
Get-Process nginx -ErrorAction SilentlyContinue
Get-ScheduledTask -TaskName 'Nginx' | Get-ScheduledTaskInfo
Invoke-WebRequest -Uri "https://$YOUR_DOMAIN/" -UseBasicParsing
计划任务启动是异步的;若进程尚未出现,检查任务执行结果和错误日志,不能据此反复运行。计划任务显示脚本完成并不表示后台 Nginx 已停止,应同时检查进程和 HTTP 响应。
SYSTEM 身份下的手工 reload#
Windows 控制命令应与运行实例的权限和身份协调。开机任务与 win-acme 默认续期任务均使用 SYSTEM;如果管理员直接 -s reload 出现访问拒绝,可创建一个 仅手工调用、没有触发器 的 SYSTEM reload 任务,调用第 11 节已经存在的测试脚本:
if (Get-ScheduledTask -TaskName 'Nginx Reload' -ErrorAction SilentlyContinue) {
throw 'Nginx Reload already exists. Inspect it before replacing.'
}
$ReloadAction = New-ScheduledTaskAction -Execute 'C:\Windows\System32\cmd.exe' `
-Argument '/c C:\nginx\reload-nginx.cmd' -WorkingDirectory 'C:\nginx'
$ReloadPrincipal = New-ScheduledTaskPrincipal -UserId 'SYSTEM' `
-LogonType ServiceAccount -RunLevel Highest
Register-ScheduledTask -TaskName 'Nginx Reload' -Action $ReloadAction `
-Principal $ReloadPrincipal -Description 'Validate and reload this Nginx instance'
Start-ScheduledTask -TaskName 'Nginx Reload'
Get-ScheduledTask -TaskName 'Nginx Reload' | Get-ScheduledTaskInfo
检查任务运行完成后的 LastTaskResult,成功应为 0,并再次确认 HTTPS 响应。这个任务不负责定时续期;续期仍由 win-acme 调用 installation script 完成。实际重启服务器后还应验证无需登录即可访问 Nginx,不能只凭任务创建成功就认为开机测试通过。
15. 证书自动续期与 Nginx Reload#
win-acme 在第一次成功创建证书后,会建立负责续期的 Scheduled Task;它定期检查,只在需要时续期。默认使用 SYSTEM。任务路径依赖其安装目录,不能随意移动或删除该目录。
$RenewalTasks = Get-ScheduledTask | Where-Object TaskName -Like '*win-acme*'
$RenewalTasks | Select-Object TaskName, TaskPath, State
$RenewalTasks | ForEach-Object {
$_.Principal | Select-Object UserId, RunLevel
$_.Actions | Select-Object Execute, Arguments, WorkingDirectory
$_ | Get-ScheduledTaskInfo
}
schtasks.exe /Query | Select-String 'win-acme'
检查任务确实存在、启用,程序路径指向永久目录,并核对最近执行结果。找不到任务时,不自行创建参数不明的续期任务;使用官方支持的重建方式:
& 'C:\Program Files\win-acme\wacs.exe' --setuptaskscheduler
if ($LASTEXITCODE -ne 0) { throw 'Cannot create or update the win-acme scheduled task.' }
第 11 节签发时已经登记 --installation script --script C:\nginx\reload-nginx.cmd,续期应沿用该配置。正确流程为:更新 PEM 文件 → installation script → nginx -t → 成功后 reload。仅更新证书文件不会让正在运行的 Nginx 自动使用新证书。
验证续期与脚本#
可以先手工运行第 14 节的 Nginx Reload 任务,验证 SYSTEM 能读取配置和证书、测试并 reload,再检查错误日志。也可在管理员会话执行:
& 'C:\Program Files\win-acme\wacs.exe' --list
& 'C:\Program Files\win-acme\wacs.exe' --renew --verbose
--renew 仅处理已到期需要续期的项目。新证书可能被跳过,因此该命令没有错误不代表已经完整执行签发、更新文件与 hook。
确需进行一次完整故障诊断时,从 --list 获取对应续期 ID,将 YOUR_RENEWAL_ID 替换为该 ID,再执行官方支持的强制续期:
& 'C:\Program Files\win-acme\wacs.exe' --renew --id YOUR_RENEWAL_ID --force --verbose
这会执行真实续期流程,可能使用缓存或访问生产 ACME 服务,不能当作无限重复的 dry run。避免频繁强制签发触发限额。若手工运行的 installation script 因身份差异失败,先确认保存的 PEM 与日志,再通过 SYSTEM reload 任务加载,不盲目重复申请;最终仍需核实默认 SYSTEM 续期任务能够完成全部流程。
日志可在 Windows Event Viewer 及 %ProgramData%\win-acme\ 下对应 ACME 服务的 Log 目录查看。还应检查 PEM 修改时间、脚本退出结果,以及外部浏览器实际收到的证书有效期。保留 HTTP-01 路径和公网 80;不要在首次签发后关闭验证入口。
16. 两台服务器部署#
示例架构:Windows Nginx 私网地址 10.0.0.10,Emby 私网地址 10.0.0.20,HTTP 端口 8096。在入口服务器设置 $EMBY_HOST = '10.0.0.20',重新生成并测试配置。
Test-NetConnection 10.0.0.20 -Port 8096
Invoke-WebRequest -Uri 'http://10.0.0.20:8096/' -UseBasicParsing
对应上游为:
proxy_pass http://10.0.0.20:8096;
如果源站也是 Windows,在 Emby 服务器 上允许入口私网 IP 访问 8096:
New-NetFirewallRule -Name 'EmbyFromNginx' `
-DisplayName 'Emby 8096 from Nginx' -Direction Inbound `
-Protocol TCP -LocalPort 8096 -RemoteAddress 10.0.0.10 `
-Action Allow -Profile Any
Get-NetFirewallRule -Name 'EmbyFromNginx' | Get-NetFirewallAddressFilter
Get-NetFirewallRule -Name 'EmbyFromNginx' | Get-NetFirewallPortFilter
新增窄范围 Allow 规则不会自动取消已有宽范围 Allow。应检查并在确认用途后收窄原有 Emby 安装规则、其他 8096 规则和源站云安全组,使公网不能绕过 Nginx。不要再添加“阻止所有 8096”的冲突规则来覆盖来源例外。
源站为 Linux 时,参照 Linux 教程第 16 节 限制来源。分离部署的 Emby 不能只监听 127.0.0.1;同机部署则优先保持源站仅本机可达。
两机 HTTP 上游仅用于可信私网。跨公网部署应先使用加密隧道,或采用经过证书验证的 HTTPS 上游;客户端到 Nginx 的 TLS 不会自动加密 Nginx 到 Emby 的 HTTP。
17. 常见故障排查#
Nginx 启动失败或配置测试失败#
& 'C:\nginx\nginx.exe' -t -p C:/nginx/ -c conf/nginx.conf
Get-Content 'C:\nginx\logs\error.log' -Tail 100
需要实时观察时使用以下命令,按 Ctrl+C 结束:
Get-Content 'C:\nginx\logs\error.log' -Tail 30 -Wait
没有生成错误日志时,检查 Windows Event Viewer、任务执行结果、目录权限与软件启动依赖。unknown directive 出现在第一行且配置看起来正确时,检查文件是否带 BOM;重新按本文无 BOM 写入方法生成,不使用 > 把默认 UTF-16 文本写入 Nginx 配置。
CreateFile() failed / 证书无法加载#
Test-Path 'C:\nginx\conf\nginx.conf'
Test-Path "C:\nginx\certs\$YOUR_DOMAIN-chain.pem"
Test-Path "C:\nginx\certs\$YOUR_DOMAIN-key.pem"
Get-ChildItem 'C:\nginx\certs'
icacls 'C:\nginx\certs'
确认文件名、实际签发域名、Nginx 路径的正斜杠、固定 -p 前缀,以及 SYSTEM 对目录的访问权限。不要把私钥移动到 html 目录,也不要为解决权限问题授予 Everyone 完全控制。
bind() 80 / 443 failed#
$Listeners = Get-NetTCPConnection -State Listen -ErrorAction SilentlyContinue |
Where-Object { $_.LocalPort -in 80, 443 }
$Listeners | Select-Object LocalAddress, LocalPort, OwningProcess
$PortProcessIds = @($Listeners.OwningProcess | Sort-Object -Unique)
if ($PortProcessIds.Count -gt 0) { Get-Process -Id $PortProcessIds }
识别 IIS、HTTP.sys、旧实例或其他服务后再处理。检查是否同时执行了手工启动与开机任务;不要通过反复启动或直接杀占用进程掩盖原因。
502 Bad Gateway#
先在 Nginx 服务器测试源站:
Test-NetConnection $EMBY_HOST -Port $EMBY_PORT
Invoke-WebRequest -Uri "http://${EMBY_HOST}:$EMBY_PORT/" -UseBasicParsing
Get-Content 'C:\nginx\logs\error.log' -Tail 100
检查 Emby 进程、监听地址、私网路由、源站防火墙和 proxy_pass。Connection refused 通常指向服务未监听或端口错误;超时更应检查网络路径与规则。
504 Gateway Timeout 与资源占用#
Measure-Command {
Invoke-WebRequest -Uri "http://${EMBY_HOST}:$EMBY_PORT/" -UseBasicParsing
}
Get-Process | Sort-Object CPU -Descending |
Select-Object -First 10 Name, Id, CPU, WorkingSet64
Get-CimInstance Win32_OperatingSystem |
Select-Object TotalVisibleMemorySize, FreePhysicalMemory
Get-Process 的 CPU 字段是累计处理器时间,不是实时 CPU 百分比;内存查询的两个字段以 KiB 为单位。上述根页面耗时也不能代表转码或整段视频性能。结合 Task Manager / Resource Monitor、Emby 转码日志检查 CPU、GPU、RAM、磁盘 I/O、网络和实际播放请求。不要直接无限提高 timeout。
视频播放一段时间后断开、字幕或 seek 异常#
确认最终配置保留 proxy_read_timeout 3600s、proxy_send_timeout 3600s、proxy_buffering off 和 proxy_request_buffering off。再检查 Emby 转码、GPU、磁盘 I/O、公网带宽、客户端网络及中间 CDN 的限制;查看实际请求状态与错误日志。不要随意增加 Range 重写。413 时检查当前生效配置的 client_max_body_size,并结合上传策略设置。
WebSocket 异常#
检查第 8 节的 HTTP/1.1、Upgrade、Connection 和 map 是否都存在且位于正确的 http / location 作用域。观察实际 WS 请求是否成功升级;页面正常、实时状态异常时优先查 WebSocket 和中间代理。
防火墙、本机正常但公网失败#
Get-NetFirewallRule -DisplayName 'Nginx HTTP 80', 'Nginx HTTPS 443' |
Select-Object DisplayName, Enabled, Direction, Action, Profile
Get-NetConnectionProfile
Get-NetFirewallRule -DisplayName 'Nginx HTTP 80', 'Nginx HTTPS 443' |
Get-NetFirewallPortFilter
继续核对云安全组、NAT、公网 IP、A / AAAA、IPv6 监听及外部客户端网络。显式阻止规则或组织策略也可能影响本地 Allow 规则;查看有效规则,不关闭整个防火墙。
ACME 验证、续期或 reload 失败#
重新请求挑战测试文件,确认公网 80 没有被关闭、认证或代理到 Emby。核对 win-acme 任务的程序路径与账户、PEM 文件时间、保存的 installation script、脚本测试结果和 Nginx 运行身份。证书更新但浏览器仍收到旧证书时,核对 reload 是否成功以及访问的是否是正确入口。
18. Windows 最终检查清单#
- 管理员 PowerShell 权限确认正常
- Emby 源站 8096 或实际端口正常响应
- DNS A 指向 Windows Nginx 公网入口
- AAAA 正确,或在无可用 IPv6 时不存在错误记录
- 云安全组已开放服务所需 TCP 80 / 443
- Windows Firewall 已开放服务所需 TCP 80 / 443
- RDP / SSH 管理端口来源范围合理
- 80 / 443 没有其他服务冲突
- 官方 Nginx 已安装到 C:\nginx
- 配置已备份,使用单引号 Here-String 与 UTF-8 无 BOM
- Nginx 配置中的路径使用正斜杠
- nginx.exe -t 成功,成功后才 reload
- HTTP 反向代理已通过本机与外部测试
- WebSocket map、HTTP/1.1、Upgrade 与 Connection 正常
- Emby External Domain 为实际域名且不带协议
- Public HTTPS Port = 443
- Secure Connection Mode = Handled by reverse proxy 或对应译文
- ACME Challenge 测试文件可从公网 HTTP 访问
- win-acme 已成功签发 PEM,实际文件名已确认
- Nginx 使用网站证书加完整链,以及匹配私钥
- HTTPS 域名与证书链验证正常
- 普通 HTTP 请求自动跳转 HTTPS,挑战路径仍可访问
- 登录、图片、字幕与视频播放正常
- Seek、直接播放与转码播放正常
- 手机 Emby 客户端正常
- WebSocket 与长连接、长时间播放正常
- Nginx 开机任务已创建,并在实际重启后验证
- win-acme 自动续期任务存在、启用且路径正确
- installation script 已登记,续期后先 nginx -t 再 reload
- SYSTEM 能读取证书、配置并执行 reload
- 证书更新后已核对外部收到的证书有效期
- Emby 8096 没有不必要暴露公网
- 私钥未放入 webroot,配置和脚本不能被无关用户修改
部署时可继续核对:NGINX Windows、NGINX WebSocket、win-acme 入门、win-acme 自动续期、Microsoft schtasks create、Microsoft New-NetFirewallRule、PowerShell 文件编码。

