从 Lucky 迁移到 luci-nginx
tags: NAS从 Lucky 迁移到 luci-nginx
一直以来,Lucky 都是我的在 OpenWRT 路由器上的必备软件,它可以图形化配置 SSL 证书、反向代理等比较复杂的功能。我主要使用 Lucky 的三个功能:反向代理+http 重定向、DDNS、端口转发。
- 反向代理:仅开放一个路由器端口,访问所有内网服务,泛域名 SSL 部署
- DDNS:动态域名、配合反向代理
- 端口转发:转发一些内网设备的 SSH 服务,主要是 Gitea 的 SSH 功能
还有很多用不上的功能……例如网络唤醒、存储管理这类的,我觉得这不是一个网络服务器该做的事,它管得太宽了。我个人不太喜欢这样,也许我更加追求极简,Lucky 对于 CPU、内存的资源占用也确实比较大,反向代理功能感觉慢吞吞的,稳定性也不太行,有时候会报错。
以上就是我想从 Lucky 迁移到传统方案的原因。
初步迁移方案:
- 使用 luci-app-ddns-go 进行 DDNS,luci-nginx 进行反向代理
- 在有公网 IPv6 的情况下,不再进行传统的端口转发,而是在部署 Gitea 的设备上 ddns,直接开放端口(也就是说 Gitea 不再进行反向代理)
- 额外:直接放通 OpenWRT 网页端口,不再反向代理,避免反代出问题导致无法访问 luci,这件事已经出现过多次
从 uhttpd 迁移到 Nginx
把 OpenWrt 的 Web 服务器从 uhttpd 迁到 luci-nginx,听起来不麻烦,我以为和 dedian 上配置 nginx 差不多,不就是代理 /www 网页吗?真正动手才发现官方端口、证书、防火墙,并不能靠复制粘贴配置文件解决,全部需要用 UCI 来配置。
先辟个谣:有些资料和大模型 AI 认为 OpenWRT 的 luci-nginx 只有基础的网页访问功能,说反向代理、SSL 功能需要额外装插件,实际上 luci-nginx 已经带有了反向代理和 SSL 功能,完全不需要操心,对了,我的版本是 ImmortalWRT 24.10,可能与纯血 OpenWRT 有所不同。
编译固件时只要勾上 luci -> collection -> luci-nginx,第一次刷机系统会自动把 uhttpd 踢出自启动列表,80 和 443 直接被 nginx 取代,并且 luci-nginx 默认会把 80 端口的 http 访问重定向到 443 的 https。如果是升级刷机,uhttpd 不会自动禁用,这可能导致 nginx 无法启动(看谁先抢到,一般是 uhttpd),需要手动 /etc/init.d/uhttpd stop 然后 /etc/init.d/uhttpd disable。
OpenWrt 的 Nginx 没有 site-available 一类的东西,所有基本参数被拆成 UCI 节点,躺在 /etc/config/nginx 里,像改防火墙一样改 Web 服务器——uci set + commit + reload 三件套。
此外,可以把传统配置写在 /etc/nginx/conf.d 里面,这些配置会自动加载。
我把默认的 HTTPS 端口从 443(被禁用)挪到高端口。然后改掉 _lan 区块的 listen,把 IPv4 和 IPv6 一起写成 18888 ssl,再把 80 端口的重定向目标指向新端口。从内网访问 ip 会直接被 302 到 https://路由器:18888/,外网则直接访问 https。
如之前所说,luci 界面不打算反向代理了,直接走 https。
# 修改 https 端口
uci set nginx._redirect2ssl.listen='list 18888 ssl default_server'
uci set nginx._redirect2ssl.listen='list [::]:18888 ssl default_server'
# 302 重定向规则
uci set nginx._redirect2ssl.return='302 https://$host:18888$request_uri'
# 提交并重启 nginx 使配置生效
uci commit nginx
/etc/init.d/nginx restart
为了达到公网访问的效果,还需要禁用 restrict_locally 这个 nginx 自带的防火墙,他会拦截公网连接。
因为 openwrt 本来就有防火墙,添加这个玩意纯属多此一举。
# 去掉 _lan 区块中对 restrict_locally 的引用
uci del_list nginx._lan.include='restrict_locally'
# 提交并重启 nginx 使配置生效
uci commit nginx
/etc/init.d/nginx restart
最后设置一下证书路径:
uci set nginx._redirect2ssl.ssl_certificate='/etc/nginx/conf.d/_.Example.com.crt'
uci set nginx._redirect2ssl.ssl_certificate_key='/etc/nginx/conf.d/_.Example.com.key'
uci commit nginx
/etc/init.d/nginx restart
目前这个路径是空的,后面可以用 acme.sh 生成,最好可以等有证书之后再设路径。
SSL 自动化
OpenWrt 的 init 脚本有个“贴心”设计:只要发现 _lan.crt 或 _lan.key 不存在,就会立刻现场生成一套自签名证书,并把文件硬写到 /etc/nginx/conf.d/。这意味着如果你提前把 SSL 证书覆盖他俩,再 restart,你的证书直接啪、没了。解决方法是把证书放到脚本不会碰的路径,再通知 UCI 用新文件。或者可以禁用 uci_manage_ssl 自签功能。根据最小改动原则,最好是更换路径(命令在上文)。
例如可以把通配符证书 *.Example.com 放在同一目录下,命名成 _.Example.com.crt 与 _.Example.com.key。不建议手动添加证书,可以用 acme.sh 一次性解决证书问题。
说到这玩意我有点气,不得不提 luci-app-acme——名字像官方 app,界面看着省心,其实年久失修,几乎没有功能可以正常使用,默认还不支持 cloudflare 自动化,最后还是回到官方 acme.sh 最踏实。
以下是我折腾时的一些笔记,可能有点乱,供参考:
- 安装
curl https://get.acme.sh | sh -s [email protected]
安装完成后,acme.sh 会位于 /root/.acme.sh 目录
但 ~ 下的文件,每次升级系统都会被清空,因此把 .acme.sh 目录转移到 /etc/
转移并进入这个目录 mv ~/.acme.sh/ /etc/acme.sh; cd /etc/acme.sh/,一定要去掉隐藏文件夹符号 .,否则 OpenWRT 的备份会忽略这个文件夹
如果转移目录的话,还需要修改以下文件的路径,并每次操作前 source 一下这份文件,不然证书还是会生成到~` 下面
root@OpenWRT:/etc/acme.sh# cat acme.sh.env
# 修改变量路径,每次操作前source一下这份文件
export LE_WORKING_DIR="/etc/.acme.sh"
alias acme.sh="/etc/.acme.sh/acme.sh"
然后 source 这份文件 source ./acme.sh.env,这样的话 acme 会把这里当成工作目录
root@OpenWRT:/etc/.acme.sh# ls
account.conf acme.sh.env deploy http.header
acme.sh ca dnsapi notify
在 account.conf 中加入
# 区域 ID,不要填成账户 ID 了
export CF_Zone_ID="dbxxxxxxxxxxxxxxxxxxxxxxxxx49"
export CF_Token="HkDxxxxxxxxxxxxxxxxxxxxxxxxxX6B"
在 CloudFlare 中可以获取 Token,不赘述。如果还是提示 API 未配置,就直接在终端中粘贴这两行,就这次生成 CA 需要,后面自动和手动 renew 都会利用现有的 CA。
- 获取通配证书
# 先注册账户
root@OpenWRT:/etc/acme.sh# ./acme.sh --register-account -m [email protected]
...
[Tue Oct 21 09:28:53 CST 2025] Registered
...
# 申请证书
root@OpenWRT:/etc/acme.sh# ./acme.sh --issue --dns dns_cf -d "*.Example.com" -d Example.com
...
[Tue Oct 21 09:35:37 CST 2025] Processing. The CA is processing your order, please wait. (1/30)
...
[Tue Oct 21 09:36:12 CST 2025] Le_LinkCert='https://acme.zerossl.com/v2/DV90/cert/ZwfTwf62OfrWJfn-8BC_CA'
[Tue Oct 21 09:36:14 CST 2025] Cert success.
-----BEGIN CERTIFICATE-----
MIID/TCCA4OgAwIBAgIQJVNY8s4byCEsTVwEK6ux+DAKBggqhkjOPQQDAzBLMQsw
...
h35UkbjZYZPD6vZYMEMAAjAIM1HWamQ9HmHPpZs1E43uO98ryH3YPwpLgUaKZVnA
AnKjtPEgSauzjhriukPRo8o=
-----END CERTIFICATE-----
[Tue Oct 21 09:36:14 CST 2025] Your cert is in:...
[Tue Oct 21 09:36:14 CST 2025] Your cert key is in:...
[Tue Oct 21 09:36:14 CST 2025] The intermediate CA cert is in:...
[Tue Oct 21 09:36:14 CST 2025] And the full-chain cert is in:...
成功的话生成一份 *.Example.com_ecc 文件夹,里面放置了申请到的证书文件:
root@OpenWRT:/etc/acme.sh# ls *.Example.com_ecc/
*.Example.com.cer *.Example.com.csr *.Example.com.key ca.cer
*.Example.com.conf *.Example.com.csr.conf backup fullchain.cer
- 部署证书
注意通配域名必须要用 "" 包裹,部署的目的文件,文件名不要有 *,最好用下划线 _ 代替。
./acme.sh --install-cert -d "*.Example.com" \
--key-file /etc/nginx/conf.d/certs/_.Example.com.key \
--fullchain-file /etc/nginx/conf.d/certs/_.Example.com.crt \
--reloadcmd '/etc/init.d/nginx restart'
nginx 会自动重启,关闭浏览器,清除缓存再次打开网页(快捷键 Ctrl+Shift+R),不安全提示消失。
- 刷新证书
acme 会自动向 crontab 中添加定时刷新
root@OpenWRT:/etc/acme.sh# crontab -l
# 这里要改一下路径,默认还是 /root/.acme.sh/
43 5 * * * "/etc/acme.sh"/acme.sh --cron --home "/etc/acme.sh" > /dev/null
手动刷:
# 时间没到的话要加 --force
acme.sh --renew -d *.Example.com --force
...
-----END CERTIFICATE-----
[Tue Oct 21 10:08:21 CST 2025] Your cert is in: /etc/.acme.sh/*.Example.com_ecc/*.Example.com.cer
[Tue Oct 21 10:08:21 CST 2025] Your cert key is in: /etc/.acme.sh/*.Example.com_ecc/*.Example.com.key
[Tue Oct 21 10:08:21 CST 2025] The intermediate CA cert is in: /etc/.acme.sh/*.Example.com_ecc/ca.cer
[Tue Oct 21 10:08:21 CST 2025] And the full-chain cert is in: /etc/.acme.sh/*.Example.com_ecc/fullchain.cer
[Tue Oct 21 10:08:21 CST 2025] Installing key to: /etc/nginx/conf.d/_.Example.com.key
[Tue Oct 21 10:08:21 CST 2025] Installing full chain to: /etc/nginx/conf.d/_.Example.com.crt
[Tue Oct 21 10:08:21 CST 2025] Running reload cmd: /etc/init.d/nginx restart
[Tue Oct 21 10:08:22 CST 2025] Reload successful
[Tue Oct 21 10:08:22 CST 2025] ===End cron===
可以看到不仅自动申请了证书,还替换了已经部署的证书。
这部分主要难点就是 OpenWRT 重启清理非持久化数据的问题,acme.sh 默认安装在/root 下面,是无法持久化的,必须转移安装位置。
Nginx 反向代理
root@OpenWRT:/etc/nginx# cat uci.conf
{
...
include conf.d/*.conf;
}
nginx 的反向代理配置在 /etc/nginx/conf.d 中编写,以下是一个模板,注释的地方需要注意
server {
listen 8443 ssl;
# IPv6 监听
listen [::]:8443 ssl;
# 监听的域名
server_name emby-server.domain.xyz;
# 证书的路径
ssl_certificate /etc/nginx/conf.d/_.domain.xyz.crt;
ssl_certificate_key /etc/nginx/conf.d/_.domain.xyz.key;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384;
ssl_prefer_server_ciphers off;
add_header Strict-Transport-Security "max-age=63072000" always;
location / {
proxy_pass http://10.0.0.111:8096;
# 不能用 Host $host,必须完整 IP + 端口
proxy_set_header Host 10.0.0.111:8096;
# 启用 WebSocket 监听,不懂就留着
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
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 https;
}
}
每一次反向代理的域名都要写一份配置文件,例如
root@OpenWRT:/etc/nginx/conf.d# ls | grep conf
emby-server.conf
hoage.conf
...
xi.conf
也可以写在一份文件里面,但不建议这样做,管理起来很麻烦。
写好配置后,重启 nginx 服务 /etc/init.d/nginx restart,不报错的话就行,如果报错则说明有配置没写对,注意有没有少字母或者多打标点符号。