Seafile 13 使用 Nginx/Nginx Proxy Manager 做公网反代

分享一下Seafile 13 使用 Docker 部署,但不使用官方默认的 Caddy,而是使用外部 Nginx 或 Nginx Proxy Manager 统一做 HTTPS 反向代理。

适合这种网络架构:

公网用户
  -> HTTPS + 域名 + 端口
  -> 网关 / Nginx / Nginx Proxy Manager
  -> 内网 Seafile Docker 服务器

例如:

公网访问地址:
https://cloud.example.com:8443

Nginx Proxy Manager 所在机器:
192.168.1.10

Seafile Docker 所在机器:
192.168.1.20

实际使用时,请把本文中的:

cloud.example.com:8443
192.168.1.20

替换成你自己的域名、端口和内网 IP。

一、为什么可以不用 Caddy?

Seafile 13 Docker 默认会带 Caddy,用来自动处理 HTTPS 和反向代理。

但如果你的网络里已经有统一入口,比如:

Nginx
Nginx Proxy Manager
OpenResty
Traefik
HAProxy

那么就可以不用 Seafile 自带的 Caddy。

去掉 Caddy 后,Seafile 服务器上不需要额外安装宿主机 Nginx 或 Apache,因为 Seafile 容器自己已经提供 HTTP 服务。

你只需要把容器端口映射到宿主机,然后让外部 Nginx 代理过去。

最终关系类似这样:

NPM / Nginx
  -> http://192.168.1.20:80      -> Seafile 主服务
  -> http://192.168.1.20:8888    -> SeaDoc 在线文档服务

数据库、Redis 不需要暴露给 Nginx,它们继续在 Docker 内部网络里通信。

二、目标效果

最终用户只访问:

https://cloud.example.com:8443

并且这些功能都正常:

登录
上传
下载
文件预览
Markdown 在线编辑
SeaDoc 在线文档编辑

三、准备工作

假设 Seafile 的 compose 目录是:

/opt/seafile

目录里通常有:

.env
seafile-server.yml
caddy.yml
seadoc.yml

先备份:

cd /opt/seafile

cp .env .env.bak.$(date +%F-%H%M)
cp seafile-server.yml seafile-server.yml.bak.$(date +%F-%H%M)
cp seadoc.yml seadoc.yml.bak.$(date +%F-%H%M)

四、修改 .env

编辑:

nano /opt/seafile/.env

找到类似内容:

COMPOSE_FILE='seafile-server.yml,caddy.yml,seadoc.yml'
SEAFILE_SERVER_HOSTNAME=192.168.1.20
SEAFILE_SERVER_PROTOCOL=http
JWT_PRIVATE_KEY=

改成:

COMPOSE_FILE='seafile-server.yml,seadoc.yml'

SEAFILE_SERVER_HOSTNAME=cloud.example.com:8443
SEAFILE_SERVER_PROTOCOL=https

JWT_PRIVATE_KEY=这里填写随机字符串

SEADOC_SERVER_URL=https://cloud.example.com:8443/sdoc-server
SEAFILE_SERVICE_URL=https://cloud.example.com:8443

生成随机字符串:

openssl rand -hex 32

然后填到:

JWT_PRIVATE_KEY=生成出来的随机字符串

注意:

SEAFILE_SERVER_HOSTNAME 要写公网访问域名和端口
SEAFILE_SERVER_PROTOCOL 要写 https
JWT_PRIVATE_KEY 不要留空

如果公网访问不用特殊端口,比如直接是 https://cloud.example.com,那就写:

SEAFILE_SERVER_HOSTNAME=cloud.example.com
SEAFILE_SERVER_PROTOCOL=https
SEADOC_SERVER_URL=https://cloud.example.com/sdoc-server
SEAFILE_SERVICE_URL=https://cloud.example.com

五、修改 seafile-server.yml

编辑:

nano /opt/seafile/seafile-server.yml

找到 seafile 服务里的端口配置。

原来可能是注释状态:

    # ports:
    #   - "80:80"

改成:

    ports:
      - "80:80"

然后删除或注释掉 Caddy labels:

    labels:
      caddy: ${SEAFILE_SERVER_PROTOCOL:-http}://${SEAFILE_SERVER_HOSTNAME:?Variable is not set or empty}
      caddy.reverse_proxy: "{{upstreams 80}}"

删除后,Seafile 主服务会直接暴露到宿主机:

http://192.168.1.20:80

六、修改 seadoc.yml

编辑:

