终于搞定了宿主机nginx代理seafile 弃用caddy。同时开启seadoc onlyoffice。解决头像问题。简单安全防护。

首先感谢这个帖子,从nginx配置文件找到了调用seadoc的配置语句。

Seafile 13专业版使用nginx配置https实现公网域名访问并启用onlyoffice - 经验分享 - Seafile 用户论坛

一、前言

云盘服务如果暴露在公网首先会被恶意IP扫描,其次家宽会被警告需要备案。
之前使用seafile代理的时候,可以限制useragent,请求方法、域名等等。
曾经尝试研究caddy的配置,将caddy容器config路径映射到宿主机,但是无论如何修改Caddyfile.autosave。重启后,总是会恢复到默认配置。
然后是在部署中遇到了各种小问题,将解决方法与大家分享一下。希望对同样需求的人提供一点点帮助。
建议一步一步操作,每次只改动1-2个配置,这样出现问题排查问题会更准确。

二、基本部署

1:按照官方手册,部署默认配置。

https://cloud.seafile.com/wiki/publish/seafile-manual/hk5G/

建议只修改最基本的配置,比如下面几个变量值。

SEAFILE_SERVER_HOSTNAME=地址/域名
JWT_PRIVATE_KEY
SEAFILE_MYSQL_DB_PASSWORD
INIT_SEAFILE_MYSQL_ROOT_PASSWORD
INIT_SEAFILE_ADMIN_EMAIL=
INIT_SEAFILE_ADMIN_PASSWORD=

部署成功后,使用SEAFILE_SERVER_HOSTNAME中定义的域名或IP访问确保基本组件工作正常。(上传下载、SDOC编辑、知识库编辑。)

2:变更端口。

一般家用宽带80是不会开放的,我个人会选择50000之后的端口。

.env中的配置

SEAFILE_SERVER_HOSTNAME=地址/域名:50000

改了端口之后,就要修改caddy.yml中的端口映射配置。

    container_name: seafile-caddy
    ports:
      - 50000:50000

重新创建容器,确保地址/域名:50000能正常使用。(上传下载、SDOC编辑、知识库编辑。)

3:增加ONLY OFFICE扩展

同样还是参考官方手册
https://cloud.seafile.com/wiki/publish/seafile-manual/7Y75/

有两个地方要注意:

官方手册下载的yml文件第一行有三个…建议删除。
如果加了端口,会造成ONLYOFFICE调用失败,需要修改caddy.yml中的端口配置。

端口问题

onlyoffice.yml中配置引用了SEAFILE_SERVER_HOSTNAME变量导致配置文件最终为:地址/域名:50000:6233。造成ONLYOFFICE调用失败。

onlyoffice.yml配置:

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

最直接的方法就是不用变量直接修改onlyoffice.yml配置,比如:

caddy: http://地址、域名:6233

或者在.env中,增加一个ONLYOFFICE_HOSTNAME的配置,将不带端口的域名写进去。

onlyoffice.yml配置:

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

至此,基于官方文档的部署基本完成,可以通过地址/域名:50000端口进行本地访问,文档编辑。
路由器映射端口后也可以通过互联网进行访问。

二、修改宿主机nginx配置文件

sdoc的调用依靠caddy代理,如果直接把seafile容器内置nginx暴露在宿主机的话,会导致sdoc功能失效。

Seafile 13专业版使用nginx配置https实现公网域名访问并启用onlyoffice - 经验分享 - Seafile 用户论坛
在这个帖子的基础上,我根据个人实际情况做了一些修改。

照例还是贴一下完整的nginx配置文件,然后重要的地方做一些备注说明。

