1. 为什么Docker里的Alist一定要配SSL证书
先说个我自己的经历。很早之前我在一台小机器上用Docker跑Alist,图省事直接用http://IP:5244访问,用了大半年一直没当回事。后来有一次在外部网络环境下列表文件,浏览器直接弹了个“不安全连接”的警告,我当时还以为是浏览器抽风,随手点了继续。直到有一天我发现自己登录Alist管理后台的时候,被某个中间环节截了密码——虽然那次没有造成实际损失,但让我彻底明白了一个道理:任何暴露在公网的服务,没有SSL证书就等于裸奔。
Alist这个项目本身是一个支持多存储协议的文件列表程序,很多人拿它当网盘聚合入口,挂载各种云盘、本地目录、对象存储。它会涉及账号密码、Token、文件元数据等敏感信息。如果只在局域网里玩,那HTTP尚可接受;但只要你做了端口映射、用域名访问、或者让朋友从外面访问你的Alist,就必须用HTTPS。SSL证书的核心作用不复杂:一是加密传输内容,防止中间人窃听;二是验证服务器身份,让用户确信访问的是你的服务而不是冒牌货。
Docker部署Alist的场景有点特殊:容器本身只监听HTTP端口,证书配置不在Alist应用内部完成,而是通过外层反代、Docker端口映射、或挂载证书文件等方式实现。这就导致很多人在这个环节卡壳——明明证书申请好了,却不知道应该放在哪里、怎么让Alist用上。这篇文章就专门说清楚这件事,手把手带你把Docker里的Alist加上SSL证书,顺便把自动续期和排查问题的方法也一并讲了。
如果你是刚接触Docker或者刚接触Alist,这篇文章同样适合你。我会尽量把原理讲清楚,把每一步的命令和配置文件都贴全,你照着复制粘贴基本就能跑通。如果你已经是老手,重点看第3章的架构选型和第5章的避坑排查,那部分是我实操中积累的硬经验。
2. 三种主流证书获取方式,到底该怎么选
给Alist配SSL证书,第一步不是操作,而是选型。证书从哪来、怎么续期,直接决定了你后续的维护成本和使用体验。目前主流的选择有三种:云厂商免费证书、Let's Encrypt免费证书、以及自签名证书。各有各的适用场景,我分别说下。
2.1 阿里云免费证书:国内访问友好,手动续期
阿里云SSL证书服务每年都可以申请免费的单域名证书,一般有效期是3个月(具体以控制台展示为准)。这种证书的优点是国内链路访问体验好,兼容性高,品牌信任度高;缺点是续期需要登录控制台手动操作,每年好几次,容易忘记。
热词里提到的“阿里云ssl证书免费续期”,我理解说的就是这件事。实际操作流程是:
- 登录阿里云控制台,进入数字证书管理服务。
- 在SSL证书页面点击“申请免费证书”,填写域名。
- 按提示完成域名验证,一般有DNS验证和文件验证两种模式。DNS验证需要你到域名解析处加一条TXT记录。
- 签发成功后下载证书文件,会得到一个PEM格式的证书文件和KEY私钥文件。
- 把这两个文件放到服务器上,配置到Nginx/Caddy等反代服务中。
注意:阿里云免费证书一个自然年内有数量限制,之前是20个,现在是每年50个左右(具体看活动),个人使用完全够。但如果你手头有十来个域名要配,建议还是考虑Let's Encrypt全自动方案。
我对这种方式的评价是:国内站点、用阿里云DNS解析、一年手动续几次也不嫌烦的人,选它最省心。特别是你的域名本身就在阿里云买的,DNS验证操作非常快。
2.2 Let's Encrypt免费证书:全自动续期,运维友好
Let's Encrypt是目前全球使用量最大的免费证书颁发机构,证书有效期90天,但支持通过acme.sh或certbot定时任务自动续期。配上泛域名证书申请能力,一个证书可以覆盖所有子域名。
我自己的Alist就是用这种方式。优点非常明显:
- 免费,没有任何数量限制。
- 自动化程度高,配置好之后基本不用管。
- 支持通配符证书,
*.example.com一个证书搞定所有子服务。 - 与Caddy、Nginx等反代工具集成得很好。
缺点是:Let's Encrypt的证书链在国内某些网络条件下偶尔出现验证慢的现象,但实际使用中影响不大;另外续期依赖服务器上的定时任务,如果机器长期关机或任务被干掉,证书就可能过期。
对于需要长期稳定运行、追求“一劳永逸”的Docker用户,我优先推荐Let's Encrypt配合acme.sh的方案。第4章我会给出完整配置。
2.3 自签名证书:仅限内网测试
自签名证书就是自己当CA签发的证书,浏览器会报“不受信任”。它只适合纯内网测试、临时调试、或者用在开发环境。如果你只是在家里用Docker跑Alist,不打算映射到公网,那自签名也能凑合;但只要你打开浏览器看到红色警告就烦躁,就别费这个劲了。
Windows上“windows生成ssl证书”这类热词,通常指的就是用OpenSSL自签证书,或者用PowerShell的New-SelfSignedCertificate命令。我不建议在生产环境用自签证书,因为浏览器信任问题会带来大量误报和用户困惑。
我的结论很简单:公网服务用Let's Encrypt或云厂商证书,内网测试用自签,不要在这件事上纠结太久。
3. Docker部署Alist的SSL配置架构选型
Alist官方Docker镜像默认监听5244端口,直接暴露HTTP服务。给这个服务加SSL,本质上就是两种做法:要么在Alist前面加一层能终结HTTPS的反代服务,要么直接让Docker端口映射时承担加密工作——但后者并不现实,因为端口映射本身不处理加密。
所以实际可行的架构有三种。
3.1 方案A:Nginx反代 + 证书文件
这是最传统、最通用的方式。你在宿主机或另一个容器里跑Nginx,把443端口映射到宿主机,然后Nginx把请求转发给Alist容器的5244端口。证书文件配置在Nginx里。
你可能会问,为什么非要加一层Nginx,不能直接让Alist支持HTTPS吗?Alist的新版本其实有证书配置选项,但直接在容器里挂证书有个问题:证书续期时要重启容器,而且日志、报错、性能调优都绑在Alist进程上,不如让专业的HTTP服务器干专业的活。Nginx处理SSL、静态文件、连接复用都是一把好手,Alist专注做文件列表服务,各司其职更合理。
nginx配置示例大概长这样:
server { listen 443 ssl http2; server_name alist.example.com; ssl_certificate /etc/nginx/certs/alist.example.com/fullchain.pem; ssl_certificate_key /etc/nginx/certs/alist.example.com/key.pem; ssl_protocols TLSv1.2 TLSv1.3; ssl_ciphers HIGH:!aNULL:!MD5; location / { proxy_pass http://127.0.0.1:5244; 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; } }用不用独立容器跑Nginx?两种选择都有道理。宿主机安装Nginx生态成熟,但污染了宿主机环境;用docker-compose把Alist和Nginx编排在一起则更整洁。我建议用docker-compose统一管理,这样迁移、备份、重建都方便,也很符合“Docker部署Alist”的场景设定。
3.2 方案B:Caddy反代 + 自动证书
Caddy是我特别想推荐给新手的方案,因为它内置了自动HTTPS功能。你只需要写一行配置,Caddy会自动申请证书、配置续期、处理HTTP到HTTPS跳转。如果你用的是Let's Encrypt方案,Caddy可以让复杂度再降一个维度。
Caddyfile配置如下:
alist.example.com { reverse_proxy alist:5244 }就这么多。Caddy会自动为alist.example.com申请和续期Let's Encrypt证书。你不需要手动放证书文件,不需要写复杂的SSL配置段,不需要操心续期任务。对于只想快速把Alist跑起来、不想折腾Nginx配置的人来说,Caddy是体验最好的方案。
但它也有缺点:Caddy默认申请证书走的是Let's Encrypt,在大陆网络环境下偶发挑战失败;而且Caddy的配置语法虽然简单,但出了问题排错时,社区资料没有Nginx那么丰富。如果你的环境网络访问国外正常,Caddy绝对是个好选择。
3.3 方案C:Nginx Proxy Manager可视化配置
如果你不喜欢写配置文件,喜欢图形化管理,那么Nginx Proxy Manager(NPM)值得一试。它本质上是Nginx的封装,提供Web界面,让你在浏览器里完成域名绑定、证书申请、反向代理配置。从热词里看,很多人对这类“面板化”工具接受度很高。
在NPM里给Alist加SSL,流程是:
- 新建一个Proxy Host,域名填
alist.example.com,转发IP填Alist容器名或IP,端口填5244。 - SSL选项卡里选择“Request a new SSL Certificate”,填邮箱和域名。
- 保存后NPM自动申请证书并配置HTTPS。
NPM对新手友好,界面逻辑清晰,而且证书管理面板里能看到到期时间、手动续期按钮。缺点是NPM本身也是一个Docker容器,你需要额外分配80和443端口给它;在你机器上如果已经有Nginx占用了这些端口,就得先处理冲突。
提示:无论你选择Nginx、Caddy还是NPM,核心要点都一样——让一个持久运行的进程持有443端口,并维护证书文件。Alist容器本身不需要知道证书的存在,它只负责提供HTTP服务。
4. 完整实操:使用acme.sh + Nginx给Docker里的Alist加SSL
这一章我以Ubuntu 22.04 + Docker Compose + Nginx反代为例,完整走一遍从申请证书到配置完成的流程。这个方案适合多数人,也是我实际生产环境的配置。整个过程分四步。
4.1 第一步:用docker-compose编排Alist和Nginx
先看一下目录结构。我习惯把Alist和Nginx放同一个compose文件里,这样一条命令就能启动整套服务。
mkdir -p /opt/alist-ssl/{data,nginx/certs,nginx/conf} cd /opt/alist-ssl编写docker-compose.yml:
version: "3.8" services: alist: image: xhofe/alist:latest container_name: alist restart: always volumes: - ./data:/opt/alist/data environment: - PUID=0 - PGID=0 - UMASK=022 # 不直接映射端口,由Nginx内部转发,避免端口暴露风险 networks: - alist-net nginx: image: nginx:stable-alpine container_name: alist-nginx restart: always ports: - "80:80" - "443:443" volumes: - ./nginx/conf:/etc/nginx/conf.d - ./nginx/certs:/etc/nginx/certs networks: - alist-net networks: alist-net: driver: bridge注意几个细节:
- Alist容器不映射端口到宿主机,只有Nginx暴露80和443,这样外部无法直接通过5244端口绕过HTTPS访问Alist。
./data目录持久化Alist配置和数据库,删除容器不丢数据。- Nginx的证书目录挂在宿主机上,后续acme.sh写证书文件就能直接生效。
先启动Alist(Nginx还没配置,先不急着启动也可以,直接整体启动也行,反正Nginx配置缺失不影响Alist启动):
docker-compose up -d alist docker exec -it alist ./alist admin random第二条命令会输出Alist管理员的初始账号密码。记下来,后面登录后台用。
4.2 第二步:安装acme.sh并申请证书
acme.sh是一个用Shell脚本实现的ACME客户端,非常轻量,配合cron实现自动续期。安装方式一行命令:
curl https://get.acme.sh | sh -s email=youremail@example.com安装完成后,需要重新加载shell环境变量,然后配置DNS API实现自动验证。我以阿里云为例(因为热词里提到阿里云相关,而且国内用阿里云DNS解析域名的人多):
export Ali_Key="你的阿里云AccessKey ID" export Ali_Secret="你的阿里云AccessKey Secret" acme.sh --issue --dns dns_ali -d alist.example.com提示:AccessKey需要具有DNS管理的权限,建议在RAM里创建子用户,只授权
AliyunDNSFullAccess,不要用主账号Key,安全习惯要养成。
签发成功后,把证书安装到Nginx的证书目录:
acme.sh --install-cert -d alist.example.com \ --key-file /opt/alist-ssl/nginx/certs/alist.example.com/key.pem \ --fullchain-file /opt/alist-ssl/nginx/certs/alist.example.com/fullchain.pem \ --reloadcmd "docker exec alist-nginx nginx -s reload"--fullchain-file会写入包含证书链的完整文件,Nginx需要的就是这个。最后的reloadcmd参数定义了证书更新后自动执行的操作——这里通过docker exec让Nginx容器重载配置。如果你用宿主机Nginx,就改成nginx -s reload。
4.3 第三步:编写Nginx配置并启动
在/opt/alist-ssl/nginx/conf/目录下新建alist.conf:
server { listen 80; server_name alist.example.com; # 强制跳转HTTPS return 301 https://$host$request_uri; } server { listen 443 ssl http2; server_name alist.example.com; ssl_certificate /etc/nginx/certs/alist.example.com/fullchain.pem; ssl_certificate_key /etc/nginx/certs/alist.example.com/key.pem; ssl_session_timeout 1d; ssl_session_cache shared:SSL:10m; 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:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384; ssl_prefer_server_ciphers off; client_max_body_size 500M; location / { proxy_pass http://alist:5244; 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_read_timeout 300s; } }关于这段配置的几点解释:
proxy_pass http://alist:5244;用的是docker-compose服务名alist,在同一个networks里可以直接通过服务名访问,不需要关心Alist容器的实际IP,这是Compose网络的一个便利特性。client_max_body_size 500M;允许上传大文件。Alist本身支持文件上传,大小限制要在这里放开,否则上传超过默认1MB的文件会被Nginx直接拒掉。proxy_set_header X-Forwarded-Proto $scheme;把协议头传交给Alist,这样Alist后台能看到真实访问协议,某些情况下影响重定向链接的生成。
保存配置后启动整个服务栈:
docker-compose up -d然后检查Nginx是否正常运行:
docker ps docker logs alist-nginx -f如果一切正常,Nginx日志里不会有报错。这时用浏览器访问https://alist.example.com,你应该能看到安全的HTTPS锁标志了。
4.4 第四步:验证证书自动续期
acme.sh默认安装了cron任务,你可以手动检查续期任务是否存在:
crontab -l正常情况下会有一条记录:
0 0 * * * "/root/.acme.sh"/acme.sh --cron --home "/root/.acme.sh" > /dev/null想手动模拟一次续期,可以运行:
acme.sh --cron --force这个命令会检查证书有效期,如果剩余时间大于60天则不会重复签发,加--force才会强制更新。验证逻辑没问题之后,你的证书就处于自动续期状态了。
5. SSL配置中最常见的5个坑与排查方案
我见过很多人本来配好了SSL,结果一个低级错误导致网站打不开,或者重启后证书突然失效。这里把我踩过的坑和排查经验完整列出来。
5.1 证书文件权限或路径错误
Nginx启动时报[emerg] cannot load certificate,几乎都是证书路径写错或者权限不对。
排查方法:
docker exec -it alist-nginx ls -l /etc/nginx/certs/alist.example.com/确认fullchain.pem和key.pem都在。如果只有root用户可以读,Nginx工作进程可能无法访问。我一般统一执行:
chmod 644 /opt/alist-ssl/nginx/certs/alist.example.com/*注意:私钥文件理论上权限可以收紧到600,但由于Nginx容器内的用户映射机制,644更稳妥。如果你追求严格安全,可以单独设置容器的user映射,但那属于进阶操作。
5.2 容器重启后证书目录丢失
这是docker-compose挂载路径写错导致的。如果你把证书放在了容器可写层而不是宿主机挂载目录,容器重建时数据就会丢失。
解决办法是确认docker-compose.yml里的volumes挂载点与实际路径一致。我在第4章示例中把证书路径统一放在./nginx/certs目录下,容器删除重建后证书依然存在。
5.3 强制HTTPS跳转导致死循环
如果你同时配置了http -> https跳转,但Nginx的proxy_pass又把请求转发到Alist时带有X-Forwarded-Proto头不正确,可能触发死循环。
一个典型的错误配置是:Alist后台设置了“站点地址”为https://alist.example.com,但X-Forwarded-Proto头没有传过去,导致Alist生成的重定向链接还是HTTP。你需要注意在Nginx里把X-Forwarded-Proto带上,同时到Alist后台管理的“设置-基础设置-站点地址”中填写完整的HTTPS地址。这样两边保持一致,就不会有跳转循环问题。
5.4 防火墙或安全组没有放开443端口
如果你的browser显示连接超时,但证书检查一切正常,很可能就是443端口压根没通。检查云安全组和本机防火墙:
ufw status verbose iptables -L -n ss -tlnp | grep 443如果用的是云服务器,登录控制台查看安全组是否放行了TCP 443入方向规则。很多云厂商默认只开22和80,443要手动加规则。
5.5 acme.sh续期后Nginx没有重载
acme.sh自动续期成功后,需要触发Web服务重载才能让新证书生效。我在第4章的--reloadcmd里写了docker exec alist-nginx nginx -s reload,这是关键。但很多教程里没有这一步,导致证书明明已经续期,Nginx还在用旧证书文件。
验证你是否遇到这个问题,可以看Nginx日志:
docker logs alist-nginx --tail 50如果看到类似NGINX reloaded的日志,说明reload执行了。检查证书实际生效时间:
openssl x509 -enddate -noout -in /opt/alist-ssl/nginx/certs/alist.example.com/fullchain.pem输出里的notAfter时间应该是新的到期日,这就说明证书和Nginx都正常。
6. 从Nginx换到Caddy后的体验对比
第4章我给的方案以Nginx为主,但我在另一台机器上用Caddy也跑了一套Alist。这里给你做个坦诚的对比。如果你不想维护Nginx配置文件,我建议直接上Caddy。
Caddy的compose配置长这样:
version: "3.8" services: alist: image: xhofe/alist:latest container_name: alist restart: always volumes: - ./data:/opt/alist/data networks: - alist-net caddy: image: caddy:2-alpine container_name: alist-caddy restart: always ports: - "80:80" - "443:443" volumes: - ./Caddyfile:/etc/caddy/Caddyfile:ro - caddy_data:/data networks: - alist-net volumes: caddy_data: networks: alist-net: driver: bridge对应的Caddyfile:
alist.example.com { reverse_proxy alist:5244 encode gzip }Caddy会自动在/data卷里维护证书文件,自动续期,无需你介入。如果你用Cloudflare DNS,甚至可以在Caddyfile中添加tls指令选择DNS挑战方式,对那种不开放80端口的网络环境很友好。
从两者对比来看,我给个表格供你快速判断:
| 对比项 | Nginx方案 | Caddy方案 |
|---|---|---|
| 配置难度 | 中等,需要理解server块 | 极低,三行配置搞定 |
| 证书管理 | 需要额外接acme.sh | 内置集成完全自动化 |
| 扩展性 | 强,支持各种高级路由和限流 | 较弱,但日常反代够用 |
| 资源占用 | 更低 | 略高,但可忽略 |
| 调试便利 | 日志清晰,资料多 | 日志简洁,资料相对少 |
| 适用人群 | 有Nginx经验或追求高性能 | 新手优先 |
我用Nginx的原因很简单:环境里已经跑了很多其他服务,Nginx统一管理更顺手。如果你是从零开始、只服务Alist一个WEB应用,直接选Caddy,没必要折腾Nginx。
7. 实操心得:我的Alist SSL部署习惯
最后聊几个我的个人操作习惯,供你参考。这些是我使用了很长时间后沉淀下来的经验,不是教条,但确实能省很多麻烦。
第一,域名永远比IP可靠。即使你暂时没有域名,也可以用一个免费二级域名或者花生壳之类的动态域名,先配上SSL。用IP访问时证书是不好签发的(Let's Encrypt从2025年开始已经陆续支持IP证书了,但云厂商免费证书还不支持),流程会麻烦很多。一个域名一年就几十块钱,没必要省。
第二,不要把证书文件放在容器可写层内。这是血泪教训——有一天我想升级Alist镜像,顺手docker-compose up -d重建了容器,结果证书没了,网站瞬间变回HTTP。从那以后我把Alist和反代服务的所有持久化数据都放在宿主机目录或者独立volume里,容器重建一百次也不怕。
第三,开启HTTP/2。Nginx配置里的listen 443 ssl http2能显著提升HTTPS下的访问速度,尤其是Alist展示大量文件列表时,并发请求多,HTTP/2的多路复用优势明显。
第四,记得在Alist后台开启“站点地址”配置。在Alist管理后台的设置-基础设置里,把“站点地址”填成https://alist.example.com。这一步很多人会漏掉,导致后台生成的分享链接、下载链接都是HTTP开头的,用户点进去又走一遍不安全连接。
第五,定期看看证书状态。自动化再稳定,也要防患于未然。我习惯每个月做一次巡检:
acme.sh --list顺便看一眼证书到期输出,心里有数。如果你部署的机器不多,一个浏览器收藏夹把证书状态链接放进去也行。别等到用户跟你反馈“网站证书过期了”才来处理,那种局面对谁都不好看。
踩过几次坑之后,我现在每台运行Alist的机器都固定使用同一套部署模板:Docker Compose编排、反代终止SSL、acme.sh自动续期、域名访问、强制HTTPS。这套配置我迁移过好几台机器,从2核4G的小鸡到8核16G的独服都稳定运行。你按照这篇文章的步骤走一遍,大概率也能一次跑通。中间如果遇到别的问题,欢迎带着具体报错信息来交流,我可以把这篇内容继续往更细的方向扩展。