首页 / 资讯中心 / 文章详情

Docker Compose 部署 Nextcloud:自建网盘公网访问与调优

Docker Compose 部署 Nextcloud:自建网盘公网访问与调优 ★ FEATURED ARTICLE
自建网盘这件事我前后折腾过好几轮。最早是在一台旧机器上手动编译 PHP 装 Nextcloud依赖装了大半天跑起来之后每次升级都像拆炸弹后来收敛到 Linux 系统 Docker Nextcloud 这套组合才算真正省心。原因很实在Nextcloud 这类应用牵扯的组件太多——Web 服务、PHP 扩展、数据库、缓存、后台定时任务、文件锁版本之间还互相挑剔。源码装一次能跑半年后升级一次就可能散架换成容器之后这些依赖被固化进镜像宿主机只负责提供内核、磁盘和网络升级回滚无非是换个镜像 tag。再往上加一条公网访问链路手机、笔记本、平板在任何网络环境下都能挂载自己的网盘这套东西才算真正可用。这篇写的是我从零到公网可访问的完整路径编排文件里每一段参数为什么取这个值、证书怎么自动续、没有公网 IP 时隧道转发怎么接、以及那些官方文档不会写但几乎一定会踩的坑。读者对象我尽量放宽——你只要能敲几条 Linux 命令、看得懂 YAML 缩进就能跟着做下来如果你已经在跑类似的容器服务可以直接跳到第 4、5 章看转发配置和调优部分。1. 方案整体设计和选型思路动手之前先把图纸画清楚比边装边改省事得多。这一章聊的是我为什么这么搭以及几个关键岔路口上我怎么选。1.1 为什么把 Nextcloud 交给 Docker 而不是源码包源码包安装 Nextcloud 的流程本质上是把一套 LAMP 环境手工拼起来装 Apache 或 Nginx、装 PHP 并挑齐 gd、intl、imagick、bcmath、zip、redis 这些扩展、配 opcache、再装数据库。中间任何一环版本不对页面就是白屏或者 500报错还藏在日志深处。Docker 的做法是把这一整套东西预先打包成镜像nextcloud:stable-apache这个 tag 里已经包含了 Apache、匹配版本的 PHP 和全部必需扩展你拉下来就能跑。真正的收益在升级和回滚。Nextcloud 每季度发一个大版本跨版本升级必须按顺序来比如 27 → 28 → 29不能从 27 直接跳 30手动升级时你得先把代码目录备份、进维护模式、替换文件、跑occ upgrade中间失败要手工回滚文件。容器化之后升级就是把 compose 里的镜像 tag 从nextcloud:28-apache改成nextcloud:29-apachedocker compose up -d拉新镜像重建容器挂载的数据卷还在occ upgrade在容器启动时自动执行。想回退把 tag 改回去docker compose up -d十秒钟的事。另一层好处是数据与程序分离得干净。代码在镜像里可写的只有两个地方/var/www/html配置、应用、数据和数据库。我把/var/www/html整体挂到宿主机磁盘上停机重建容器、换机器迁移都是拷贝目录 导入 SQL 的活儿没有什么注册表式的隐藏状态。提示镜像 tag 别用latest。latest跟着上游走某天你重启容器它悄悄进了新大版本跨版本升级直接卡死。锁定到具体的主版本号升级变成一次显式动作。1.2 服务拓扑三个容器加一个入口网关我这套的最小拓扑是这样appNextcloud Apache、dbMariaDB、redis缓存和文件锁三个容器挂在同一个自定义桥接网络上彼此用容器名做主机名互通。对外不直接暴露 app 的端口而是把它绑到宿主机回环地址127.0.0.1:8080再由宿主机上的 Nginx 或 Caddy 做 HTTPS 接入。这么设计有两个考虑。第一Docker 的-p 8080:80默认会在 iptables 里插一条规则绕过 UFW 之类的防火墙直接对公网开放很多人以为防火墙开着就安全了其实端口早就透出去了。绑到127.0.0.1上外面根本连不上只有宿主机上的网关能访问。第二TLS 证书、HTTP/2、gzip、限流这些事交给专业的 Web 服务器做Nextcloud 容器专心处理应用逻辑职责不重叠。为什么不用 Nextcloud 自带的 Apache 直接对外提供 HTTPS可以但证书续期要在容器里跑容器重建时 acme 的状态容易丢而且从容器内拿真实客户端 IP 需要额外配置mod_remoteip。放到宿主机层做这些都不是问题。Redis 单独拉一个容器是因为 Nextcloud 用 Redis 做分布式缓存和文件锁。文件锁这功能不起眼但它是多客户端并发写同一份文件时不损坏的关键。用文件锁默认实现在高并发下会明显拖慢响应而且日志里会刷File is locked的警告。挂上 Redis 之后这类告警基本消失。1.3 公网访问的两条路线有公网 IP 与没有公网 IP这是整个方案里最容易卡住的地方得先搞清楚自己处在哪种网络条件下。路线 A有可用的公网 IP。如果你用的是云服务器通常自带公网 IP只需要在安全组里放行 80 和 443把域名 A 记录解析过去在宿主机上跑个 Web 服务器接住流量就行。如果是在家里的宽带上得先确认运营商给的是不是公网 IP——登录路由器看 WAN 口地址如果落在 10.x、100.64.x 到 100.127.x、172.16.x 到 172.31.x、192.168.x 这些私有网段那就是运营商做了地址转换你没有独立的公网地址。这种情况下即使路由器上做了端口映射也没用外面进不来。路线 B没有公网 IP走隧道转发。原理是由一台有公网地址的中间节点替你接住外部请求通过一条长连接把流量转回你内网的机器。常见做法有两种一是用市面上成熟的隧道转发服务装个客户端、填个 token几分钟就有个可访问的地址二是自己在云服务器上部署开源的隧道工具服务端和客户端都自己控带宽和流量完全自己说了算。两条路线的差异主要在带宽和可控性上。云服务器自带公网 IP 的方案速度上限就是服务器带宽2 Mbps 的入门配置传大文件会很难受隧道转发服务通常有带宽限制但不需要自己维护服务器。我自己的做法是主力走云服务器 域名家里那台做备份节点用隧道转发接上去只在主节点挂掉时切过去。注意不管走哪条路线最终暴露到公网的只有 80 和 443 两个端口。数据库、Redis、Nextcloud 的 8080 一个都不要往外放。这点后面第 4.4 节还会展开。2. Linux 底座与 Docker 环境的准备装 Docker 本身没什么难度真正影响后续体验的是磁盘布局和内核参数这章重点讲这两块。2.1 系统版本选择与基础加固发行版我一般用 Ubuntu 22.04 LTS 或 Debian 12两者对 Docker 的支持都很成熟内核版本 5.x 以上能直接用 overlay2 存储驱动不需要额外折腾。CentOS 系当然也能用但要留意部分版本的 cgroup v2 和默认防火墙行为跟 Docker 的 iptables 规则有交互容易出容器能上网但外面访问不到的怪事。装系统时有几个选项值得留意。一是不要在安装时勾选任何预装的 Web 服务Apache、Nginx、MySQL它们会和后面的容器抢 80/443 端口排查起来很费劲。二是创建一个普通用户并加入 sudo 组日常操作别用 rootDocker 虽然需要 root 权限但docker命令本身把用户加进docker组就能用——不过要注意进了docker组等价于拿到 root 权限这是常识心里有数就行。系统装完第一件事是更新并装几个基础工具sudo apt update sudo apt -y upgrade sudo apt -y install curl ca-certificates gnupg ufw fail2ban chronychrony负责时间同步。这条容易被忽略但 Nextcloud 生成的分享链接、文件修改时间、TOTP 二次验证都依赖准确的系统时间容器继承宿主机时钟宿主机时间偏了几分钟验证码就对不上。接着配置防火墙。以 UFW 为例sudo ufw default deny incoming sudo ufw default allow outgoing sudo ufw allow 22/tcp sudo ufw allow 80/tcp sudo ufw allow 443/tcp sudo ufw enable这里有个坑必须提前说Docker 发布的端口会绕过 UFW。因为 Docker 直接往 nat 表的 DOCKER 链里写规则优先级在 UFW 之前。所以即便你 UFW 只放行了 22/80/443只要 compose 里写了- 8080:808080 就对公网敞开了。解决办法有两个一是像第 1.2 节说的端口映射时绑到127.0.0.1二是修改/etc/docker/daemon.json加上iptables: false但这会影响容器间的端口转发不建议新手动。绑回环地址是最省心的做法。2.2 Docker 与 Compose 插件的安装细节用官方仓库安装别用发行版自带的docker.io包那个版本往往落后两三年compose 还是老的 python 版本。sudo install -m 0755 -d /etc/apt/keyrings curl -fsSL https://download.docker.com/linux/ubuntu/gpg \ | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg sudo chmod ar /etc/apt/keyrings/docker.gpg echo deb [arch$(dpkg --print-architecture) signed-by/etc/apt/keyrings/docker.gpg] \ https://download.docker.com/linux/ubuntu $(. /etc/os-release echo $VERSION_CODENAME) stable \ | sudo tee /etc/apt/sources.list.d/docker.list /dev/null sudo apt update sudo apt -y install docker-ce docker-ce-cli containerd.io \ docker-buildx-plugin docker-compose-plugin这里装的是docker-compose-plugin也就是docker compose子命令注意中间是空格不是横杠。网上大量老教程还在用docker-compose带横杠的独立二进制两者配置文件格式基本兼容但插件版跟随 Docker 一起升级长期看更省事。装完验证一下docker --version docker compose version sudo systemctl status docker --no-pager再跑个 hello world 确认能拉镜像。如果拉取卡在Pulling from library/hello-world多半是 DNS 问题检查/etc/resolv.conf或者给 Docker 配一个可用的 DNS{ dns: [223.5.5.5, 1.1.1.1], log-driver: json-file, log-opts: { max-size: 50m, max-file: 3 } }这份/etc/docker/daemon.json里的log-opts是我强烈建议加的。Docker 默认不限制日志大小一个访问量稍大的 Nextcloud 容器json.log能长到几个 G把磁盘塞满。限制成单文件 50MB、最多 3 个够排查问题又不至于撑爆盘。2.3 存储分层数据盘、缓存盘与备份盘Nextcloud 的性能有很大一部分取决于磁盘 IO。我的建议是至少分三块用途挂载点建议介质说明系统与镜像/SSD40G 起镜像层和容器可写层都在这里用户数据/srv/nc/html大容量盘独立分区文件本体、缩略图、版本库数据库/srv/nc/dbSSD20G 起InnoDB 随机读写频繁机械盘会是瓶颈备份独立磁盘或远端—至少要跟数据盘物理隔离如果只有一块盘那就用目录区分但一定要把数据目录和系统盘分开挂载否则系统盘一旦写满Nextcloud 的缩略图和版本文件能吃掉几十 G 而不自知机器直接失联。数据盘挂载后做两件事格式化并写入 fstab 时用defaults,noatime选项noatime能减少不必要的元数据写入对文件型负载有实际收益。然后创建目录结构sudo mkdir -p /srv/nc/{html,db,php,backup} sudo chown -R 33:33 /srv/nc/html sudo chown -R 999:999 /srv/nc/db33是 Debian 系镜像里www-data的 UIDNextcloud 容器里的 Apache 就以这个身份写文件。999是 MariaDB 容器的mysql用户 UID。这两个数字看着像玄学但它们是数据目录权限正确的关键后面 6.3 节会专门讲权限错乱怎么排查。3. 用 Compose 编排 Nextcloud 主服务这章是全篇的核心编排文件我逐段拆开讲参数取值给理由。3.1 编排文件逐段拆解与参数取值先看完整文件我把它放在/srv/nc/docker-compose.ymlservices: db: image: mariadb:10.11 container_name: nc-db restart: unless-stopped command: - --transaction-isolationREAD-COMMITTED - --binlog-formatROW - --innodb-buffer-pool-size1G - --innodb-flush-log-at-trx-commit2 volumes: - /srv/nc/db:/var/lib/mysql environment: - MARIADB_ROOT_PASSWORD改成一个长随机串 - MARIADB_DATABASEnextcloud - MARIADB_USERnextcloud - MARIADB_PASSWORD另一个长随机串 networks: - ncnet redis: image: redis:7-alpine container_name: nc-redis restart: unless-stopped command: redis-server --requirepass 再一个长随机串 --maxmemory 256mb --maxmemory-policy allkeys-lru networks: - ncnet app: image: nextcloud:29-apache container_name: nc-app restart: unless-stopped depends_on: - db - redis ports: - 127.0.0.1:8080:80 volumes: - /srv/nc/html:/var/www/html - /srv/nc/php/custom.ini:/usr/local/etc/php/conf.d/custom.ini:ro environment: - MYSQL_HOSTdb - MYSQL_DATABASEnextcloud - MYSQL_USERnextcloud - MYSQL_PASSWORD与上面数据库密码一致 - REDIS_HOSTredis - REDIS_HOST_PASSWORD与上面 redis 密码一致 - NEXTCLOUD_TRUSTED_DOMAINScloud.example.com - NEXTCLOUD_ADMIN_USERadmin - NEXTCLOUD_ADMIN_PASSWORD安装向导用的强密码 - TRUSTED_PROXIES172.20.0.0/16 - APACHE_DISABLE_REWRITE_IP1 - PHP_MEMORY_LIMIT1G - PHP_UPLOAD_LIMIT2G networks: - ncnet networks: ncnet: driver: bridge ipam: config: - subnet: 172.20.0.0/16逐块解释几个关键点。command里的四个 MariaDB 参数。READ-COMMITTED是隔离级别Nextcloud 官方明确要求用可重复读会产生大量间隙锁在文件扫描这类密集写入场景下容易死锁。binlog-formatROW配合隔离级别。innodb-buffer-pool-size1G是缓冲池经验值是分给它物理内存的 40%~60%——容器里最好显式写死不然它默认按宿主机内存算容易吃撑被杀。innodb-flush-log-at-trx-commit2把日志刷盘从每次事务降为每秒一次牺牲极小概率的崩溃丢失换吞吐个人网盘场景完全值得。ports写成127.0.0.1:8080:80。前面说过这是防止绕过防火墙的唯一简单手段。冒号前面加 IPDocker 就只在那一个地址上监听。环境变量里的TRUSTED_PROXIES。这个值要跟下面networks里定义的子网对上。宿主机上的 Nginx 通过172.20.0.1网桥网关访问容器容器看到的来源 IP 就是这个网关地址。告诉 Nextcloud 信任这个网段它才会解析X-Forwarded-For拿到真实客户端 IP日志和登录限流才有意义。写成172.20.0.0/16是个宽松范围够用。APACHE_DISABLE_REWRITE_IP1。不加这个Apache 会自己重写REMOTE_ADDR把真实 IP 又盖掉跟上面那项打架。PHP_UPLOAD_LIMIT2G。官方镜像支持这个环境变量会自动写对应的 php.ini。不过我建议还是用挂载的custom.ini统一管见下。/srv/nc/php/custom.ini内容upload_max_filesize 2G post_max_size 2G memory_limit 1G max_execution_time 3600 max_input_time 3600 output_buffering 0 opcache.enable 1 opcache.enable_cli 1 opcache.memory_consumption 256 opcache.interned_strings_buffer 32 opcache.max_accelerated_files 20000 opcache.save_comments 1 opcache.revalidate_freq 1opcache.enable_cli 1这行看似无关紧要实际上occ命令行工具在跑files:scan这类批量任务时开启 CLI 的 opcache 能让执行时间显著缩短。save_comments必须为 1Nextcloud 大量依赖注解式路由关掉注释保存页面直接白屏。3.2 数据库与 Redis 的关键参数怎么定数据库这块密码策略值得单独说一句。三个密码MariaDB root、Nextcloud 数据库用户、Redis都用 32 位以上的随机串生成方式openssl rand -base64 32 | tr -d / | head -c 32为什么要去掉/因为密码里出现这些字符时写进 compose 的command或环境变量容易出转义问题Redis 尤其明显——--requirepass后面跟一串带特殊字符的密码shell 解析和配置解析两套规则打架报出来的错还牛头不对马嘴。全部用字母数字一劳永逸。MariaDB 的innodb-buffer-pool-size我上面写了 1G。怎么定这个值公式是可用内存 − Nextcloud 容器占用PHP 大概 512MB~1GB− 系统占用约 512MB− Redis 的maxmemory。一台 4G 内存的机器1G 给数据库、256MB 给 Redis、1G 给 PHP剩下 1.7G 留给页缓存和系统是比较舒服的分配。8G 的机器可以给数据库 3G~4GPhp 给 2GRedis 给 512MB。Redis 的maxmemory-policy allkeys-lru是必须的。Nextcloud 用 Redis 存的是缓存和文件锁两类数据都是可以丢的LRU 淘汰最久未用的键正好。如果配成默认的noeviction内存满了之后写操作直接报错文件锁加不上用户会看到莫名其妙的保存失败。提示Redis 容器不要挂持久化卷。缓存数据掉电就掉电重新生成即可某些情况下 AOF/RDB 文件损坏反而会拖慢启动。3.3 首次启动、安装向导与安装后必做的四件事编排文件就位后启动cd /srv/nc sudo docker compose up -d sudo docker compose logs -f app盯着日志看到AH00558: apache2: Could not reliably determine the servers fully qualified domain name不用管那是 Apache 的常规提示。真正开始工作会在几十秒后自动执行安装因为有NEXTCLOUD_ADMIN_USER这些环境变量向导会被跳过。日志里出现Nextcloud was successfully installed就成功了。然后访问http://127.0.0.1:8080服务端本地或先配置好域名再访问https://cloud.example.com。第一件事确认 config.php 里的信任域和转发配置。虽然环境变量写了NEXTCLOUD_TRUSTED_DOMAINS但镜像只在首次安装时读取这些变量写进配置后续修改环境变量不起作用。要改就得用occsudo docker exec -u www-data nc-app php occ config:system:set \ trusted_domains 1 --valuecloud.example.com sudo docker exec -u www-data nc-app php occ config:system:set \ overwriteprotocol --valuehttps sudo docker exec -u www-data nc-app php occ config:system:set \ overwrite.cli.url --valuehttps://cloud.example.com sudo docker exec -u www-data nc-app php occ config:system:set \ default_phone_region --valueCN sudo docker exec -u www-data nc-app php occ config:system:set \ maintenance_window_start --value1 --typeintegeroverwrite.cli.url特别关键后面生成分享链接、邮件通知里的地址、以及客户端配置都是用这个值拼接。不设的话你在后台看到的分享链接可能是http://127.0.0.1:8080/s/xxxx发出去谁都打不开。default_phone_region填国家代码影响电话号码的默认解析格式。maintenance_window_start是指定维护任务在几点开始跑UTC 时间设成 1 就是凌晨一点避免白天跑后台任务抢占资源。第二件事切到 cron 模式。Nextcloud 默认用 AJAX 方式跑后台任务也就是用户访问页面时才触发这在个人使用场景下问题不大但会导致缩略图生成、外部存储扫描、回收站清理这些任务拖很久。改成 cron 之后由系统定时器驱动效率高得多。sudo docker exec -u www-data nc-app php occ background:cron然后在宿主机加定时任务echo */5 * * * * docker exec -u www-data nc-app php -f /var/www/html/cron.php \ | sudo tee /etc/cron.d/nextcloud sudo chmod 644 /etc/cron.d/nextcloud每 5 分钟跑一次官方推荐值。第三件事数据库索引补齐。大版本升级或迁移之后检查一下有没有缺失的索引列sudo docker exec -u www-data nc-app php occ db:add-missing-indices sudo docker exec -u www-data nc-app php occ db:add-missing-columns sudo docker exec -u www-data nc-app php occ db:add-missing-primary-keys这三条命令执行时经常要跑几分钟尤其是文件表大的时候耐心等别中途 CtrlC。第四件事后台安全与设置检查。登录 Web 界面进「管理设置 → 概览」Nextcloud 会做一轮体检列出缺的 PHP 模块、推荐的配置项、以及安全隐患。常见的几条The PHP module imagick is not installed影响预览图生成质量、No memory cache has been configuredRedis 没接上、The Strict-Transport-Security HTTP header is not setHTTPS 配好之后在网关层加 HSTS。这些不解决也能用但会一直挂个橙色警告强迫症受不了。4. 公网访问落地域名、证书与转发配置服务在容器里跑通只是半程真正让手机在外面也能用的是这章的转发链路。4.1 域名解析与证书自动续期域名随便找个注册商买一个几十块一年。解析加一条 A 记录主机记录填cloud值填你的公网 IP。解析生效后用dig cloud.example.com确认返回的是你的公网地址才算数。如果你走的是家里的宽带且确认有公网 IP这条记录也照样能用只是路由器上要做端口映射把 80/443 转到内网机器。关于 80 端口还有个小细节ACME 的 HTTP-01 验证需要 80 端口可达所以证书签发期间 80 必须通。签发完成之后80 可以只用来做 301 跳转到 443。证书我用 Caddy 来管因为它把申请、安装、续期全自动化了一个 Caddyfile 就够。装 Caddy 用官方源sudo apt -y install debian-keyring debian-archive-keyring apt-transport-https curl -1sLf https://dl.cloudsmith.io/public/caddy/stable/gpg.key \ | sudo gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg curl -1sLf https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt \ | sudo tee /etc/apt/sources.list.d/caddy-stable.list sudo apt update sudo apt -y install caddyCaddyfile 放在/etc/caddy/Caddyfilecloud.example.com { encode zstd gzip header { Strict-Transport-Security max-age31536000; includeSubDomains X-Content-Type-Options nosniff X-Frame-Options SAMEORIGIN Referrer-Policy no-referrer -Server } dav path /remote.php/dav/* wellknown path /.well-known/carddav /.well-known/caldav redir wellknown /remote.php/dav/ permanent reverse_proxy 127.0.0.1:8080 { header_up X-Forwarded-Proto {scheme} header_up X-Forwarded-Host {host} transport http { read_timeout 1h write_timeout 1h } } }header_up X-Forwarded-Proto {scheme}这行必须留Caddy 默认会传但显式写上更容易排查。read_timeout/write_timeout拉到 1 小时是为了避免大文件上传时连接被中途掐断——默认值更短几个 G 的视频传到一半断掉用户会以为网盘坏了。改完sudo systemctl reload caddy然后看日志sudo journalctl -u caddy -f第一次访问域名时会触发证书申请几秒钟后日志里出现certificate obtained successfully。Caddy 会在到期前自动续期完全不用管。注意Caddy 申请证书需要 80 和 443 都能从公网访问到。如果 80 被占用比如系统预装了 Apache先停掉它。sudo ss -tlnp | grep :80能快速查出谁在占。4.2 反向转发配置里的请求头陷阱用 Nginx 的话配置长这样server { listen 80; server_name cloud.example.com; 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; client_max_body_size 0; client_body_buffer_size 512k; client_body_timeout 3600s; location / { proxy_pass http://127.0.0.1:8080; 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; proxy_set_header X-Forwarded-Host $host; proxy_request_buffering off; proxy_buffering off; proxy_read_timeout 3600s; proxy_send_timeout 3600s; } location /.well-known/carddav { return 301 /remote.php/dav/; } location /.well-known/caldav { return 301 /remote.php/dav/; } }三个点必须说到。client_max_body_size 0表示不限大小。Nextcloud 桌面客户端用的是分块上传单个分块通常 10MB看起来默认 1MB 的限制不至于挡路但 Web 界面上传走的是整体 POST超过限制直接 413浏览器只给你一个上传失败连原因都不显示。X-Forwarded-Proto必须传。Nextcloud 生成绝对 URL 时靠这个判断协议不传的话它以为自己在 HTTP 下会生成http://cloud.example.com/...的链接浏览器打开被 Caddy/Nginx 301 到 HTTPS但如果是 API 调用或者 WebDAV 客户端就会出现登录成功后立刻掉线的诡异行为。proxy_request_buffering off让 Nginx 边收边转发而不是先把整个请求体缓存到磁盘再发。上传大文件时这个设置能省掉一份临时磁盘写入也避免了临时目录被撑爆。4.3 没有公网 IP 时的隧道转发做法前面说过家用宽带有很大概率拿不到公网 IP。测的方法很简单在机器上执行curl -4 ifconfig.me拿到出口地址跟路由器 WAN 口显示的地址比一致就说明是公网不一致就是运营商转换过。也可以直接问运营商客服不过问法要注意有些客服听见公网 IP就上线话术不如自己先测。没公网地址时隧道转发是标准解法。原理不复杂内网机器主动向公网节点建立一条长连接并保持心跳公网节点收到外部请求后顺着这条已经建立好的连接把数据推回来。因为是内网主动发起NAT 和防火墙都拦不住。现成的服务用起来最省事注册账号、创建隧道、把本地127.0.0.1:8080映射出去会给你一个二级域名。配置大概是这样以常见客户端为例# 客户端安装后用 token 认证 tunnel-client login --token 你的令牌 # 绑定本地服务 tunnel-client http 127.0.0.1:8080 --subdomain myshare之后访问https://myshare.xxx.com就能进到你的 Nextcloud。但有几个折扣要打免费档的带宽普遍在 1 Mbps 到 2 Mbps传文档没问题传视频基本没法看隧道地址是别人的域名Nextcloud 要把这个域名加进trusted_domains否则直接拒绝访问sudo docker exec -u www-data nc-app php occ config:system:set \ trusted_domains 2 --valuemyshare.xxx.com sudo docker exec -u www-data nc-app php occ config:system:set \ overwrite.cli.url --valuehttps://myshare.xxx.com自己搭隧道服务的话需要一台带公网 IP 的云服务器当服务端。开源方案里比较主流的是 frp 这一类服务端配一个bindPort和vhostHTTPPort客户端配serverAddr和localPort两边用 token 认证。配置文件大概长这样# 服务端 bindPort 7000 vhostHTTPPort 80 vhostHTTPSPort 443 auth.token 换成随机串# 客户端 serverAddr 你的公网服务器地址 serverPort 7000 auth.token 与服务端一致 [[proxies]] name nextcloud type http localIP 127.0.0.1 localPort 8080 customDomains [cloud.example.com]阿里云、腾讯云这类厂商的入门款 2 核 2G、3M 带宽的机器跑这个绰绰有余。带宽是硬限制3M 大概就是 380KB/s能同步文档和小图不适合当主力看电影。想要更高带宽成本就上去了。4.4 防火墙、安全组与暴露面收敛公网暴露之后安全配置不能省。分三层做。第一层云厂商安全组。只放行 22最好改成非标端口、80、443 入站出站全放。其余全部拒绝。这里有个常见失误安全组是按实例生效的Docker 自己写的 iptables 规则在容器网络命名空间里跟安全组的检查顺序不同别指望安全组能兜住容器私自发布的端口。所以前面强调127.0.0.1:8080:80的必要性。第二层主机防火墙。UFW 规则如第 2.1 节。另外装上 fail2ban给 SSH 和 Nextcloud 的登录失败做封禁sudo apt -y install fail2ban sudo systemctl enable --now fail2banNextcloud 自带暴力破解防护bruteforce protection连续登录失败会锁定来源 IP 一段时间但作用范围只在应用层SSH 的保护还得靠 fail2ban。第三层应用层。有几个配置值得调关闭公开注册个人网盘用不上。开启两步验证TOTP 用手机应用即可。在「管理设置 → 安全」里把「强制 HTTPS」打开。定期检查「设备与会话」列表把不认识的登出。分享链接默认加密码和过期时间别图省事。提示别把 Nextcloud 的 admin 账号用于日常同步。建一个普通用户admin 只用来做管理。桌面客户端保存的是长期凭据客户端被入侵时 admin 权限泄露的后果大得多。5. 性能调优与长期运维跑起来只是及格线能用得舒服还得调一调。这章讲我实测有效的几项。5.1 PHP 与缓存把首屏从 3 秒压到 1 秒默认配置下Nextcloud 的文件列表页面首屏要 2 到 3 秒点进一个几万文件的目录更是要转半天。慢的原因通常是三处opcache 没开、文件锁走磁盘、缩略图不缓存。opcache的配置前面custom.ini里已经给了一份。判断是否生效sudo docker exec nc-app php -i | grep -E opcache.enable|memory_consumption看到opcache.enable On就对了。如果memory_consumption还是默认的 128按我给的 256 改。缓存文件数max_accelerated_files设 20000Nextcloud 加上各种应用PHP 文件数量轻松过万。APCu 内存缓存。这是本地缓存存配置和路由表能省掉每次请求重读文件的开销。在config.php里加sudo docker exec -u www-data nc-app php occ config:system:set \ memcache.local --value\OC\Memcache\APCu sudo docker exec -u www-data nc-app php occ config:system:set \ memcache.distributed --value\OC\Memcache\Redis sudo docker exec -u www-data nc-app php occ config:system:set \ memcache.locking --value\OC\Memcache\Redis sudo docker exec -u www-data nc-app php occ config:system:set \ redis host --valueredis sudo docker exec -u www-data nc-app php occ config:system:set \ redis password --value你的redis密码注意redis这个前缀的写法它是数组式配置的简写实际写进 config.php 是嵌套的redis [host redis, ...]。配置完最好重启一次容器docker compose restart app让 Apache 重新加载。缩略图预生成。图片目录第一次浏览时会现场生成缩略图几百张图能卡住页面。用occ preview:pre-generate提前跑一遍sudo docker exec -u www-data nc-app php occ preview:pre-generate这命令默认只处理最近的文件想全量的话加上--all但会很慢建议挂在夜间 cron 里分批跑。这三项加起来我的实测数据是文件列表首屏从 2.8 秒降到 0.9 秒进入万文件目录从 6 秒多降到 2 秒以内。机器配置没变。5.2 后台任务从 AJAX 切到 cron 的收益第 3.3 节已经讲了怎么切这里说为什么值得切。AJAX 模式下后台任务由页面访问触发每次请求跑一小段跑到一半请求结束了就中断下次接着来。结果就是你上传了 500 张照片缩略图可能一周都生成不完回收站里删掉的文件30 天清理周期变成了看心情外部存储的增量扫描永远滞后。cron 模式下任务由定时器独占执行没有 30 秒超时的限制一次能跑完一大片。判断当前模式sudo docker exec -u www-data nc-app php occ background:cron这条命令本身就是切换没有输出表示成功。再确认一下sudo docker exec -u www-data nc-app php occ config:app:get core backgroundjobs_mode返回cron就成了。然后回到「管理设置 → 概览」那条后台任务使用 AJAX的警告会自动消失。有个细节容器内的 cron 不会自己跑必须靠宿主机的 cron 调docker exec。我上面的写法是每 5 分钟一次。如果服务器负载低也可以把occ命令和cron.php都挂上但一般来说 5 分钟够。5.3 备份策略与恢复演练自建网盘最怕的不是停服是数据丢了。备份我按3-2-1来至少三份、两种介质、一份异地。Nextcloud 的备份分两块缺一不可数据库和文件目录。只备份文件不备份数据库恢复出来的是一堆散文件用户、分享、评论、标签全丢只备份数据库不备份文件恢复出来是空壳。数据库备份脚本#!/bin/bash set -e BACKUP_DIR/srv/nc/backup DATE$(date %Y%m%d-%H%M) mkdir -p $BACKUP_DIR docker exec nc-db mariadb-dump \ --single-transaction --quick --lock-tablesfalse \ -u nextcloud -p$DB_PASSWORD nextcloud \ | gzip $BACKUP_DIR/db-$DATE.sql.gz rsync -a --delete /srv/nc/html/ $BACKUP_DIR/html-mirror/ find $BACKUP_DIR -name db-*.sql.gz -mtime 14 -delete--single-transaction让导出在一致性快照里进行不锁表导出期间服务照常可用。--lock-tablesfalse配合它使用。文件用 rsync 做镜像同步第一次全量之后增量。恢复流程一定要演练不然真出事的时候手忙脚乱。步骤是docker compose down。清空/srv/nc/db重新up -d db让它初始化空库。导入 SQLgunzip -c db-xxx.sql.gz | docker exec -i nc-db mariadb -u nextcloud -p*** nextcloud。把 html 镜像同步回/srv/nc/htmlchown -R 33:33。docker compose up -d等 app 启动。occ maintenance:mode --offocc files:scan --all重建文件索引。我在测试环境里跑过两次完整恢复第二次才发现一个坑数据库备份里的用户 ID 和文件目录里的data/用户名/files/必须对应如果中间删过用户残留的目录会导致扫描报错。所以恢复后先跑occ files:scan --all看日志里有没有孤儿目录的提示。注意备份文件不要放在同一块盘上。我见过一个案例数据盘坏掉备份正好也在同一个物理盘上一起没了。异地那份最省事的做法是 rsync 到另一台机器或者用对象存储的同步工具。5.4 升级节奏与回滚预案Nextcloud 的发版节奏是每年四个大版本每个大版本的维护周期大约一年。次要版本比如 29.0.x 到 29.0.y是修 bug 和小漏洞可以放心升大版本29 → 30要等发布后一个月再动让社区先趟雷。升级步骤我固定的套路是cd /srv/nc # 1. 全量备份包括数据库和文件 sudo ./backup.sh # 2. 让应用进入维护模式 sudo docker exec -u www-data nc-app php occ maintenance:mode --on # 3. 改 compose 里的镜像 tag sudo sed -i s|nextcloud:29-apache|nextcloud:30-apache| docker-compose.yml # 4. 拉镜像并重建 sudo docker compose pull app sudo docker compose up -d app # 5. 看日志等升级完成 sudo docker compose logs -f app # 6. 关闭维护模式 sudo docker exec -u www-data nc-app php occ maintenance:mode --off升级结束后跑一遍sudo docker exec -u www-data nc-app php occ status sudo docker exec -u www-data nc-app php occ db:add-missing-indices sudo docker exec -u www-data nc-app php occ maintenance:repair回滚就是把第 3 步的 tag 改回去docker compose up -d app。但要注意升级过程中数据库结构可能已经改了大版本回退时数据库不兼容只能连备份一起恢复。所以我每次大版本升级前的那次备份都单独留一份不参与 14 天自动清理。6. 常见问题排查实录这一章基本是我这两年里踩过的坑的合集按症状归类。6.1 大文件上传卡住或提示 413症状上传 500MB 以上的文件进度条卡在某个百分比不动或者浏览器直接报413 Request Entity Too Large。排查顺序先看是不是网关拦的。浏览器开发者工具的 Network 面板里如果请求返回 413 且响应体是 Nginx 或 Caddy 的默认错误页那就是网关层。Caddy 默认没有请求体大小限制Nginx 默认client_max_body_size 1m改配置里那一行。如果网关没拦看容器内 Apache。Nextcloud 的 Apache 镜像有一个LimitRequestBody设置用环境变量APACHE_BODY_LIMIT0可以关掉environment: - APACHE_BODY_LIMIT0再看 PHP 层。upload_max_filesize和post_max_size要同步改只改一个没用——post_max_size限制的是整个请求体包括表单字段和其他部分通常要设得比upload_max_filesize略大。我习惯两个都设 2G简单。最后看 Nextcloud 客户端。桌面客户端走分块上传理论上不受 PHP 限制但如果服务端返回 413 让分块上传失败客户端会整体重试表现就是上传一直不动。提示client_body_temp_path所在的磁盘如果满了也会表现为上传卡住且没有明显报错。检查df -h和/var/log/nginx/error.log里的 write error。6.2 打开页面被重定向到内网地址症状用域名访问登录成功后又跳回http://127.0.0.1:8080/...或者http://内网IP/...甚至陷入重定向循环。这是overwrite系列配置没设对。Nextcloud 需要一个外部看到的地址来判断怎么生成链接这个值就是overwrite.cli.url。检查sudo docker exec -u www-data nc-app php occ config:system:get overwrite.cli.url sudo docker exec -u www-data nc-app php occ config:system:get overwriteprotocol sudo docker exec -u www-data nc-app php occ config:system:get trusted_domains三个值的正确形态分别是https://cloud.example.com、https、包含cloud.example.com。缺哪个补哪个。还有一个隐蔽情况配置里同时有overwritehost和overwrite.cli.url两者不一致Nextcloud 会优先用overwritehost。检查一下有没有历史遗留sudo docker exec -u www-data nc-app php occ config:system:get overwritehost有值且不需要的话删掉occ config:system:delete overwritehost。重定向循环的另一个成因是网关没传X-Forwarded-Proto。容器以为自己在 HTTP 下于是生成 HTTP 链接浏览器请求后又被网关 301 到 HTTPS来回打转。加上请求头就好。6.3 权限报错与文件不可写症状后台概览报Your data directory is invalid或Cannot create files in the data directory或者上传文件时报Could not write to ...。根因宿主机目录的属主和容器内进程的 UID 对不上。先查容器内进程的 UIDsudo docker exec nc-app id www-data # 输出类似 uid33(www-data) gid33(www-data)然后修宿主机目录sudo chown -R 33:33 /srv/nc/html sudo chmod -R 755 /srv/nc/html这里有个细节chmod -R 755会把所有文件变成可执行权限虽然不影响功能但不太规范。更好的做法是分开处理sudo find /srv/nc/html -type d -exec chmod 755 {} \; sudo find /srv/nc/html -type f -exec chmod 644 {} \;另一个常见坑容器启动时报Cannot start when maintenance mode is enabled。这是上次升级中断留下的状态执行sudo docker exec -u www-data nc-app php occ maintenance:mode --off如果容器根本起不来一启动就退出进不去执行 occ就手工改/srv/nc/html/config/config.php把maintenance true改成false。6.4 容器反复重启与数据库连接失败症状docker compose ps显示 app 容器的状态在Restarting
阅读完成 · 觉得有帮助?
咨询建站