server {
        listen 50000;

    location / {
        proxy_pass http://127.0.0.1:8000/;
        proxy_read_timeout 1200s;
        proxy_set_header Host $http_host;
        proxy_set_header Forwarded "for=$remote_addr;proto=$scheme";
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header Connection "";
        proxy_http_version 1.1;
        client_max_body_size 0;
        access_log      /var/log/nginx/seahub.access.log;
        error_log       /var/log/nginx/seahub.error.log;
    }

    location /seafhttp {
        rewrite ^/seafhttp(.*)$ $1 break;
        proxy_pass http://127.0.0.1:8082;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        client_max_body_size 0;
        proxy_connect_timeout  36000s;
        proxy_read_timeout  36000s;
        proxy_request_buffering off;
        access_log      /var/log/nginx/seafhttp.access.log;
        error_log       /var/log/nginx/seafhttp.error.log;
    }


    location /seafdav {
        proxy_pass         http://127.0.0.1:8080/seafdav;
        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-Host $server_name;
        proxy_set_header   X-Forwarded-Proto $scheme;
        proxy_read_timeout  1200s;
        client_max_body_size 0;
        access_log      /var/log/nginx/seafdav.access.log;
        error_log       /var/log/nginx/seafdav.error.log;
    }

    location /media {
        root /opt/seafile-data/seafile;
    }

    location /sdoc-server/ {
        add_header Access-Control-Allow-Origin *;
        add_header Access-Control-Allow-Methods GET,POST,PUT,DELETE,OPTIONS;
        add_header Access-Control-Allow-Headers "deviceType,token, authorization, content-type";
        if ($request_method = 'OPTIONS') {
            add_header Access-Control-Allow-Origin *;
            add_header Access-Control-Allow-Methods GET,POST,PUT,DELETE,OPTIONS;
            add_header Access-Control-Allow-Headers "deviceType,token, authorization, content-type";
            return 204;
        }
        proxy_pass         http://127.0.0.1:8089/;
        proxy_redirect     off;
        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-Host  $server_name;
        proxy_set_header   X-Forwarded-Proto $scheme;
        client_max_body_size 100m;
        access_log      /var/log/nginx/seadoc.access.log;
        error_log       /var/log/nginx/seadoc.error.log;
    }

    location /socket.io {
        proxy_pass http://127.0.0.1:8089;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection 'upgrade';
        proxy_redirect off;
        proxy_buffers 8 32k;
        proxy_buffer_size 64k;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header Host $http_host;
        proxy_set_header X-NginX-Proxy true;
    }

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

}


1: 前三个location

前三个 / /seafhttp /seafdav 主要都是seafile容器的功能,我也太理解具体各项配置的含义,但复制下来能正常使用就行。 :laughing:

宿主机nginx代理后,会将请求转发到宿主机端口,因此需要修改seafile-server.yml的配置,将8000 8080 8082 几个端口映射到宿主机。
需要注意yml有比较严格的缩进校验,容易导致yml文件无法正确识别。

seafile-server.yml配置:

  seafile:
    image: ${SEAFILE_IMAGE:-seafileltd/seafile-mc:13.0-latest}
    container_name: seafile
    restart: unless-stopped
    ports:
      - 8000:8000
      - 8080:8080
      - 8082:8082

2、MEDIA路径

media 要重点说一下,会影响页面样式、头像显示。

首先seafile容器会把/media定位到容器内部的/opt/seafile/seafile-server-latest/seahub路径下。

seafile容器内置nginx配置文件:

    location /media {
        root /opt/seafile/seafile-server-latest/seahub;
    }

然后seafile容器的media路径中,有两个存放头像的路径映射到了宿主机的本地存储。

lrwxrwxrwx 1 root root   28 Jan 29 10:25 avatars -> ../../../seahub-data/avatars/
drwxr-xr-x 2 root root 4096 Jun 27  2025 bootstrap/
drwxr-xr-x 3 root root 4096 Aug 28 05:12 comment-editor/
drwxr-xr-x 4 root root 4096 Dec 23 09:43 css/
lrwxrwxrwx 1 root root   34 Jan 29 10:25 custom -> /shared/seafile/seahub-data/custom/
drwxr-xr-x 2 root root 4096 Sep 19 07:30 favicons/
drwxr-xr-x 7 root root 4096 Dec 23 09:43 img/
drwxr-xr-x 5 root root 4096 Jun 27  2025 js/
drwxr-xr-x 3 root root 4096 Jun 27  2025 office-template/
drwxr-xr-x 4 root root 4096 Dec 23 09:43 sdoc-editor/
drwxr-xr-x 4 root root 4096 Jul 30  2025 seafile-editor/

