> ## Content Index
> Fetch the complete content index at: https://iiong.com/llms.txt
> Use this file to discover other available public pages before exploring further.

# ASUS 路由器联动更新 Synology 群晖证书
- URL: https://iiong.com/asus-routers-link-to-synology-certificates/
- Published: 2024-11-21T08:13:26.000Z
- Updated: 2026-06-22T15:23:22.000Z
- Description: 最近折腾群晖 Nas 部署的时候结合路由器遇到一些问题，就写下记录，方便以后踩坑回头再看。这次遇到的问题是自动化更新 SSL 证书记录下来。
- Author: 淮城一只猫
- Tags: 奇技淫巧

### 前言

最近折腾群晖 `Nas` 部署的时候结合路由器遇到一些问题，就写下记录，方便以后踩坑回头再看。这次遇到的问题是自动化更新 `SSL` 证书，由于家中路由器性能过好承担一些任务，就包括了 `SSL` 证书更新，群晖那边共用路由器同一个域名，但系统需要 `SSL` 自定义域名证书，所以本文就是根据自身情况尽节约互联网资源来更新 `SSL` 证书。

### 环境

宽带：联通 `200M` ，支持 `IPv6`

主路由器：**华硕路由器** `RT-AX86U` 信息如下：

- `KoolCenter` 梅林官改：3004.388.8\_2
- 安装 `DDNS-Go` 用于自动更新 `IP`
- 安装 `Let's Encrypt` 插件用于自动更新域名证书
- 安装 `魔法猫咪` 用于畅通网络

群晖 `Nas`：`Synology DS220+` 信息如下：

- `DSM` 版本：`DSM 7.2.2-72806 Update 1`
- `RAID` 类别：`RAID0`
- 安装 `Docker`

### 光猫修改

宽带师傅安装宽带的时候，默认是路由模式，但这样对宽带速度牺牲很大，主要光猫设备性能不咋的，所以要从路由模式修改成桥接，然后用路由器拨号就行，正好尝试下是否有公网地址。

我家的光猫设备类型`GPON/4+1（C 系统）` 首先需要知道管理员超级密码，光猫后面账号是默认密码，并没什么用，根据我的型号直接进入光猫网关：

```shell
光猫后台：http://192.168.1.1/cu.html 
开启Telnet：http://192.168.1.1/telnet?enable=1&key=光猫MAC
关闭Telnet：http://192.168.1.1/telnet?enable=0&key=光猫MAC

```

直接开启 `Telnet` 后在终端输入：`telnet 192.168.1.1` ，账号为 `admin` 密码是 `Fh@你家光猫的MAC地址后六位`

进来之后输入命令：

```bash
/var # load_cli factory
cli main init.....argc=2 /r/n
consoleFd = 3
----cli_main_begin 475 3
########cli sip init ok########
cannot   open   (fh-bin)confile
cl_run_memfile_config  unlocked success!

Config\factorydir# show admin_name 
Success! admin_name=CUAdmin
Config\factorydir# show admin_pwd
Success! admin_pwd=CUAdminPs6nAyME

```

这里面的密码还会随时变，所以需要进入的时候建议从头再查看，然后进入基本配置选项中的网络连接修改成桥接模式即可，这里就不再详细描述，这类教程网上一大把。

### AX86U 路由器设置

设置好光猫后在路由器设置拨号账号，输入宽带和密码即可，然后在 `IPv6` 设置选择 `Native` 选项即可获取 `IPv6` 地址。去无线网络 - 专业设置关闭 `无线传输公平性` 和 `通用 Beamforming` 选项可以解决家里智能设备离线的问题。去软件中心安装 `Let's Encrypt` 插件配置好相关配置后，在 `USB 相关应用` 新建一个 `FTP` 账号，这个账号用于给群晖自动更新证书用的，这里注意是专门新建一个文件夹，然后文件夹只授权 `Read` 权限，不需要给他读写权限以免账号被黑。

然后登录路由器生成 `RSA` 密钥对用于路由器免密登录群晖来执行脚本操作：

```bash
# 大多数环境下生成密钥是ssh-keygen，但在华硕路由器命令是dropbearkey
dropbearkey -t rsa -f id_rsa

```

生成好的密钥 `id_rsa` 文件放在 `~/.ssh` 文件内，然后将 `id_rsa.pub` 内容复制到记录下来，到后面会用到。

> 后续我发现路由器重启 .ssh 目录密钥会丢失，所以换个位置，例如我放在硬盘上：`/tmp/mnt/WDisk/WorkTemp/ssh/id_rsa`