nano /opt/seafile/seadoc.yml

找到端口配置,原来可能是:

    # ports:
    #   - "80:80"

改成:

    ports:
      - "8888:80"

找到:

      - SEAHUB_SERVICE_URL=${SEAFILE_SERVICE_URL:-http://seafile}

改成:

      - SEAHUB_SERVICE_URL=${SEAFILE_SERVICE_URL:-https://cloud.example.com:8443}

然后删除或注释掉 seadoc 服务里的 Caddy labels:

    labels:
      caddy: ${SEAFILE_SERVER_PROTOCOL:-http}://${SEAFILE_SERVER_HOSTNAME:?Variable is not set or empty}
      caddy.1_handle_path: "/socket.io/*"
      caddy.1_handle_path.0_rewrite: "* /socket.io{uri}"
      caddy.1_handle_path.1_reverse_proxy: "{{upstreams 80}}"
      caddy.2_handle_path: "/sdoc-server/*"
      caddy.2_handle_path.0_rewrite: "* {uri}"
      caddy.2_handle_path.1_reverse_proxy: "{{upstreams 80}}"

修改后,SeaDoc 会暴露到宿主机:

http://192.168.1.20:8888

七、停止 Caddy 并启动服务

因为 .env 里已经去掉了 caddy.yml

COMPOSE_FILE='seafile-server.yml,seadoc.yml'

所以后续不会再启动 Caddy。

如果旧 Caddy 容器还在运行,停止并删除:

docker stop seafile-caddy
docker rm seafile-caddy

重新启动:

cd /opt/seafile
docker compose down
docker compose up -d

查看状态:

docker compose ps

期望看到:

seafile         0.0.0.0:80->80/tcp
seadoc          0.0.0.0:8888->80/tcp
seafile-mysql
seafile-redis

不应该再看到:

seafile-caddy

八、测试 Seafile 服务器端口

在 Nginx / Nginx Proxy Manager 那台机器上测试:

curl -I http://192.168.1.20:80
curl -I http://192.168.1.20:8888

只要有 HTTP 响应即可,不一定必须是 200

如果访问不到,在 Seafile 服务器上检查:

docker compose ps
ss -lntp | grep -E ':80|:8888'

九、配置 Nginx Proxy Manager

如果你使用 Nginx Proxy Manager,创建或编辑 Proxy Host。

基础配置:

Domain Names:
cloud.example.com

Scheme:
http

Forward Hostname / IP:
192.168.1.20

Forward Port:
80

Websockets Support:
开启

Block Common Exploits:
可以开启

SSL 配置:

SSL Certificate:
选择你的证书

Force SSL:
开启

HTTP/2 Support:
可开可不开,排错阶段可以先关

Advanced 填入:

location /sdoc-server/ {
    proxy_pass http://192.168.1.20:8888/;
    proxy_redirect off;

    proxy_http_version 1.1;
    proxy_set_header Host $http_host;
    proxy_set_header X-Forwarded-Host $http_host;
    proxy_set_header X-Forwarded-Proto https;
    proxy_set_header X-Forwarded-Port 8443;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

    client_max_body_size 100m;
    proxy_read_timeout 3600s;
    proxy_send_timeout 3600s;
}

location /socket.io {
    proxy_pass http://192.168.1.20:8888;
    proxy_http_version 1.1;

    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";

    proxy_set_header Host $http_host;
    proxy_set_header X-Forwarded-Host $http_host;
    proxy_set_header X-Forwarded-Proto https;
    proxy_set_header X-Forwarded-Port 8443;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

    proxy_read_timeout 3600s;
    proxy_send_timeout 3600s;
}

location / {
    proxy_pass http://192.168.1.20:80;

    proxy_http_version 1.1;
    proxy_set_header Connection "";

    proxy_set_header Host $http_host;
    proxy_set_header X-Forwarded-Host $http_host;
    proxy_set_header X-Forwarded-Proto https;
    proxy_set_header X-Forwarded-Port 8443;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

    client_max_body_size 0;
    proxy_request_buffering off;
    proxy_read_timeout 3600s;
    proxy_send_timeout 3600s;
}

如果你的公网访问没有特殊端口,是标准 443,可以把:

proxy_set_header X-Forwarded-Port 8443;

改成:

proxy_set_header X-Forwarded-Port 443;

或者删除这一行。

十、普通 Nginx 配置参考

如果你不用 Nginx Proxy Manager,而是手写 Nginx,可以参考:

server {
    listen 443 ssl;
    server_name cloud.example.com;

    ssl_certificate /path/to/fullchain.pem;
    ssl_certificate_key /path/to/privkey.pem;

    client_max_body_size 0;

    location /sdoc-server/ {
        proxy_pass http://192.168.1.20:8888/;
        proxy_redirect off;

        proxy_http_version 1.1;
        proxy_set_header Host $http_host;
        proxy_set_header X-Forwarded-Host $http_host;
        proxy_set_header X-Forwarded-Proto https;
        proxy_set_header X-Forwarded-Port 443;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

        proxy_read_timeout 3600s;
        proxy_send_timeout 3600s;
    }

    location /socket.io {
        proxy_pass http://192.168.1.20:8888;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";

        proxy_set_header Host $http_host;
        proxy_set_header X-Forwarded-Host $http_host;
        proxy_set_header X-Forwarded-Proto https;
        proxy_set_header X-Forwarded-Port 443;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

        proxy_read_timeout 3600s;
        proxy_send_timeout 3600s;
    }

    location / {
        proxy_pass http://192.168.1.20:80;
        proxy_http_version 1.1;
        proxy_set_header Connection "";

        proxy_set_header Host $http_host;
        proxy_set_header X-Forwarded-Host $http_host;
        proxy_set_header X-Forwarded-Proto https;
        proxy_set_header X-Forwarded-Port 443;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

        proxy_request_buffering off;
        proxy_read_timeout 3600s;
        proxy_send_timeout 3600s;
    }
}

十一、配置 seahub_settings.py

编辑:

nano /opt/seafile-data/seafile/conf/seahub_settings.py

加入或整理成类似下面这样:

# -*- coding: utf-8 -*-

TIME_ZONE = 'Asia/Shanghai'

ENABLE_SETTINGS_VIA_WEB = False

SERVICE_URL = 'https://cloud.example.com:8443'
FILE_SERVER_ROOT = 'https://cloud.example.com:8443/seafhttp'

USE_X_FORWARDED_HOST = True
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')

ALLOWED_HOSTS = [
    'cloud.example.com',
    '192.168.1.20',
    '127.0.0.1',
    'localhost',
]

CSRF_TRUSTED_ORIGINS = [
    'https://cloud.example.com:8443',
]

ENABLE_SEADOC = True
SEADOC_SERVER_URL = 'https://cloud.example.com:8443/sdoc-server'

如果你是标准 HTTPS 443 端口,就改成:

SERVICE_URL = 'https://cloud.example.com'
FILE_SERVER_ROOT = 'https://cloud.example.com/seafhttp'
CSRF_TRUSTED_ORIGINS = ['https://cloud.example.com']
SEADOC_SERVER_URL = 'https://cloud.example.com/sdoc-server'

注意:

SERVICE_URL 不要写内网 IP
FILE_SERVER_ROOT 不要写内网 IP
不要在结尾多写 /

推荐:

FILE_SERVER_ROOT = 'https://cloud.example.com:8443/seafhttp'

不推荐:

FILE_SERVER_ROOT = 'http://192.168.1.20/seafhttp/'

修改后重启:

cd /opt/seafile
docker compose restart seafile seadoc

十二、不要单独代理 /seafhttp 到 8082

很多人会看到:

[fileserver]
port=8082

于是尝试把:

/seafhttp

直接反代到:

192.168.1.20:8082

这通常是错误的。

Seafile 13 Docker 版中,/seafhttp 应该先进入 Seafile 容器内置的 OpenResty/Nginx,再由它处理认证和转发。

如果直接代理到 fileserver,常见错误是:

Both token and cookie are not set

正确做法是:

/seafhttp 也走主服务 192.168.1.20:80

也就是让它进入 NPM/Nginx 的:

location /

十三、测试

重新打开浏览器,建议使用隐私窗口或清除站点 Cookie。

访问:

https://cloud.example.com:8443

依次测试:

1. 登录
2. 新建资料库
3. 上传小文件
4. 下载小文件
5. 上传 Markdown 文件
6. 在线打开 Markdown
7. 编辑并保存 Markdown
8. 上传 docx 文件
9. 测试 SeaDoc 在线编辑

下载链接应该类似:

https://cloud.example.com:8443/seafhttp/repos/xxx/files/xxx?op=download

如果下载链接变成下面这样,就是配置错了:

http://192.168.1.20/...
http://cloud.example.com/...
https://cloud.example.com/...

最后一种如果你实际公网必须带 8443,那么少了 :8443 也是错的。

十四、常见问题排查

1. 下载 400 Bad Request

先看浏览器 F12 → Network。

如果失败的是:

/seafhttp/...

检查:

FILE_SERVER_ROOT 是否是公网完整地址
Nginx 是否把 Host 转成了公网 Host
是否错误地把 /seafhttp 单独代理到了 8082

正确:

FILE_SERVER_ROOT = 'https://cloud.example.com:8443/seafhttp'

错误:

FILE_SERVER_ROOT = 'http://192.168.1.20/seafhttp'

2. Markdown 打开一直转圈

Markdown 在线编辑需要读取文件内容,底层也会请求:

/seafhttp/...op=download

所以如果下载 400,Markdown 编辑通常也会一直转圈。

先修下载,Markdown 往往会一起恢复。

3. 日志里出现 Invalid HTTP_HOST header

如果 seahub.log 里看到:

Invalid HTTP_HOST header: '127.0.0.1:8000'

就在 seahub_settings.py 里加入:

ALLOWED_HOSTS = [
    'cloud.example.com',
    '192.168.1.20',
    '127.0.0.1',
    'localhost',
]

然后重启:

docker compose restart seafile

4. 在线文档编辑打不开

重点检查:

SeaDoc 容器是否运行
192.168.1.20:8888 是否能访问
/sdoc-server/ 是否代理到 8888
/socket.io 是否代理到 8888
WebSocket 是否开启
JWT_PRIVATE_KEY 是否非空

检查容器:

docker compose ps

检查端口:

curl -I http://192.168.1.20:8888

5. 端口 80 被占用

检查:

ss -lntp | grep ':80'

如果是旧的 Caddy 占用:

docker stop seafile-caddy
docker rm seafile-caddy

如果必须换端口,比如让 Seafile 暴露到 8080

ports:
  - "8080:80"

那 Nginx/NPM 也要改成代理:

http://192.168.1.20:8080

十五、最终检查清单

.env

COMPOSE_FILE='seafile-server.yml,seadoc.yml'
SEAFILE_SERVER_HOSTNAME=cloud.example.com:8443
SEAFILE_SERVER_PROTOCOL=https
SEADOC_SERVER_URL=https://cloud.example.com:8443/sdoc-server
SEAFILE_SERVICE_URL=https://cloud.example.com:8443
JWT_PRIVATE_KEY=非空随机字符串

seafile-server.yml

ports:
  - "80:80"

seadoc.yml

ports:
  - "8888:80"

Nginx/NPM:

/             -> 192.168.1.20:80
/sdoc-server/ -> 192.168.1.20:8888
/socket.io    -> 192.168.1.20:8888

seahub_settings.py

SERVICE_URL = 'https://cloud.example.com:8443'
FILE_SERVER_ROOT = 'https://cloud.example.com:8443/seafhttp'
CSRF_TRUSTED_ORIGINS = ['https://cloud.example.com:8443']
ALLOWED_HOSTS 包含公网域名、127.0.0.1、localhost

不要做:

不要用 sub_filter 硬替换页面内容
不要把 Host 固定成内网 IP
不要把 SERVICE_URL 写成内网 IP
不要把 FILE_SERVER_ROOT 写成内网 IP
不要单独把 /seafhttp 代理到 8082

十六、回滚

如果改乱了,恢复备份:

cd /opt/seafile

cp .env.bak.xxxx .env
cp seafile-server.yml.bak.xxxx seafile-server.yml
cp seadoc.yml.bak.xxxx seadoc.yml

docker compose down
docker compose up -d

如果要恢复官方 Caddy:

COMPOSE_FILE='seafile-server.yml,caddy.yml,seadoc.yml'

然后恢复 Caddy labels,再启动:

docker compose up -d

按照以上配置后,seafile能够正常使用,seadoc打开后始终转圈,然后报【加载文档内容错误】,看了下日志,没有找到解决方案:

xxx.x.xx.xx - - [05/Jun/2026:17:22:18 +0800] “GET /sdoc-server/api/v1/docs/536cc817-c42c-4c0d-97f3-146421ce472d/ HTTP/2.0” 500 80 “https://sf.xxxx.cc:82/lib/3118d698-1c66-4912-afa6-0f6eac799b4e/file/ee.sdoc” “Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/26.5 Safari/605.1.15” "-"45.205.1.241 - - [05/Jun/2026:17:24:29 +0800] “GET / HTTP/1.1” 400 150 “-” “curl/7.68.0” “-”

500和400都没有找到解决方案