管理页面设置用户头像,seafile会将其保存到容器内部的路径(但实际还是在宿主机的/opt/seafile-data/seafile/seahub-data下面)

如果通过宿主机nginx代理,宿主机无法直接使用对应路径。造成头像无法正常显示。

首先要进入seafile容器内部。

docker exec -it seafile /bin/bash

将media目录全部复制到宿主机(命令在容器内部执行。)
如果只处理头像路径,导致登陆页面CSS样式无法加载,懒得一一验证具体是那个路径了,建议完整拷贝出来

cp -r /opt/seafile/seafile-server-latest/seahub/midia /shared/seafile

退出容器。刚刚复制的目录应该在宿主机/opt/seafile-data/seafile路径下。
进入media路径后,ll命令查看内容,可以发现 avatars 和 custom两个软连接的目标是错误的。将其修正到宿主机的正确位置。

ln -s /opt/seafile-data/seafile/seahub-data/avatars ./avatars 
ln -s /opt/seafile-data/seafile/seahub-data/custom ./custom

最后在宿主机nginx配置文件中,定位/media路径,就可以正常显示头像了。

3、sdoc-server 和 socketio

这两个应该是调用的seadoc容器服务。
我将其转发到了8089端口,修改seadoc.yml文件。

    volumes:
      - ${SEADOC_VOLUME:-/opt/seadoc-data/}:/shared
    ports:
      - 8089:80

4、修改seafile容器监听端口。

seafile内置的8000 8080 8082 默认监听在127.0.0.1上。需要修改成0.0.0.0否则宿主机无法正常访问。

修改/opt/seafile-data/seafile/conf/gunicorn.conf.py

将bind修改为0.0.0.0:8000

# default localhost:8000
bind = "0.0.0.0:8000"


至此,基于宿主机nginx代理操作基本完成,可以通过地址/域名:50000端口进行本地访问,文档编辑。
路由器映射端口后也可以通过互联网进行访问。

三、nginx加固。

为避免家宽被扫描到开启WEB服务,或减少互联网恶意攻击。需要对nginx做一些安全加固配置。

    if ($http_user_agent !~ "mozilla 5.0|seafile***")
    {return 444;}
#限制user agent白名单,如果不是清单里的agent,直接返回444。上面两个Agent是随便写的,通过查看nginx日志可以查看到具体的agent信息,可以使用正则表达式,大小写敏感。
    if ($host !~ "example.ddns.com" )
    {return 444;}
#请求过来的域名如果不是你的域名,也是返回444。一般扫描都只会扫IP,这样能减少一些IP端口探测。建议从国外DDNS服务商注册一个比较偏僻的域名。
    if ($request_method !~ ^(GET|HEAD|POST|PUT|DELETE|PROPFIND|MKCOL|OPTIONS|TRACE|PROPPATCH|LOCK|UNLOCK|COPY|MOVE|PATCH)$ )
    {return 444;}
#请求方法白名单,我这里还没有详细的测试验证,可能会影响sdoc onlyoffice在线编辑。具体查看nginx日志然后加进去就可以了。
    error_page 400 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 500 501 502 503 504 505 /0.html;
    location = /0.html {return 444;}
#最后是一些错误页面,通通返回444。

用curl验证一下:

 curl -v http://IP隐藏:port隐藏