路由器安装的是 [Let's Encrypt](https://github.com/koolshare/rogsoft/tree/master/acme?ref=iiong.com) 插件实际上是 `acme.sh` 实现的，这里就不关注细节，我找了下插件的位置，在路由器的 `/koolshare/scripts/` 位置下面的 `acme_config.sh` ，查阅代码在更新证书任何情况下会走 `install_cert` 方法，所以在 `install_cert` 方法结束后执行更新证书到 `FTP` 公开地方然后再执行群晖的脚本即可：

```bash
install_cert(){
	# ... 更多内容就不展示了
  # 在最后添加下面代码
	# 复制证书并且执行群晖脚本
	cp /koolshare/acme/你的域名/你的域名.cer /mnt/WDisk/iNas/cert.pem
	cp /koolshare/acme/你的域名/你的域名.key /mnt/WDisk/iNas/key.pem
	cp /koolshare/acme/你的域名/fullchain.cer /mnt/WDisk/iNas/fullchain.pem
	# 此处是群晖 ssh 免密登录执行脚本
	ssh -o StrictHostKeyChecking=no -i ~/.ssh/id_rsa root@192.168.50.2 -p 2233 '/root/scripts/my_script.sh'
	# ssh -o StrictHostKeyChecking=no -i /tmp/mnt/WDisk/WorkTemp/ssh/id_rsa root@192.168.50.2 -p 2233 '/root/scripts/my_script.sh'
}

```

**路由器端口转发**

外部网络(WAN) - 端口转发新增配置：

- 服务名称：NAS Server
- 通讯协议：TCP
- 外部端口：5000,5001,6690
- 内部端口：空
- 本地 IP 地址：填写 NAS 本地 IP 地址
- 来源 IP：空

> 5000,5001对应 Synology DSM 端口号，6690 是强制要求转发端口，否则无法正常使用 Drive 等相关应用

### Synology 群晖设置

群晖要打开控制面板 - 终端机和 `SNMP` \- 启用 `SSH`，然后修改端口号，建议修改成千位端口号，打开终端登录群晖的 `ssh`：

```bash
ssh 你的用户名@群晖ip -p 端口号
# 登录 root 账号
sudo -i
cd /root/.ssh
# 如果提示文件不存在那就新建 .ssh 文件夹
mkdir .ssh
cd .ssh
vi authorized_keys
# 将上面路由器复制的 id_ras.pub 文件内容复制进去
chmod 755 ~
chmod 600 ~/.ssh/authorized_keys

vim /etc/ssh/sshd_config
# 编辑 sshd_config 将前面 # 删除
PubkeyAuthentication yes
AuthorizedKeysFile .ssh/authorized_keys

```

然后在群晖 `控制面板 -> 终端机和 SNMP` 关闭再开启 `SSH`，登录路由器输入：

```bash
ssh -i ~/.ssh/id_rsa root@192.168.50.2 -p 2233

```

是否直接能登录群晖，如果提示权限不足请检查下是否设置 `authorized_keys` 权限为 `600` 。登录成功回到群晖的 `ssh` 终端在 `/root` 新建一个文件：

```bash
mkdir scripts
vim my_script.sh

```

脚本内容编写如下：

```bash
#!/bin/bash

# 定义FTP相关变量
FTP_SERVER="路由器 ftp 地址包括端口号：192.168.50.1"
FTP_USER="路由器 ftp 账户名"
FTP_PASSWORD="路由器 ftp 密码"
FTP_DIR="路由器 ftp 目录，例如我的是：/WDisk/iNas/"

# 定义本地相关变量
LOCAL_DIR="."

# 定义证书相关变量
CERT_FILE="cert.pem"
KEY_FILE="key.pem"
PRIVKEY_FILE="privkey.pem"
FULLCHAIN_FILE="fullchain.pem"

# 定义目标目录相关变量
CERT_DIRS=(
    "/volume1/docker/AList/ssl/"
    "/usr/syno/etc/certificate/system/default/"
    "/usr/syno/etc/certificate/_archive/$(cat /usr/syno/etc/certificate/_archive/DEFAULT | tr -d '\n')/"
)

# 定义日志文件路径
LOG_FILE="$HOME/scripts/script_log.txt"

# 日志记录函数
log_message() {
    local level=$1
    local message=$2
    local line_number=${3:-"N/A"}
    local timestamp=$(date +"%Y-%m-%d %H:%M:%S")
    echo "[$timestamp] [LINE:$line_number] [$level] $message" >> "$LOG_FILE"
}

# 错误处理函数
handle_error() {
    local message=$1
    local exit_code=${2:-1}
    log_message "ERROR" "$message" "${BASH_LINENO[0]}"
    exit "$exit_code"
}

# 检查脚本运行环境
check_environment() {
    command -v wget >/dev/null 2>&1 || handle_error "wget 命令未安装，请安装后重试。" 2
    command -v openssl >/dev/null 2>&1 || handle_error "openssl 命令未安装，请安装后重试。" 2
    command -v systemctl >/dev/null 2>&1 || handle_error "systemctl 命令未安装，请检查环境。" 2
}

# 下载证书文件函数
download_file() {
    local file=$1
    log_message "INFO" "开始下载文件 $file ..." "${BASH_LINENO[0]}"
    wget -t 3 -P "$LOCAL_DIR" "ftp://$FTP_USER:$FTP_PASSWORD@$FTP_SERVER$FTP_DIR$file"
    if [ $? -ne 0 ]; then
        handle_error "文件 $file 下载失败，请检查网络连接或FTP服务器设置。" 3
    fi
}

# 转换密钥格式
convert_key_format() {
    log_message "INFO" "开始转换密钥格式..." "${BASH_LINENO[0]}"
    openssl pkcs8 -topk8 -inform PEM -in "$LOCAL_DIR/$KEY_FILE" -outform PEM -nocrypt -out "$LOCAL_DIR/$PRIVKEY_FILE"
    if [ ! -s "$LOCAL_DIR/$PRIVKEY_FILE" ]; then
        handle_error "密钥格式转换失败，请检查 openssl 配置及相关文件。" 4
    fi
}

# 复制文件到目标目录
copy_and_check() {
    local source_file=$1
    local target_dir=$2
    log_message "INFO" "复制文件 $source_file 到目录 $target_dir ..." "${BASH_LINENO[0]}"
    cp -f "$source_file" "$target_dir"
    if [ $? -ne 0 ]; then
        handle_error "文件 $source_file 复制到 $target_dir 失败，请检查权限或磁盘空间。" 5
    fi
}

# 重启服务
restart_service() {
    local service=$1
    log_message "INFO" "重启服务 $service ..." "${BASH_LINENO[0]}"
    systemctl reload "$service"
    if [ $? -ne 0 ]; then
        handle_error "$service 服务重载失败，请检查配置。" 6
    fi
    systemctl restart "$service"
    if [ $? -ne 0 ]; then
        handle_error "$service 服务重启失败，请检查配置。" 7
    fi
}

# 清理临时文件
cleanup_files() {
    local files=("$@")
    log_message "INFO" "开始清理临时文件..." "${BASH_LINENO[0]}"
    for file in "${files[@]}"; do
        rm -f "$file"
    done
    log_message "INFO" "临时文件清理完成。" "${BASH_LINENO[0]}"
}

# 主程序
main() {
    check_environment

    # 下载文件
    download_file "$CERT_FILE"
    download_file "$KEY_FILE"
    download_file "$FULLCHAIN_FILE"

    # 转换密钥格式
    convert_key_format

    # 复制文件到目标目录
    for cert_dir in "${CERT_DIRS[@]}"; do
        copy_and_check "$LOCAL_DIR/$CERT_FILE" "$cert_dir"
        copy_and_check "$LOCAL_DIR/$PRIVKEY_FILE" "$cert_dir"
        copy_and_check "$LOCAL_DIR/$FULLCHAIN_FILE" "$cert_dir"
    done

    # 重启服务
    restart_service "nginx"

    # 清理临时文件
    cleanup_files "$LOCAL_DIR/$CERT_FILE" "$LOCAL_DIR/$KEY_FILE" "$LOCAL_DIR/$PRIVKEY_FILE" "$LOCAL_DIR/$FULLCHAIN_FILE"

    log_message "INFO" "脚本执行完成！" "${BASH_LINENO[0]}"
    
    exit
}

# 执行主程序
main "$@"

```

编写好的脚本再授权：`chmod +x ./my_script.sh`，脚本内容主要内容就是从路由器的 `FTP` 读取 `pem` 文件然后下载群晖，如果不确定 `FTP` 是否能用可以用命令测试下：`curl ftp://账号:密码@192.168.50.1/WDisk/iNas/cert.pem` 如果有返回内容那就没问题的，群晖下载证书后，还需要将原先的 `私钥` 转换成 `PKCS#8` 的格式密钥，然后再将密钥分别复制到几个地方，除了群晖自带的 `ssl` 默认证书，还涉及到 `AList` 的 `ssl` 证书。如果路由器执行定时脚本就会关联执行到群晖的脚本，就不需要操心手动更新这么多证书了。

### 一些问题

~~**梅林华硕路由器端口转发在重启的时候失效了**~~

这个问题存在了很久，主要问题不明确，重启概率不多，有时候重启发现家里部分设备不能在网络上访问，查询好久才发现是端口转发失效了，明明在 `webui` 显示开启状态，实际上状态是不对，所以后面查询一些 `API` 操作命令：

```bash
/bin/sleep 10
# vts_enable_x 为端口转发 1=启动 0=禁用
nvram set vts_enable_x=1
# 提交变量
nvram commit
# 重启防火墙
service restart_firewall

```

根据 [Asuswrt-Merlin 文档得知](https://github.com/RMerl/asuswrt-merlin.ng/wiki/User-scripts?ref=iiong.com) 在路由器重启放在 **services-start** 脚本最合适了，打开 `/jffs/scripts/services-start` 添加上面命令即可。

**路由器更新 SSL 证书**

新建脚本 `install_cert.sh` 如下：

```sh
#!/bin/sh

SRC_DIR="/jffs/.koolshare/acme/router.yourdomain.com"
DST_DIR="/jffs/.cert"

# 1. 确保目标目录存在
[ -d "$DST_DIR" ] || mkdir -p "$DST_DIR"

# 2. 复制并按 Merlin 要求重命名
cp -f "$SRC_DIR/fullchain.cer" "$DST_DIR/cert.pem"
cp -f "$SRC_DIR/router.yourdomain.com.key" "$DST_DIR/key.pem"

# 3. 权限收紧（httpd 会读取，但别给全世界777）
chmod 600 "$DST_DIR/cert.pem" "$DST_DIR/key.pem"

# 4. 通知 httpd 重新载入证书
service restart_httpd

```

**刷新路由器 DNS 缓存**

同样在 `/jffs/scripts/services-star` 脚本添加：

```bash
# 刷新 DNS 缓存
/bin/sleep 60
service restart_dnsmasq

```

**Synology DDNS 获取公网 IP 不对**

这个一般是用魔法导致的，走的路线不对，可以自己定义规则即可：

```
DOMAIN-SUFFIX,checkip.synology.com
DOMAIN-SUFFIX,checkipv6.synology.com
DOMAIN-SUFFIX,checkport.synology.com
DOMAIN-SUFFIX,ddns.synology.com

```

将上面规则走 `DIRECT` 就可以正确获取公网地址了。

**修正群晖自带域名证书问题**

群晖上面默认绑定自己的域名之外，我还额外启用群晖自带的域名访问：`https://demo.synology.me`，访问发现证书绑定的是自己域名证书而不是群晖自带的证书，研究下只需要修改 `nginx` 配置文件即可，在 `/etc/nginx/sites-enabled/` 新建 `server.ReverseProxyMy.conf` 内容如下：

```nginx
server {
	listen 1998;
	listen [::]:1998;

	server_name demo.synology.me;

	gzip on;

	include conf.d/alias.*.conf;
	root /usr/syno/synoman;
	index index.cgi;

	ignore_invalid_headers off;

	include /usr/syno/share/nginx/conf.d/dsm.*.conf;
	include conf.d/dsm.*.conf;

	location = / {
		try_files $uri /index.cgi$is_args$query_string;
	}

	location ~ ^/volume(?:X|USB|SATA|Gluster)?\d+/ {
		internal;

		root /;

		open_file_cache off;

		include conf.d/x-accel.*.conf;
	}

	location ~ /webman/modules/(PersonalSettings|ExternalDevices|FileBrowser)/index_ds.php$ {
		alias /usr/syno/share/OAuth/index_ds.php;
		default_type text/html;
	}

	location ~ \.cgi {
		include scgi_params;
		scgi_pass synoscgi;

		scgi_read_timeout 3600s;
	}

	location ~ /synoscgi.sock/socket.io/ {
		proxy_read_timeout 3600s;
		include proxy.conf;
		rewrite /synoscgi.sock/(.*)$ /$1 break;
		proxy_set_header Connection $connection_upgrade;
		proxy_pass http://synoscgi.sock;

	}

	location ~ /api/ {
		include scgi_params;
		scgi_pass synoscgi_restfulapi;

		scgi_read_timeout 3600s;
	}

	error_page 403 404 500 502 503 504 /dsm_error_page;

	location /dsm_error_page {
		internal;
		root /usr/syno/share/nginx;
		rewrite (.*) /error.html break;
		allow all;
	}

	location ~ ^/webman/modules/Indexer/ {
		deny all;
	}

	location ~ ^/webapi/lib/ {
		deny all;
	}

	location ~ ^/webapi/(:?(:?.*)\.lib|(:?.*)\.api|(:?.*)\.auth|lib.def)$ {
		deny all;
	}

	location ~ /\. {
		access_log off;
		log_not_found off;
		deny all;
	}

	location ~* \.(?:js|css|png|jpg|gif|ico)$ {
		access_log off;
		log_not_found off;
	}

	location = /favicon.ico {
		access_log off;
		log_not_found off;
	}

	location = /robots.txt {
		allow all;
		access_log off;
		log_not_found off;
	}

}

server {
	listen 1999 ssl;
	listen [::]:1999 ssl;

	server_name demo.synology.me;

	location ^~ /.well-known/acme-challenge {
		root /var/lib/letsencrypt;
		default_type text/plain;
	}

	ssl_certificate /usr/syno/etc/certificate/_archive/MSyrAt/cert.pem;
	ssl_certificate_key /usr/syno/etc/certificate/_archive/MSyrAt/privkey.pem;

	include /usr/syno/etc/security-profile/tls-profile/config/system_quickconnect.conf*;

	include conf.d/ssl.*.conf;

	include conf.d/alias.*.conf;
	root /usr/syno/synoman;
	index index.cgi;

	ignore_invalid_headers off;

	include /usr/syno/share/nginx/conf.d/dsm.*.conf;
	include conf.d/dsm.*.conf;

	location = / {
		try_files $uri /index.cgi$is_args$query_string;
	}

	location ~ ^/volume(?:X|USB|SATA|Gluster)?\d+/ {
		internal;

		root /;

		open_file_cache off;

		include conf.d/x-accel.*.conf;
	}

	location ~ /webman/modules/(PersonalSettings|ExternalDevices|FileBrowser)/index_ds.php$ {
		alias /usr/syno/share/OAuth/index_ds.php;
		default_type text/html;
	}

	location ~ \.cgi {
		include scgi_params;
		scgi_pass synoscgi;

		scgi_read_timeout 3600s;
	}

	location ~ /synoscgi.sock/socket.io/ {
		proxy_read_timeout 3600s;
		include proxy.conf;
		rewrite /synoscgi.sock/(.*)$ /$1 break;
		proxy_set_header Connection $connection_upgrade;
		proxy_pass http://synoscgi.sock;

	}

	location ~ /api/ {
		include scgi_params;
		scgi_pass synoscgi_restfulapi;

		scgi_read_timeout 3600s;
	}

	error_page 403 404 500 502 503 504 /dsm_error_page;

	location /dsm_error_page {
		internal;
		root /usr/syno/share/nginx;
		rewrite (.*) /error.html break;
		allow all;
	}

	location ~ ^/webman/modules/Indexer/ {
		deny all;
	}

	location ~ ^/webapi/lib/ {
		deny all;
	}

	location ~ ^/webapi/(:?(:?.*)\.lib|(:?.*)\.api|(:?.*)\.auth|lib.def)$ {
		deny all;
	}

	location ~ /\. {
		access_log off;
		log_not_found off;
		deny all;
	}

	location ~* \.(?:js|css|png|jpg|gif|ico)$ {
		access_log off;
		log_not_found off;
	}

	location = /favicon.ico {
		access_log off;
		log_not_found off;
	}

	location = /robots.txt {
		allow all;
		access_log off;
		log_not_found off;
	}

}

```

只需要修改 `ssl_certificate` 和 `ssl_certificate_key` 地方，其他都是从 `nginx.conf` 复制而来，可以从那边复制过来一份也可以的，上面的证书地址需要自己搜索才能确定：

```bash
cd /usr/syno/etc/certificate/_archive/
grep -ri "demo.synology.me" /usr/syno/etc/certificate/_archive/

/usr/syno/etc/certificate/_archive/MSyrAt/renew.json:   "domains" : "demo.synology.me;*.demo.synology.me",

```

输出确定下来就知道在 `/usr/syno/etc/certificate/_archive/MSyrAt` 目录下，再填入 `nginx` 配置文件里面即可。

```bash
# 重启 nginx 服务
systemctl reload nginx

```

### 结束

脚本为什么不用 `curl` 是因为群晖的 `curl` 不支付 `ftp` 协议：

```bash
curl -u "iNas:123456" ftp://192.168.50.1/WDisk/iNas/key.pem
curl: (1) Protocol "ftp" not supported or disabled in libcurl

```

所以没办法改用 `wget` 来下载证书。

另外值得注意是 `控制面板 - 安全性 - 证书` 要先上传自己域名的证书，作为初始化，方便后续脚本自动更新证书。