前言
随着部署的服务越来越多,从NAS里的 Jellyfin 影音平台,到自建的 OpenWebUI 对话界面,记住一串串 192.168.x.x:端口号 变得越来越痛苦。不仅输入繁琐,而且由于缺乏 SSL 证书支持,浏览器满屏的“不安全”警告也让人心烦。
为了实现像访问互联网一样,通过 https://jellyfin.local 这种优雅的方式访问内网,于是做了基于 DNS 劫持 与 Nginx 反向代理 的链路来实现域名https访问ip:端口的服务。
核心在于两个关键动作:“指路”与“分流”。
- 指路 (DNS): 当你在浏览器输入域名时,路由器上的
dnsmasq 充当了交警的角色。它告诉客户端:“别去公网找,这个域名就在我们家里的 192.168.8.5(Nginx)上。”
- 分流 (Nginx): 当 Nginx 收到加密的 HTTPS 请求后,它会根据请求里的 SNI(主机名) 信息进行拆解。如果是找 Jellyfin 的,它就默默地把流量转发给
8096 端口;如果是找别的,就转给对应的服务。
设备情况:
主路由器 (GL-MT3000 / OpenWrt): 利用其内置的 dnsmasq 服务,将特定的内网域名请求(如 *.lan)强制解析到 Nginx 所在的服务器 IP。
流量网关 (N3540 小主机 - 192.168.8.5): 一台低功耗的 Linux 主机,运行 Nginx。它作为唯一的 HTTPS 入口,负责证书卸载(SSL Termination)和请求分发。
后端应用服务器 (Epson ST190E - 192.168.8.223): 核心业务所在地。上面跑着各类自建服务(如 Jellyfin、OpenWebUI 等),通过不同的端口号提供服务。
通信流程:
客户端 → DNS 查询 → 路由器 dnsmasq (192.168.8.1) 返回 192.168.8.5 → 客户端连接 192.168.8.5:443 → Nginx(反向代理)根据 Host 头转发到 服务端192.168.8.223:8096 → 响应返回。
下面是详细的搭建教程。
主机名映射
目标:
局域网设备比如pc,执行以下查询
1 2
| nslookup qb.lan ping.qb.lan
|
可以被正确解析到设定的局域网ip(进行反代设备的ip)
提供两种映射方式,config domain和list address
| 方式 |
写法 |
优点 |
缺点 |
config domain(静态DNS条目) |
config domain option name ‘owu.lan’ option ip ‘192.168.8.5’ |
清晰、可管理多个静态条目,支持 Web UI |
每个域名要写一块 |
list address |
list address '/qb.lan/192.168.8.5' |
快速、可在同一条中添加多个 |
管理多个域名不如 config domain 清晰 |
最简单的方式是静态DNS条目方式,在openwrt后台就能添加;无论哪种方式,当验证的时候发现没有预期的效果,一定记得尝试重启路由器!!!!
listaddress方式
推荐用 SSH 工具(PuTTY / Windows Terminal / macOS Terminal 等)连接路由器
如果没有或不想安装SSH工具也可以使用如下CMD命令
输入路由器密码登录。
所有域名都指向反代机器 IP 192.168.8.5
1 2 3 4 5 6
| uci add_list dhcp.@dnsmasq[0].address='/jellyfin.lan/192.168.8.5' uci add_list dhcp.@dnsmasq[0].address='/owu.lan/192.168.8.5' uci add_list dhcp.@dnsmasq[0].address='/qb.lan/192.168.8.5' uci add_list dhcp.@dnsmasq[0].address='/wp.lan/192.168.8.5' uci add_list dhcp.@dnsmasq[0].address='/music.lan/192.168.8.5' uci add_list dhcp.@dnsmasq[0].address='/book.lan/192.168.8.5'
|
保存更改(写入/etc/config/dhcp配置文件)
重启 dnsmasq
1
| /etc/init.d/dnsmasq restart
|
1 2
| ipconfig /flushdns nslookup qb.lan
|
正确结果应该是:
- Server: 192.168.8.1
- Address: 192.168.8.5
如果不是,路由器断电重启,重新验证
删除配置(配置错误时)
1 2 3
| uci -q del_list dhcp.@dnsmasq[0].address='/jellyfin.lan/192.168.8.5' uci -q del_list dhcp.@dnsmasq[0].address='/owu.lan/192.168.8.5' uci -q del_list dhcp.@dnsmasq[0].address='/qb.lan/192.168.8.5'
|
查看配置是否删除/写入
1
| cat /etc/config/dhcp | grep address
|
静态DNS条目方式
这种方式有两种添加方法,任选一种

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| # 添加三个服务的域名记录(指向 NPM IP 192.168.8.5) uci add dhcp domain uci set dhcp.@domain[-1].name='jellyfin.lan' uci set dhcp.@domain[-1].ip='192.168.8.5'
uci add dhcp domain uci set dhcp.@domain[-1].name='owu.lan' uci set dhcp.@domain[-1].ip='192.168.8.5'
uci add dhcp domain uci set dhcp.@domain[-1].name='qb.lan' uci set dhcp.@domain[-1].ip='192.168.8.5'
# 保存并重启 dnsmasq uci commit dhcp /etc/init.d/dnsmasq restart dnsmasq --test
|
PC验证
1 2
| ipconfig /flushdns nslookup jellyfin.lan
|
无法正确解析,尝试重启路由器再次验证
域名反代
nginx配置
nginx示例配置文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157
| # ====================== 服务配置 ======================
# 反向代理到 Jellyfin server { listen 443 ssl; server_name jellyfin.lan;
ssl_certificate /www/server/nginx/conf.d/lan-fullchain.pem; ssl_certificate_key /www/server/nginx/conf.d/lan-privkey.pem;
client_max_body_size 20M;
location / { proxy_pass http://192.168.8.117:8096; 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_cache off; sendfile on; tcp_nopush on; tcp_nodelay on; }
location /socket { proxy_pass http://192.168.8.117:8096/socket; 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; } }
# 反向代理到 OWU (OpenWebUI) server { listen 443 ssl; server_name owu.lan;
ssl_certificate /www/server/nginx/conf.d/lan-fullchain.pem; ssl_certificate_key /www/server/nginx/conf.d/lan-privkey.pem;
location / { proxy_pass http://192.168.8.117:8180; 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_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; } }
# 反向代理到 qBittorrent server { listen 443 ssl; server_name qb.lan;
ssl_certificate /www/server/nginx/conf.d/lan-fullchain.pem; ssl_certificate_key /www/server/nginx/conf.d/lan-privkey.pem;
location / { proxy_pass http://192.168.8.117:8282; 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_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; } }
# 反向代理到 Navidrome (音频播放器) server { listen 443 ssl; listen [::]:443 ssl; http2 on; # 建议加上 server_name music.lan;
ssl_certificate /www/server/nginx/conf.d/lan-fullchain.pem; ssl_certificate_key /www/server/nginx/conf.d/lan-privkey.pem;
location / { proxy_pass http://192.168.8.117:16563; proxy_set_header Range $http_range; proxy_set_header If-Range $http_if_range; proxy_buffering off; proxy_cache off; proxy_http_version 1.1; tcp_nopush on; sendfile on; directio 4m; }
location /socket { proxy_pass http://192.168.8.117:16563/socket; 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; } }
# 反向代理到 Calibre-web (电子书库) server { listen 443 ssl; server_name book.lan;
ssl_certificate /www/server/nginx/conf.d/lan-fullchain.pem; ssl_certificate_key /www/server/nginx/conf.d/lan-privkey.pem;
location / { expires 30d; add_header Cache-Control "public, no-transform"; proxy_pass http://192.168.8.117:8983; 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_buffering off; proxy_request_buffering off; proxy_read_timeout 3600s; } }
# 反向代理到 OpenList (网盘) server { listen 443 ssl; server_name wp.lan;
ssl_certificate /www/server/nginx/conf.d/lan-fullchain.pem; ssl_certificate_key /www/server/nginx/conf.d/lan-privkey.pem;
location / { proxy_pass http://192.168.8.117:15244; proxy_set_header Host $host; proxy_set_header X-Forwarded-Host $host; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_buffering off; proxy_cache off; proxy_request_buffering off; proxy_read_timeout 3600s; } }
# ====================== HTTP 自动跳转到 HTTPS ====================== server { listen 80; server_name jellyfin.lan owu.lan qb.lan wp.lan music.lan book.lan; return 301 https://$host$request_uri; }
|
jellyfin的部分配置参考解决反向代理JellyFin后播放等待时间过长的问题
申请SSL
这里使用Step-CA来申请。使用openssl也可以,这个会更简单。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| # 1. 更新系统并安装依赖 sudo apt update sudo apt install -y --no-install-recommends curl gpg ca-certificates
# 2. 添加 Smallstep 官方仓库(2026 年最新方式) curl -fsSL https://packages.smallstep.com/keys/apt/repo-signing-key.gpg -o /etc/apt/keyrings/smallstep.asc
cat << EOF | sudo tee /etc/apt/sources.list.d/smallstep.sources Types: deb URIs: https://packages.smallstep.com/stable/debian Suites: debs Components: main Signed-By: /etc/apt/keyrings/smallstep.asc EOF
# 3. 更新仓库并安装 step-cli + step-ca sudo apt update sudo apt install -y step-cli step-ca
|
1 2
| step version step-ca version
|
1 2 3 4 5 6
| sudo step ca init \ --name "Home LAN CA" \ --dns 192.168.8.5 \ --dns localhost \ --dns 127.0.0.1 \ --address 192.168.8.5:8443
|
Standalone → 完全本地 CA,最适合家庭/局域网环境。你可以自己签发证书,不依赖云服务。
Linked → 本地 CA + cloud 功能,适合想要管理/报告/告警的用户。
Hosted → 小步提供的全托管 CA,不适合本地局域网。
之后会让输入账号和密码
如果不修改默认是只能申请24小时的
1
| nano ~/.step/config/ca.json
|
在 "authority" 部分,添加或修改成下面这样(把整个 authority 块替换/补充为以下内容):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| "authority": { "claims": { "minTLSCertDuration": "5m", "maxTLSCertDuration": "87600h", "defaultTLSCertDuration": "87600h" }, "provisioners": [ { "type": "JWK", "name": "\u0016", "key": { ...原有 key 配置保持不变... }, "encryptedKey": "原有 encryptedKey 保持不变", "maxTLSCertDuration": "87600h" } ], "signing": { "defaultTLSDuration": "87600h" } }
|
保存退出
如果是普通用户初始化的:
1
| sudo step-ca /home/<user>/step-ca/config/ca.json
|
如果是在 root 下初始化的:
1
| sudo step-ca /root/.step/config/ca.json
|
需要输入密码才能启动,启动后,会在你初始化时设置的端口监听(127.0.0.1:8443)。
后续每次需要申请证书都要先启动再申请
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| cd /www/server/nginx/conf.d
step ca certificate \ jellyfin.lan \ lan-fullchain.pem \ lan-privkey.pem \ --not-after=87600h \ --san jellyfin.lan \ --san owu.lan \ --san qb.lan \ --san wp.lan \ --san music.lan \ --san book.lan \ --san 192.168.8.5 \ --san localhost
|
需要输入密码

后续新增域名
后续新增域名时,不需要重新安装 Step-CA,也不需要重新初始化 CA。只要 CA 根证书没变,Windows、手机端也不需要重新导入根证书。
需要做的只有三件事:
- OpenWrt DNS 添加新域名,指向 Nginx 所在机器
192.168.8.5。
- 重新签发
lan-fullchain.pem 和 lan-privkey.pem,证书里要包含旧域名和新域名。
- Nginx 新增对应
server 反代块,然后重载 Nginx。
比如新增 novel.lan 和 wallpaper.lan,先在 OpenWrt 添加 DNS:
1 2 3 4
| uci add_list dhcp.@dnsmasq[0].address='/novel.lan/192.168.8.5' uci add_list dhcp.@dnsmasq[0].address='/wallpaper.lan/192.168.8.5' uci commit dhcp /etc/init.d/dnsmasq restart
|
然后重新签发证书。注意这里不能只写新增的两个域名,原来还在使用的域名也要一起放进 SAN:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| cd /www/server/nginx/conf.d
cp lan-fullchain.pem lan-fullchain.pem.bak.$(date +%Y%m%d-%H%M%S) cp lan-privkey.pem lan-privkey.pem.bak.$(date +%Y%m%d-%H%M%S)
step ca certificate \ jellyfin.lan \ lan-fullchain.pem \ lan-privkey.pem \ --force \ --not-after=87600h \ --san jellyfin.lan \ --san owu.lan \ --san qb.lan \ --san wp.lan \ --san music.lan \ --san book.lan \ --san novel.lan \ --san wallpaper.lan \ --san 192.168.8.5 \ --san localhost
nginx -t systemctl reload nginx
|
如果以后会经常新增 xxx.lan 这种一级域名,可以直接签一个泛域名证书:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| cd /www/server/nginx/conf.d
cp lan-fullchain.pem lan-fullchain.pem.bak.$(date +%Y%m%d-%H%M%S) cp lan-privkey.pem lan-privkey.pem.bak.$(date +%Y%m%d-%H%M%S)
step ca certificate \ "*.lan" \ lan-fullchain.pem \ lan-privkey.pem \ --force \ --not-after=87600h \ --san "*.lan" \ --san 192.168.8.5 \ --san localhost
nginx -t systemctl reload nginx
|
*.lan 可以用于 novel.lan、wallpaper.lan、jellyfin.lan 这种只有一层前缀的域名,但不能匹配 a.novel.lan 这种多层域名,也不匹配裸的 lan。
可以用下面命令确认证书里是否包含泛域名:
1
| step certificate inspect lan-fullchain.pem --short
|
客户端信任CA
客户端要信任的是 Step-CA 的根证书,通常是:
1
| /root/.step/certs/root_ca.crt
|
注意不要导入 Nginx 使用的 lan-fullchain.pem,更不要导入私钥 lan-privkey.pem。lan-fullchain.pem 是网站证书,root_ca.crt 才是需要发到电脑、手机上的 CA 根证书。
如果是在普通用户下初始化的 Step-CA,根证书一般在:
1
| ~/.step/certs/root_ca.crt
|
Windows 导入根证书
先把服务器上的根证书复制到 Windows,例如保存到下载目录:
1
| scp root@192.168.8.5:/root/.step/certs/root_ca.crt "$env:USERPROFILE\Downloads\Home-LAN-CA.crt"
|
推荐用管理员身份打开 PowerShell 或 CMD,执行:
1
| certutil -addstore -f Root "$env:USERPROFILE\Downloads\Home-LAN-CA.crt"
|
如果只想给当前 Windows 用户信任,不想写入整台电脑的证书库,可以用:
1
| certutil -user -addstore -f Root "$env:USERPROFILE\Downloads\Home-LAN-CA.crt"
|
导入完成后,重启浏览器再访问 https://jellyfin.lan 这类内网域名。
也可以使用图形界面导入:
- 按
Win + R,输入 certlm.msc,打开“本地计算机证书”管理器。
- 展开“受信任的根证书颁发机构” → “证书”。
- 右键“证书” → “所有任务” → “导入”。
- 选择
Home-LAN-CA.crt。
- 证书存储选择“受信任的根证书颁发机构”。
- 完成导入,重启浏览器。
Chrome 和 Edge 默认使用 Windows 证书库。Firefox 可能使用自己的证书库,如果 Firefox 仍然提示不安全,需要在 Firefox 的证书管理里单独导入这个 CA,或者在 about:config 里把 security.enterprise_roots.enabled 设置为 true。