*   Trying IP隐藏:port隐藏...
* Connected to 隐藏 port 隐藏
> GET / HTTP/1.1
> Host: IP隐藏:port隐藏
> User-Agent: curl/8.5.0
> Accept: */*
> 
* Empty reply from server
* Closing connection
curl: (52) Empty reply from server

网上有很多丰富的加固教程,我就不继续献丑了。

其实只用seafile自带的nginx就可以,完全不需要caddy,sefile映射1个端口出来就行了,容器之间是内部网络互通,给你参考一下我的配置:

容器:

NGINX配置(seafile容器内的路径/etc/nginx/sites-enabled):

# -*- mode: nginx -*-
# Auto generated at 12/20/2024 17:16:33
server {
listen 80;
server_name pan.域名.com;

	real_ip_recursive on;
	set_real_ip_from 192.168.4.0/24;    # 信任一级Nginx的IP
    set_real_ip_from 172.0.0.0/8;    # 信任Docker内部网络范围
	real_ip_header X-Forwarded-For;  # 从X-Forwarded-For头部提取真实IP
    
    client_max_body_size 100G;
	
    location / {
        proxy_pass http://127.0.0.1:8000/;
        proxy_read_timeout 310s;
        proxy_set_header Host $host;
        proxy_set_header Forwarded "for=$remote_addr;proto=$scheme";
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header Connection "";
        proxy_http_version 1.1;

        client_max_body_size 0;
        access_log      /var/log/nginx/seahub.access.log seafileformat;
        error_log       /var/log/nginx/seahub.error.log;
    }

    location /seafhttp {
        rewrite ^/seafhttp(.*)$ $1 break;
        proxy_pass http://127.0.0.1:8082;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        client_max_body_size 0;
        proxy_connect_timeout  36000s;
        proxy_read_timeout  36000s;
        proxy_request_buffering off;

        access_log      /var/log/nginx/seafhttp.access.log seafileformat;
        error_log       /var/log/nginx/seafhttp.error.log;
    }

    location /notification/ping {
        proxy_pass http://notification-server/ping;
        access_log      /var/log/nginx/notification.access.log seafileformat;
        error_log       /var/log/nginx/notification.error.log;
    }

    location /notification {
        proxy_pass http://127.0.0.1:8083/;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        access_log      /var/log/nginx/notification.access.log seafileformat;
        error_log       /var/log/nginx/notification.error.log;
    }

    location /webdav {
        rewrite ^/webdav$ /webdav/ permanent;
    }
    location /webdav/ {
        proxy_pass         http://127.0.0.1:8080/webdav/;
        proxy_set_header   Host $host;
		#proxy_set_header   X-Forwarded-Proto https;
        proxy_set_header   X-Real-IP $remote_addr;
        proxy_set_header   X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header   X-Forwarded-Host $server_name;
        proxy_read_timeout  1200s;
        client_max_body_size 0;

        access_log      /var/log/nginx/seafdav.access.log seafileformat;
        error_log       /var/log/nginx/seafdav.error.log;
    }
    location /:dir_browser {
        # Logo of WebDAV
        proxy_pass         http://127.0.0.1:8080/:dir_browser;
		access_log      /var/log/nginx/seafdav.dir_browser.log seafileformat;
        error_log       /var/log/nginx/seafdav.dir_browser.log;
    }

    location /media {
        root /opt/seafile/seafile-server-latest/seahub;
		access_log      /var/log/nginx/seafdav.media.log seafileformat;
        error_log       /var/log/nginx/seafdav.media.log;
    }


	location /sdoc-server/ {
        proxy_pass         http://seadoc:7070/;
		proxy_redirect     off;
		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-Host  $server_name;

		client_max_body_size 100m;

		access_log      /var/log/nginx/sdoc-server.access.log;
        error_log       /var/log/nginx/sdoc-server.error.log;
    }
    location /socket.io {
        proxy_pass http://seadoc:7070;
		proxy_http_version 1.1;
		proxy_set_header Upgrade $http_upgrade;
		proxy_set_header Connection 'upgrade';
		proxy_redirect off;
		proxy_buffers 8 32k;
		proxy_buffer_size 64k;
		proxy_set_header X-Real-IP $remote_addr;
		proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
		proxy_set_header Host $http_host;
		proxy_set_header X-NginX-Proxy true;

    }
	location /seadoc-converter {
        add_header Access-Control-Allow-Origin *;
        add_header Access-Control-Allow-Methods GET,POST,PUT,DELETE,OPTIONS;
        add_header Access-Control-Allow-Headers "deviceType,token, authorization, content-type";
        if ($request_method = 'OPTIONS') {
            add_header Access-Control-Allow-Origin *;
            add_header Access-Control-Allow-Methods GET,POST,PUT,DELETE,OPTIONS;
            add_header Access-Control-Allow-Headers "deviceType,token, authorization, content-type";
            return 204;
        }

        proxy_pass         http://seadoc:7070/seadoc-converter;
        proxy_redirect     off;
        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-Host  $server_name;
        proxy_set_header   X-Forwarded-Proto $scheme;

        client_max_body_size 100m;

        access_log      /var/log/nginx/seadoc-converter.access.log;
        error_log       /var/log/nginx/seadoc-converter.error.log;
    }
	
	#thumbnail
	#location /thumbnail {
	##	proxy_pass http://thumbnail-server/;
	#	proxy_http_version 1.1;
	#	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;


	#	access_log /var/log/nginx/thumbnail.access.log;
	#	error_log  /var/log/nginx/thumbnail.error.log;
	#}

    # 处理 /thumbnail/* 路径的请求
    location ~ ^/thumbnail/(?!ping).* {
        # 反向代理到上游服务器
        proxy_pass http://thumbnail-server;
        
        # 常用的代理设置
        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;
    }
    
    # 处理 /thumbnail/ping 路径的请求
    location = /thumbnail/ping {
        # 重写URL到 /ping
        rewrite ^/thumbnail/ping$ /ping break;
        
        # 反向代理到上游服务器
        proxy_pass http://thumbnail-server;
        
        # 常用的代理设置
        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;
    }
	
	# collabora static files
    location ^~ /browser {
        proxy_pass http://collabora:9980;
        proxy_set_header Host $http_host;
		access_log      /var/log/nginx/collabora-browser.access.log;
        error_log       /var/log/nginx/collabora-browser.error.log;
    }


    # collabora WOPI discovery URL
    location ^~ /hosting/discovery {
        proxy_pass http://collabora:9980;
        proxy_set_header Host $http_host;
		access_log      /var/log/nginx/collabora-discovery.access.log;
        error_log       /var/log/nginx/collabora-discovery.error.log;
    }

    # collabora Capabilities
    location ^~ /hosting/capabilities {
        proxy_pass http://collabora:9980;
        proxy_set_header Host $http_host;
		access_log      /var/log/nginx/collabora-capabilities.access.log;
        error_log       /var/log/nginx/collabora-capabilities.error.log;
    }


    # collabora main websocket
    location ~ ^/cool/(.*)/ws$ {
        proxy_pass http://collabora:9980;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "Upgrade";
        proxy_set_header Host $http_host;
        proxy_read_timeout 36000s;
		access_log      /var/log/nginx/collabora-ws.access.log;
        error_log       /var/log/nginx/collabora-ws.error.log;
    }


    # collabora download, presentation and image upload
    location ~ ^/(c|l)ool {
    	proxy_pass http://collabora:9980;
        proxy_set_header Host $http_host;
		access_log      /var/log/nginx/collabora-cool.access.log;
        error_log       /var/log/nginx/collabora-cool.error.log;
    }


    # collabora Admin Console websocket
    location ^~ /cool/adminws {
        proxy_pass http://collabora:9980;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "Upgrade";
        proxy_set_header Host $http_host;
        proxy_read_timeout 36000s;
		access_log      /var/log/nginx/collabora-adminws.access.log;
        error_log       /var/log/nginx/collabora-adminws.error.log;
    }
	
}

我用的是单独部署的nginx webui,因为有ui界面,部署方便些。虽然多套了一层,但是几乎没有影响

感谢,我之前也尝试过seafile内置的nginx。修改内置conf后,sdoc提示加载文档失败。我按你的配置文件再研究一下。