自己攒设备当网盘,最怕两件事:一是数据放在别人家服务器上心里不踏实,二是折腾半天最后只能在局域网里自娱自乐。我在 Linux 上用 Docker 跑 Nextcloud 这套组合已经换过三台机器,从家里的小主机到租来的云服务器,中间踩的坑基本集中在两处——容器化之后目录和权限怎么摆,以及怎么让它在公网上被安全地访问到。这篇就把我这几轮折腾下来的完整路径摊开讲:从系统准备、compose 配置、数据库和缓存的取舍,到域名解析、证书签发、请求转发,再到最后那些只有真跑起来才会遇到的排查细节。适合手里有一台闲置机器或者一台基础配置云服务器、想把文件同步和相册备份抓回自己手里的朋友,也适合已经跑起来但卡在远程访问这一步、被"访问不被信任的域名"或者大文件上传超时折磨过的人。全程照着抄能跑通,但每一步背后的原因我也会说清楚,省得你换个环境就抓瞎。
1. 先把需求和技术选型捋清楚
1.1 自建网盘到底要解决什么问题
动手之前得先明确自己图什么,不然很容易变成为了折腾而折腾。我当初的诉求很具体:手机相册要能自动备份到自己的盘上、几台设备之间要能双向同步工作文件、偶尔要给外部的人发一个带密码和有效期的分享链接、顺手把日历和通讯录也统一管起来。再往后还希望能在线预览 Office 文档和 PDF、能挂 WebDAV 给一些老工具用。这几条列出来,候选方案其实就很少了——Nextcloud 在这几个维度上是覆盖得最全的开源实现,桌面端、移动端、Web 端都有官方客户端,WebDAV 和 CalDAV/CardDAV 都是原生支持,插件生态也够厚。
真正需要自己想清楚的是另一件事:这台机器放在哪、数据存在哪、谁有权限碰它。放在家里意味着你要处理公网地址的问题;放在云服务器上意味着你要为磁盘和流量付费,但省掉了网络那一堆麻烦。我个人是两条腿走路——主力数据在家里的机器上,云端只放一些需要随时调取的资料。这个决定会直接影响后面的方案选型,所以建议你在动手之前就把这件事定下来。
1.2 为什么用容器而不是直接装源码包
源码方式部署 Nextcloud 的老玩家应该都有印象,那是一条相当长的依赖链:PHP 及十几个扩展(gd、zip、intl、bcmath、imagick、apcu、redis 等)、Web 服务器、数据库、缓存,装完之后还要处理文件属主、目录权限、.htaccess生效、OPcache 参数。升级更麻烦,要进维护模式、替换目录、跑occ upgrade、再对一堆目录重新 chown。这套流程熟悉了不算难,但每次大版本升级都得提心吊胆。
容器化把依赖全部固化进镜像,升级的本质变成了"改一个版本号然后重启",出问题就把版本号改回去重新起来,回滚成本极低。这是我选 Docker 最核心的理由——不是说容器性能更好,而是它把"环境一致性"这件事解决了。
但容器化也带来了三个必须提前知道的代价。第一是文件属主:Nextcloud 镜像里的 Apache 以www-data(uid 33、gid 33)运行,而宿主机上你登录的用户往往是 uid 1000,挂载进去的目录如果属主不对,表现为安装向导走完直接白屏或者报 500,日志里全是权限拒绝。第二是配置里的地址不能写死:容器每次重建 IP 都可能变,所以配置里只能用服务名(compose 里的服务名会被自动解析)而不能写容器 IP。第三是数据目录的边界:/var/www/html里既有程序文件也有用户数据,全塞在一个挂载点里,将来备份、迁移、升级都会很别扭,得提前拆开。
提示:容器化不等于"不用管系统",宿主机的时间同步、磁盘空间、内核参数该配还是要配,出问题时排查顺序永远是先宿主机再容器。
1.3 部署前的硬件与系统环境清单
我给自己定的最低线是 2 核 CPU、2GB 内存、20GB 系统盘,数据盘另算。2GB 内存跑 MariaDB + PHP + Redis 是偏紧的,能上 4GB 会舒服很多;如果只有 2GB,务必加一个 2GB 的 swap 文件兜底,否则在同步大量小文件的时候极容易被 OOM 杀掉进程。文件系统用 ext4 或 xfs 都行,别用网络上挂载的目录做数据盘——尤其是把数据库目录放在共享存储上,锁机制会出各种诡异问题。
系统我一般用 Debian 12 或者同系的版本,软件包版本新、Docker 官方源支持好。如果你的系统默认开了 SELinux,挂载目录时要加:z或:Z后缀,不然容器里的进程会被拒绝访问;用 AppArmor 的系统则要留意默认策略有没有拦网络绑定。这一步很多人会忽略,结果就是容器起来了但页面永远转圈,日志里一行 SELinux denied 藏得很深。
还有一个容易被跳过的动作:先确认磁盘空间和 inode。df -h看容量,df -i看 inode 数量。Nextcloud 会给每个文件写元数据,同步几十万个小文件的时候 inode 消耗得比想象中快,之前有个朋友的盘 40GB 没用完就写不进去了,一查 inode 满了。
2. 部署前的关键决策:目录、数据库、缓存怎么选
2.1 目录规划:把"家底"和"程序"分开
我现在的目录结构是这样的,你可以直接照搬:
/opt/nextcloud/ ├── app/ # 程序文件(挂到 /var/www/html) ├── data/ # 用户数据(挂到 /var/www/html/data) ├── db/ # 数据库数据目录 ├── redis/ # 缓存持久化 ├── php/ # 自定义 php.ini ├── nginx/ # 转发层配置与站点 └── certs/ # 证书为什么要把app和data分开挂载?因为data目录里装的是用户上传的所有文件,体积会一直涨,备份策略和app完全不同。app只需要在升级前做一次快照,data则是天天要增量备份的对象。两者混在一个挂载点里,将来想把数据迁到一块更大的盘上就得全量搬一遍,几十上百 GB 搬起来很痛苦。拆开之后,加盘、扩容、单独挂载都变得很轻松。
有个细节:首次启动前必须手动创建这些目录并改属主,data目录要chown -R 33:33。如果你忘了这一步,安装向导会在写配置文件那一步失败,报错信息还很不直观。我习惯在写 compose 之前就把这行命令执行掉,宁可多敲一次。
mkdir -p /opt/nextcloud/{app,data,db,redis,php,nginx,certs} chown -R 33:33 /opt/nextcloud/app /opt/nextcloud/data chmod 750 /opt/nextcloud/datadata目录给 750 是有意为之——里面是明文文件,同机器上的其他普通用户不该能直接读。
2.2 数据库选型:SQLite、MariaDB、PostgreSQL 的取舍
Nextcloud 默认允许 SQLite,装起来确实快,单人用、文件量几千个以内完全没问题。但它的问题是写并发:SQLite 用的是库级锁,多个客户端同时同步、同时又有人在网页上操作的时候,很容易出现 "database is locked",表现为客户端一直重试、同步卡在某个百分比。多人共用的场景我强烈建议直接上 MariaDB。
PostgreSQL 也能用,功能上没短板,但社区里现成的排错经验相对少一些,遇到问题搜到的答案经常是 MySQL 系的。我选 MariaDB 纯粹是因为踩坑时更容易找到同路人。
参数上,2GB 内存的机器我会在 compose 里给 MariaDB 加一段启动参数,主要是把 InnoDB 的缓冲池控制在合理范围,别让它把内存吃干:
command: - --transaction-isolation=READ-COMMITTED - --binlog-format=ROW - --innodb-buffer-pool-size=256M - --innodb-flush-log-at-trx-commit=2READ-COMMITTED和ROW是 Nextcloud 官方文档里明确建议的组合,主要是为了避免同步过程中出现意料之外的间隙锁。innodb-flush-log-at-trx-commit=2是拿一点点断电风险换写入性能,家用场景可以接受,如果你放的是重要资料,改回默认的 1 更保险。
2.3 缓存与性能组件:Redis 该不该上
Redis 在 Nextcloud 里承担两件事:一是文件锁(file locking),二是分布式缓存。文件锁这件事在多人或多端同时写同一个文件时非常关键,没有它就容易出现两个客户端互相覆盖、文件版本混乱。APCu 单机也能顶一部分,但它只在 PHP 进程内共享,多进程下效果有限;Redis 是独立进程,所有 worker 共享同一份锁表,行为更可预期。
我的建议是:只要你打算长期用、且不止一台设备在同步,就把 Redis 加上。它占的内存很小(我这边常驻 30MB 左右),收益却很明显——网页端的页面加载、缩略图生成、文件列表刷新都会快一截。
配的时候记得给它设密码,别裸奔。虽然它只监听容器内部网络,但多一层防护不亏:
command: redis-server --requirepass 换成你的口令 --maxmemory 128mb --maxmemory-policy allkeys-lruallkeys-lru这个策略是有意的:缓存嘛,内存满了就淘汰最久没用的,不需要持久保证。但注意别把 Redis 当成唯一存储来放重要数据,它只是缓存。
2.4 一张表看清各组件的取舍
| 组件 | 选轻量方案 | 选完整方案 | 我的建议 |
|---|---|---|---|
| 数据库 | SQLite | MariaDB | 超过 1000 个文件或 2 台以上设备就上 MariaDB |
| 缓存 | APCu | Redis | 长期使用直接上 Redis,省得后面迁移 |
| Web 服务 | 容器内置 Apache | 独立 Nginx 转发 | 要上 HTTPS 和域名就用独立 Nginx |
| 存储 | 单盘 | 独立数据盘 | 数据量超过 50GB 就单独挂盘 |
| 访问方式 | 局域网 IP | 域名 + HTTPS | 只要有外部访问需求就必须上域名证书 |
3. 动手部署:一份可直接抄的 compose 配置
3.1 安装 Docker 与 compose 插件
系统自带的软件源里 Docker 版本往往偏旧,我一般走官方仓库。这里不写具体源地址,你搜"docker 官方安装文档"按发行版照做即可,步骤就是加源、更新索引、装docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin这几个包。
装完验证一下版本,docker compose version能输出版本号说明插件已经就位(注意是docker compose中间带空格,不是老版本的docker-compose)。接着把当前用户加进 docker 组,usermod -aG docker $USER,重新登录一次生效。
如果拉镜像速度慢,配置一个镜像加速地址会有明显改善,这部分按你所在环境的实际情况处理就行。
注意:给普通用户加 docker 组等价于给了它 root 级别的权限,这是 Docker 的固有设计。机器只有你自己用无所谓,多人共用的服务器上要谨慎。
3.2 编写 docker-compose.yml
在/opt/nextcloud下建一个docker-compose.yml,内容如下。我把所有需要你改的地方都用中文标注了,替换掉再执行。
services: db: image: mariadb:10.11 container_name: nc-db restart: always command: - --transaction-isolation=READ-COMMITTED - --binlog-format=ROW - --innodb-buffer-pool-size=256M volumes: - ./db:/var/lib/mysql environment: - MYSQL_ROOT_PASSWORD=换成根口令 - MYSQL_PASSWORD=换成应用口令 - MYSQL_DATABASE=nextcloud - MYSQL_USER=nextcloud networks: - backend redis: image: redis:7-alpine container_name: nc-redis restart: always command: redis-server --requirepass 换成缓存口令 --maxmemory 128mb --maxmemory-policy allkeys-lru volumes: - ./redis:/data networks: - backend app: image: nextcloud:28-apache container_name: nc-app restart: always ports: - "127.0.0.1:8080:80" volumes: - ./app:/var/www/html - ./data:/var/www/html/data - ./php/custom.ini:/usr/local/etc/php/conf.d/zz-custom.ini:ro environment: - MYSQL_HOST=db - MYSQL_DATABASE=nextcloud - MYSQL_USER=nextcloud - MYSQL_PASSWORD=换成应用口令 - REDIS_HOST=redis - REDIS_HOST_PASSWORD=换成缓存口令 - NEXTCLOUD_TRUSTED_DOMAINS=cloud.example.com 192.168.1.10 - OVERWRITEPROTOCOL=https - OVERWRITECLIURL=https://cloud.example.com - PHP_MEMORY_LIMIT=512M depends_on: - db - redis networks: - backend networks: backend: driver: bridge几个关键点说明一下。ports我写的是127.0.0.1:8080:80,只绑本机回环,外部访问一律走后面的转发层。这样即使防火墙配错,Nextcloud 也不会直接暴露在公网上,等于多了一道保险。如果你暂时只想在局域网里用,把它改成8080:80就能用http://内网IP:8080打开了。
NEXTCLOUD_TRUSTED_DOMAINS里预置了域名和内网地址,中间用空格分隔。这个参数的作用是白名单——不在名单里的 Host 头会被拒绝,提示"访问不被信任的域名"。后面加域名、换 IP 都要回来改这里,或者用occ命令动态加。
php/custom.ini这个文件要提前建好:
memory_limit = 512M upload_max_filesize = 16G post_max_size = 16G max_execution_time = 3600 max_input_time = 3600 output_buffering = 0 opcache.enable = 1 opcache.memory_consumption = 128 opcache.interned_strings_buffer = 16 opcache.max_accelerated_files = 10000这几个数字不是拍脑袋来的。upload_max_filesize和post_max_size必须一致或者后者略大,否则大文件会被 PHP 直接拒掉,而且报错信息很含糊。max_execution_time给到 3600 是为了让大文件上传不被中途掐断——一个 10GB 的文件在慢速网络下传一两个小时很正常。output_buffering = 0是 Nextcloud 官方推荐的设置,避免输出缓冲影响进度提示。
3.3 首次启动与初始化向导
目录和配置都就位之后,一条命令起来:
cd /opt/nextcloud docker compose up -d docker compose ps docker compose logs -f applogs里看到 "Apache/2.4.x ... configured -- resuming normal operations" 就说明 Web 服务起来了。第一次启动会比较慢,因为镜像要初始化app目录,把所有程序文件铺进去,一分钟左右很正常。
浏览器打开http://服务器地址:8080,会进安装向导。这里有三个地方要注意:
第一,管理员账号名别用admin。这是被自动化工具扫得最多的用户名,换成你自己的。密码用长一点的,反正只在登录时输一次。
第二,数据目录保持默认。因为我们已经把/var/www/html/data单独挂载出来了,这里写的路径实际上就落在宿主机的/opt/nextcloud/data,不用改。
第三,数据库类型选 MySQL/MariaDB,主机填db(就是 compose 里的服务名),库名、用户名、密码跟环境变量里的一致。如果你在这一步填错,向导会报错并让你重填,不会写坏数据。
向导跑完会跳转到登录页。进去之后第一件事是到"设置 → 概览"看一眼有没有黄色警告,通常会有两条:一条是 PHP 内存建议值、一条是数据库的innodb参数建议,按提示微调就行。
3.4 用 occ 命令收尾
occ是 Nextcloud 的命令行工具,容器里跑的时候一定要带-u www-data,否则生成的文件属主会变成 root,后面 Apache 读写不了,症状是某些页面莫名其妙报 500。
docker compose exec -u www-data app php occ status docker compose exec -u www-data app php occ config:system:set trusted_domains 2 --value=cloud.example.com docker compose exec -u www-data app php occ config:system:set overwriteprotocol --value=https docker compose exec -u www-data app php occ config:system:set overwrite.cli.url --value=https://cloud.example.com docker compose exec -u www-data app php occ db:add-missing-indices docker compose exec -u www-data app php occ maintenance:repairdb:add-missing-indices是个很实用的命令,能补上数据库缺失的索引,对文件列表加载速度有肉眼可见的提升,而且它经常在新版本升级后被官方提示要执行。maintenance:repair会修复一些内部的元数据不一致,装完之后跑一次没坏处。
4. 让服务能被公网访问的几条路径
4.1 先判断自己手里到底有没有公网地址
这一步非常关键,很多人卡住就是因为默认自己"有公网 IP",其实没有。最简单的判断方法:在服务器上执行curl -s https://ipv4.icanhazip.com,拿到的是出口地址;再去路由器管理页面看 WAN 口地址。两个地址如果一致,说明你是直接拨号、拿到了公网地址;如果不一致,说明中间还有一层运营商的地址转换,端口映射是无效的。
还有一种情况:两个地址一致,但地址落在100.64.0.0/10这个段里。这不是公网地址,是运营商内部共享用的地址段,同样映射不通。这种时候正确的做法是打客服电话申请公网地址,说明用途是家里装监控或者自建服务,通常都能办下来,有些地区免费,有些需要办个基础套餐。
IPv6 是另一条路。现在多数地区的家宽都已经下发了 IPv6 地址,在服务器上ip -6 addr如果能看到2408、240e之类开头的公网地址,并且能 ping 通外部的 IPv6 地址,那你其实已经拥有公网可达的地址了,完全不需要折腾端口映射。缺点是 IPv6 的可达性取决于访问方是否支持,手机流量一般没问题,某些公司网络和老旧设备就不行。
4.2 方案 A:直接部署在云服务器上
这是最省心的一条路。云服务器天生带公网地址,只要在控制台的安全组里放行 80 和 443 端口,再把域名解析过去就完事了。你不需要处理任何网络地址转换的问题,也不用担心运营商那边的限制。
代价是磁盘和流量要花钱。我的做法是把不常用的大文件留在家里的机器上,云上只放工作文档和照片的近期备份,用 Nextcloud 的"外部存储"功能把两边挂到一起,用起来跟一个盘差不多。注意用外部存储的时候,挂载的是远程的 WebDAV 或者另一台机器的共享目录,配置在"设置 → 外部存储"里,需要装对应的插件。
安全组配置有个原则:只开放必要的端口。80 和 443 之外,SSH 端口要么限制来源 IP,要么换成非默认端口,别把 22 大方地开着。
4.3 方案 B:家里机器 + 路由器端口映射 + 动态域名
有公网地址的情况下,在家里的路由器上把外部的 443 转发到内网服务器的 443 就行。这里有个细节:既然要转发到 443,那宿主机的 HTTPS 就得由转发层来处理,Nextcloud 容器本身还是跑在 8080 上,外面用 Nginx 收 HTTPS 再转进去,链路是公网 → 路由器 → 宿主机 Nginx:443 → 容器:8080。
家用宽带拿到的公网地址一般会变,重启光猫就换一个,所以需要动态域名解析(DDNS)。两条路:一是路由器自带 DDNS 功能,在管理页面里找找,很多型号都有;二是自己在服务器上写个小脚本,定时拿当前地址去调域名服务商的接口更新 A 记录。后者的原理很简单,一个curl请求加一个 cron 就够,关键是记得把接口密钥放在只有自己能读的文件里,别硬编码在脚本里。
注意:家用宽带的 80 和 443 端口在部分地区是被封的,用来做网站访问会失败。遇到这种情况可以把外部端口换成 8443 之类的,访问时带上端口号。证书签发也会受影响,需要用 DNS 验证方式而不是 HTTP 验证。
4.4 方案 C:IPv6 直连 + AAAA 记录
我目前主力用的就是这个。服务器拿到 IPv6 公网地址之后,在域名服务商那里加一条 AAAA 记录指向它,客户端解析到 AAAA 就直接连过来了,中间不经过任何转换环节。速度上比端口映射的方案还快一点,因为没有额外的转发。
要处理的问题有三个。一是防火墙,宿主机的防火墙和路由器的 IPv6 防火墙都要放行对应端口,很多人只改了前者,结果一直连不上。二是地址会变,IPv6 的地址前缀在运营商侧调整时会变,所以同样需要 DDNS 机制,只是把 A 记录换成 AAAA 记录。三是隐私扩展,有些系统开启了临时地址,ip -6 addr会看到一堆地址,得用ip -6 addr show scope global找出那个持久的。
还有个坑:部分客户端在双栈环境下会优先尝试 IPv4,如果你的域名同时有 A 和 AAAA 记录但 IPv4 那条是不通的,浏览器会先卡几秒再回退到 IPv6,体验很差。所以要么只留 AAAA,要么确保两条路都通。
4.5 HTTPS:证书签发与自动续期
不管走哪条方案,HTTPS 都必须上。一方面是明文传输密码和文件不可接受,另一方面 Nextcloud 的很多功能(比如客户端同步、WebDAV 挂载)在非 HTTPS 下会被客户端拒绝。
用 ACME 协议的免费证书就够。以 HTTP 验证方式为例,先确保你的域名已经解析到这台机器、且外部能访问到 80 端口,然后:
apt install certbot mkdir -p /var/www/letsencrypt certbot certonly --webroot -w /var/www/letsencrypt -d cloud.example.com证书会落在/etc/letsencrypt/live/cloud.example.com/下面,fullchain.pem是证书链、privkey.pem是私钥。
续期是自动的,certbot 装的时候会注册一个定时任务,但续期之后必须重新加载 Nginx,否则它还在用内存里的旧证书。加一个钩子文件就解决了,在续期配置目录里放一个脚本,内容是systemctl reload nginx,之后每次续期成功都会自动重载。这个细节很多人漏掉,结果三个月后证书过期,客户端全部报错。
验证续期是否正常,用certbot renew --dry-run跑一次演练,看到 "Congratulations, all simulated renewals succeeded" 就放心了。
4.6 Nginx 转发层的完整配置
前面说的"转发层"就是 Nginx,它负责收 HTTPS、把请求转给容器。配置文件放在/etc/nginx/conf.d/nextcloud.conf:
server { listen 80; server_name cloud.example.com; location /.well-known/acme-challenge/ { root /var/www/letsencrypt; } location / { return 301 https://$host$request_uri; } } server { listen 443 ssl; http2 on; server_name cloud.example.com; ssl_certificate /etc/letsencrypt/live/cloud.example.com/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/cloud.example.com/privkey.pem; ssl_protocols TLSv1.2 TLSv1.3; ssl_session_cache shared:SSL:10m; client_max_body_size 16G; client_body_timeout 3600s; location / { proxy_pass http://127.0.0.1:8080; 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_request_buffering off; proxy_read_timeout 3600s; proxy_send_timeout 3600s; } location /.well-known/carddav { return 301 https://$host/remote.php/dav; } location /.well-known/caldav { return 301 https://$host/remote.php/dav; } }几个参数值得单独解释。client_max_body_size 16G必须跟 PHP 那边的upload_max_filesize对上,这是最常见的"大文件传一半就失败"的根因。proxy_request_buffering off是让请求体直接流进去,而不是先缓存在 Nginx 的临时目录里——对于大文件上传,开启缓冲会先把整个文件写到磁盘再转发,既慢又占空间。proxy_read_timeout 3600s是给长时间的上传下载留出余量。
/.well-known/carddav和/.well-known/caldav这两条重定向是给日历和通讯录同步用的,手机客户端配置账号时会自动去探测这两个路径,没有它们就得手动填完整地址,用户体验差很多。
配完nginx -t检查语法,然后systemctl reload nginx。此时访问https://cloud.example.com应该能正常打开登录页了。
5. 常见问题与排查实录
5.1 页面提示"访问不被信任的域名"
这是最高频的问题,几乎每个人都会遇到一次。原因是请求头里的 Host 不在trusted_domains白名单里。解决办法有两个,临时用occ加一条:
docker compose exec -u www-data app php occ config:system:set trusted_domains 3 --value=新域名或者直接改app/config/config.php里的trusted_domains数组,改完重启容器。我习惯用occ,因为数组索引写错不会把整个配置文件搞坏。
还有个子情况:如果你在转发层之外又套了一层(比如云厂商的负载均衡),那 Nextcloud 看到的 Host 可能是内网地址,这时候除了加白名单,还要确认转发层有没有把Host头透传过来。
5.2 大文件上传失败,或者卡在某个百分比
排查顺序按这个来:先看 Nginx 的client_max_body_size,再看 PHP 的upload_max_filesize和post_max_size(这两个必须同时改),最后看容器的磁盘空间和 inode。日志位置分别是/var/log/nginx/error.log和容器里/var/www/html/data/nextcloud.log。
还有个隐蔽的原因:反向转发如果开了请求缓冲,会在/var/lib/nginx/body下面生成临时文件,那个分区如果只有几 GB,传大文件必然失败。proxy_request_buffering off就是解决这个的,同时检查nginx.conf里client_body_temp_path指向的目录所在分区有多大。
5.3 安装向导走完白屏或者报 500
九成是目录属主问题。执行ls -ln /opt/nextcloud/app | head看一眼,如果是 root 或者 1000,改成 33:
chown -R 33:33 /opt/nextcloud/app /opt/nextcloud/data改完重启容器。如果还不行,去看容器的 Apache 错误日志:
docker compose logs app | tail -50日志里出现 "Permission denied" 后面跟着具体路径,就针对那个路径单独处理。有时候是app/config目录被写坏了,可以把它清空让容器重新生成(记得先备份config.php)。
5.4 手机客户端同步卡住或者反复重连
先确认域名在手机上能打开网页版。如果网页能开、客户端连不上,通常是证书问题——客户端对证书链的校验比浏览器严格,浏览器会自动补全中间证书,客户端不会。用openssl s_client -connect cloud.example.com:443 -showcerts看一下能不能拉出完整的链,缺中间证书就把fullchain.pem换成完整的(而不是只用cert.pem)。
如果客户端连上了但同步慢,看看是不是在传大量小文件。这个问题跟文件数量强相关,几十万个小文件挨个建索引,性能会明显下降。可以考虑把一些老资料打包成压缩包存放,或者开启服务端的批量处理参数。
5.5 速度慢,传不满带宽
三个排查方向。一是转发层的缓冲区配置,proxy_buffering off配合proxy_request_buffering off在处理大文件时更直接。二是磁盘 IO,机械盘写大文件时会成为瓶颈,iostat -x 1看一下%util是不是长期接近 100。三是 MTU 问题,IPv6 隧道或者某些网络环境下 MTU 不是 1500,会导致大包分片,表现为小文件很快、大文件很慢,ping -M do -s 1472 目标地址试探一下能找到合适的值。
5.6 问题速查表
| 现象 | 大概率原因 | 处理动作 |
|---|---|---|
| 白屏 / 500 | 挂载目录属主不是 33 | chown -R 33:33后重启 |
| 访问不被信任的域名 | Host 不在白名单 | occ config:system:set trusted_domains |
| 大文件传一半失败 | 三处体积限制没对齐 | 改 Nginx 与 PHP 的 body/upload 上限 |
| 证书过期告警 | 续期后没重载 Nginx | 加续期钩子脚本重载服务 |
| 数据库锁表 | 用了 SQLite 或多端并发 | 换 MariaDB 并检查READ-COMMITTED |
| 客户端连不上但浏览器正常 | 证书链不完整 | 用 fullchain 而非单证书 |
| 同步慢 | 小文件过多或磁盘 IO 打满 | 合并小文件、换 SSD、看 iostat |
| 容器起不来 | 端口被占用或目录权限 | docker compose logs定位具体报错 |
6. 长期运行需要补的几件事
6.1 备份:别把鸡蛋放在一个挂载点上
我见过太多人把容器跑起来就觉得万事大吉,结果一次误删或者一次磁盘故障,几年的照片全没了。备份这件事必须单独做,而且要遵守"三份数据、两种介质、一份异地"的老规矩。
数据库的备份要用mysqldump导出,不能直接复制数据目录——InnoDB 里有未刷盘的脏页,冷拷贝出来的文件可能不一致:
docker compose exec -T db mysqldump --single-transaction \ -unextcloud -p'你的口令' nextcloud | gzip > /backup/nc-db-$(date +%F).sql.gz--single-transaction是必须的参数,它保证导出的是一致的快照,不会因为导出过程中有人写数据而出现半截状态。
文件部分用rsync做增量,比tar全量打包快得多,也不占额外的临时空间:
rsync -a --delete /opt/nextcloud/data/ /backup/data/--delete要谨慎使用,它会把目标端多余的文件删掉,保持完全一致。如果目标端是你手动整理过的目录,别加这个参数。
异地那份我用的是另一块盘加一台老机器,每周同步一次。容器里还有个occ命令可以辅助:occ files:scan --all会在文件被外部改动后重建索引,从备份恢复之后一定要跑一次,否则文件在网页上看不见。
6.2 升级节奏与回滚
Nextcloud 的大版本升级有严格的路径要求,不能跨大版本跳,比如从 25 直接到 28 是不行的,得 25→26→27→28 一步步来。所以我的策略是:只锁大版本,跟着官方镜像的小版本更新走,一年做一次大版本跨越。
升级前必做三件事:备份数据库、备份app目录、记下当前镜像的完整 tag。升级过程就是改 compose 里的版本号然后docker compose up -d,容器起来后会自动进入升级流程。如果卡住,用occ upgrade手动触发。
出问题要回滚,把版本号改回去重启就行,前提是app目录你备份了——因为升级过程会改config.php和数据库结构,光改镜像版本号是不够的。这就是为什么app和data要分开:data目录在升级中完全不受影响,回滚的时候不用动它,省了大量时间。
6.3 安全加固清单
跑在公网上的东西,该做的基本功要做。我列一下自己在用的几条:
- 强制 HTTPS,并且在 Nginx 里加上 HSTS 响应头,让浏览器以后只用 HTTPS 访问
- 关掉目录列表,Nginx 里
autoindex off - 定期清理
data/nextcloud.log,别让它无限增长,同时留意里面的异常登录记录 - 开启两步验证,Nextcloud 内置了 TOTP 支持,在设置里打开就行
- 用
occ security:routes可以查看所有暴露的路由,定期看一眼有没有意外开放的接口 - 容器不要用
latest标签,锁定具体版本号,否则某天自动拉取新版本可能带来不兼容
还有一个容易忽略的点:限制容器能用的资源。在 compose 里给 app 服务加一段deploy.resources.limits,限制 CPU 和内存上限,防止某个异常请求把整台机器拖垮。这不是性能优化,是防止雪崩的保险丝。
6.4 参数配置对照表
| 参数 | 位置 | 建议值 | 说明 |
|---|---|---|---|
memory_limit | php/custom.ini | 512M | 4GB 内存机器可给 1G |
upload_max_filesize | php/custom.ini | 16G | 按实际需要调整 |
post_max_size | php/custom.ini | 16G | 必须大于等于上一条 |
max_execution_time | php/custom.ini | 3600 | 大文件上传需要 |
client_max_body_size | Nginx | 16G | 与 PHP 侧保持一致 |
proxy_read_timeout | Nginx | 3600s | 长连接传输需要 |
innodb-buffer-pool-size | MariaDB 启动参数 | 内存 1/4 | 2GB 机器给 256M |
maxmemory | Redis 启动参数 | 128mb | 缓存而已,别给太多 |
这套配置在我两台机器上分别跑了一年多和半年多,期间遇到过证书续期失败、大文件超时、容器 OOM 三类问题,都在上面的排查表里覆盖到了。真正需要长期盯的其实只有两件事:磁盘空间和证书有效期,把这两个做成定时提醒,剩下的基本可以放着不管。
如果后面想把分享链接做得好看一点,可以在 Nextcloud 里配置自定义域名前缀;想给家里人分账号,就建几个普通用户并配置配额;想在电视或者旧设备上访问,WebDAV 那条路永远不会过时。这套东西的扩展方向很多,但地基就是上面这些——目录分清楚、限制对齐、证书自动续,其他的都是在这个基础上加东西。