想把照片、文档和团队文件放在自己控制的服务器上,又需要网页访问、分享链接、版本历史和手机自动上传,Nextcloud 是 VPS 私有云盘中搜索量和使用需求都很高的方案。它比单纯的 WebDAV 文件列表更完整,但也比 Syncthing、OpenList 更依赖数据库、缓存、定时任务和正确的反向代理配置。
本文以 Ubuntu 24.04 LTS、Nextcloud 33、PostgreSQL、Redis、Docker Compose 和 Caddy 为例,从零部署一套可长期维护的单机私有云。应用只监听 VPS 本机端口,数据库和 Redis 不暴露公网,并包含大文件上传、Cron、桌面与手机同步、备份恢复、升级和常见告警处理。
这三类工具都能访问文件,但解决的问题不同:
| 工具 | 更适合的需求 | 主要取舍 |
|---|---|---|
| Nextcloud | 中心化网盘、用户、分享、版本、日历和协作 | 功能完整,资源和维护成本较高 |
| Syncthing | 多台设备之间点对点同步文件夹 | 同步效率高,但不是完整网盘门户 |
| OpenList | 聚合网盘、WebDAV 和统一文件入口 | 轻量方便,不应当作为唯一数据副本 |
如果只想让电脑和 NAS 之间同步文件,可以先看 VPS 搭建 Syncthing 教程。如果重点是挂载多个第三方网盘,则更适合 VPS 搭建 OpenList 教程。Nextcloud 更适合需要账号、权限、分享、历史版本和移动端照片备份的个人或小团队。
Nextcloud 的 CPU 压力通常来自缩略图、全文搜索、应用升级和多人并发,磁盘压力则来自大量小文件、版本历史和回收站。
| 使用规模 | 建议配置 | 说明 |
|---|---|---|
| 1-3 人、少量文档照片 | 2 核 4GB、100GB SSD | 可运行基础文件同步和分享 |
| 3-10 人、手机照片备份 | 4 核 8GB、250GB SSD 起 | 给预览生成、数据库和 Redis 留余量 |
| 大量照片视频或团队协作 | 4-8 核、8-16GB、独立数据盘 | 需要监控容量、IOPS 和备份窗口 |
系统盘和数据盘可以分开,但不要把 Nextcloud 数据目录直接放在不稳定的网络挂载上。对象存储也不是默认更快:它适合明确需要扩展容量和解耦主机生命周期的场景,单机小站优先使用可靠的本地 SSD/NVMe,再做异地备份。可参考 VPS 什么时候需要对象存储 判断是否值得增加复杂度。
准备域名,例如 cloud.example.com,将 A / AAAA 记录指向 VPS。公网只开放 SSH、80 和 443,不开放 PostgreSQL 5432、Redis 6379 或 Nextcloud 本机端口 8080。
使用 Docker 官方仓库安装 Docker Engine 和 Compose v2,然后确认版本:
docker --version
docker compose version
创建部署、配置和备份目录:
sudo mkdir -p /opt/nextcloud/backups
sudo chown -R "$USER":"$USER" /opt/nextcloud
cd /opt/nextcloud
生成 PostgreSQL、Redis 和 Nextcloud 管理员密码:
openssl rand -hex 32
openssl rand -hex 32
openssl rand -base64 32
nano .env
chmod 600 .env
写入 /opt/nextcloud/.env:
POSTGRES_DB=nextcloud
POSTGRES_USER=nextcloud
POSTGRES_PASSWORD=替换为第一组随机值
REDIS_HOST_PASSWORD=替换为第二组随机值
NEXTCLOUD_ADMIN_USER=ncadmin
NEXTCLOUD_ADMIN_PASSWORD=替换为第三组随机值
NEXTCLOUD_HOST=cloud.example.com
NEXTCLOUD_URL=https://cloud.example.com
PHP_MEMORY_LIMIT=1024M
PHP_UPLOAD_LIMIT=10G
管理员账号不要使用 admin,密码不要与 VPS、邮箱或数据库共用。首次初始化完成后仍要保留 .env,但必须限制读取权限并纳入加密备份。
创建 /opt/nextcloud/compose.yaml:
services:
db:
image: postgres:17-alpine
container_name: nextcloud-db
restart: unless-stopped
env_file:
- .env
environment:
POSTGRES_DB: "${POSTGRES_DB:?set POSTGRES_DB in .env}"
POSTGRES_USER: "${POSTGRES_USER:?set POSTGRES_USER in .env}"
POSTGRES_PASSWORD: "${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD in .env}"
volumes:
- nextcloud_db:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U $$POSTGRES_USER -d $$POSTGRES_DB"]
interval: 10s
timeout: 5s
retries: 20
start_period: 30s
logging:
driver: json-file
options:
max-size: "10m"
max-file: "3"
redis:
image: redis:8-alpine
container_name: nextcloud-redis
restart: unless-stopped
env_file:
- .env
command:
- sh
- -c
- exec redis-server --appendonly yes --requirepass "$$REDIS_HOST_PASSWORD"
volumes:
- nextcloud_redis:/data
healthcheck:
test: ["CMD-SHELL", "redis-cli -a $$REDIS_HOST_PASSWORD ping | grep -q PONG"]
interval: 10s
timeout: 5s
retries: 20
logging:
driver: json-file
options:
max-size: "10m"
max-file: "3"
app:
image: nextcloud:33-apache
container_name: nextcloud
restart: unless-stopped
depends_on:
db:
condition: service_healthy
redis:
condition: service_healthy
ports:
- "127.0.0.1:8080:80"
env_file:
- .env
environment:
POSTGRES_HOST: db
POSTGRES_DB: "${POSTGRES_DB:?set POSTGRES_DB in .env}"
POSTGRES_USER: "${POSTGRES_USER:?set POSTGRES_USER in .env}"
POSTGRES_PASSWORD: "${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD in .env}"
REDIS_HOST: redis
REDIS_HOST_PASSWORD: "${REDIS_HOST_PASSWORD:?set REDIS_HOST_PASSWORD in .env}"
NEXTCLOUD_ADMIN_USER: "${NEXTCLOUD_ADMIN_USER:?set NEXTCLOUD_ADMIN_USER in .env}"
NEXTCLOUD_ADMIN_PASSWORD: "${NEXTCLOUD_ADMIN_PASSWORD:?set NEXTCLOUD_ADMIN_PASSWORD in .env}"
NEXTCLOUD_TRUSTED_DOMAINS: "${NEXTCLOUD_HOST:?set NEXTCLOUD_HOST in .env}"
TRUSTED_PROXIES: "172.16.0.0/12"
OVERWRITEHOST: "${NEXTCLOUD_HOST:?set NEXTCLOUD_HOST in .env}"
OVERWRITEPROTOCOL: https
OVERWRITECLIURL: "${NEXTCLOUD_URL:?set NEXTCLOUD_URL in .env}"
PHP_MEMORY_LIMIT: "${PHP_MEMORY_LIMIT:-1024M}"
PHP_UPLOAD_LIMIT: "${PHP_UPLOAD_LIMIT:-10G}"
APACHE_BODY_LIMIT: 0
volumes:
- nextcloud_data:/var/www/html
healthcheck:
test: ["CMD-SHELL", "php -r '$$c=@fsockopen(\"127.0.0.1\",80,$$e,$$s,5); exit($$c ? 0 : 1);'"]
interval: 30s
timeout: 10s
retries: 10
start_period: 120s
logging:
driver: json-file
options:
max-size: "10m"
max-file: "3"
cron:
image: nextcloud:33-apache
container_name: nextcloud-cron
restart: unless-stopped
entrypoint: /cron.sh
depends_on:
app:
condition: service_healthy
env_file:
- .env
volumes:
- nextcloud_data:/var/www/html
logging:
driver: json-file
options:
max-size: "10m"
max-file: "3"
volumes:
nextcloud_db:
name: nextcloud_db
nextcloud_redis:
name: nextcloud_redis
nextcloud_data:
name: nextcloud_data
Nextcloud 只监听 127.0.0.1:8080。PostgreSQL 和 Redis 没有端口映射,只能从 Compose 网络访问。TRUSTED_PROXIES 仅信任 Docker 私网地址,不要填写 0.0.0.0/0;如果你的 Docker 地址池不同,应先用 docker network inspect 查到实际网段再修改。
启动前检查 Compose 展开结果:
docker compose config >/dev/null
docker compose pull
docker compose up -d
docker compose ps
docker compose logs --tail=150 app db redis
首次初始化可能需要几分钟。服务稳定后检查本机入口:
curl -I http://127.0.0.1:8080/status.php
sudo ss -lntp | grep ':8080'
监听地址必须是 127.0.0.1:8080,不能是 0.0.0.0:8080。
编辑 /etc/caddy/Caddyfile:
cloud.example.com {
encode zstd gzip
request_body {
max_size 10GB
}
reverse_proxy 127.0.0.1:8080 {
transport http {
read_timeout 1h
write_timeout 1h
}
}
}
验证并重载:
sudo caddy validate --config /etc/caddy/Caddyfile
sudo systemctl reload caddy
curl -I https://cloud.example.com/status.php
Caddy 会传递 X-Forwarded-For、X-Forwarded-Host 和 X-Forwarded-Proto。Compose 中的可信代理和覆盖参数让 Nextcloud 正确生成 HTTPS 链接,避免登录循环、WebDAV 地址错误和命令行生成 HTTP URL。更复杂的 CDN、多域名和证书问题可以参考 VPS 用 Caddy 反向代理完全指南。
大文件上传同时受 Caddy、PHP、Apache、客户端超时和可用磁盘影响。把上限设为 10GB 不代表内存会一次占用 10GB,但磁盘必须留出上传临时文件、版本历史和回收站所需空间。
打开 https://cloud.example.com,使用 .env 中的管理员账号登录。因为 Compose 已传入数据库和管理员变量,首次启动通常会自动完成安装。
进入“管理设置 → 概览”,逐项检查安全和设置警告。再从容器内运行状态检查:
docker compose exec -u www-data app php occ status
docker compose exec -u www-data app php occ config:system:get trusted_domains
docker compose exec -u www-data app php occ config:system:get trusted_proxies
docker compose exec -u www-data app php occ config:system:get overwriteprotocol
不要为了消除警告直接关闭安全检查。域名、代理、缓存、后台任务或数据库索引告警应根据实际原因修复。
Nextcloud 的文件扫描、版本清理、通知和应用任务都依赖后台作业。默认 AJAX 只在用户访问页面时触发,不适合生产环境。
Compose 已运行独立 cron 容器,再将后台任务模式设置为 Cron:
docker compose exec -u www-data app php occ background:cron
docker compose logs --tail=100 cron
稍后在管理设置中确认“最后一次任务执行”时间持续更新。Cron 容器和 App 必须共享同一个 nextcloud_data 卷,否则配置和已安装应用不一致。
官方镜像会根据 REDIS_HOST 配置 Redis,但上线后仍要检查:
docker compose exec -u www-data app php occ config:system:get redis
docker compose exec redis sh -c 'redis-cli -a "$REDIS_HOST_PASSWORD" ping'
Redis 用于分布式缓存和事务文件锁,可减少多人同步时的数据库锁竞争。Redis 数据卷不是 Nextcloud 文件数据的替代品;即使 Redis 丢失,核心文件和数据库仍应能恢复,但同步中的锁和缓存会重建。
安装 Nextcloud 官方桌面客户端或 iOS/Android 客户端,服务器地址填写:
https://cloud.example.com
手机自动上传建议先新建专用目录,例如 Photos/Phone,并明确是否只在 Wi-Fi 和充电时上传。首次同步大量照片前检查 VPS 剩余空间、流量配额和版本保留策略。
WebDAV 地址通常是:
https://cloud.example.com/remote.php/dav/files/用户名/
启用双因素认证后,为不支持网页登录的 WebDAV 客户端创建应用密码,不要把主密码保存到旧软件中。
文件版本与回收站会明显放大磁盘占用。不要等磁盘满了才处理。查看当前空间和后台状态:
df -h
docker system df
docker compose exec -u www-data app php occ status
对照片很多的实例,预览图生成会消耗 CPU 和磁盘。只启用真正需要的预览类型,并在低峰期预生成。不要安装来源不明的缩略图脚本,也不要让多个程序直接修改 Nextcloud 数据目录。
如果外部程序写入了数据目录,Nextcloud 数据库不会自动知道这些文件。必须使用受支持的外部存储应用,或谨慎执行:
docker compose exec -u www-data app php occ files:scan --all
全量扫描在大量文件时可能运行很久,不应频繁作为同步方案。
完整备份至少包括 PostgreSQL 与 nextcloud_data 卷。数据库保存用户、分享、文件索引和应用状态;数据卷保存 config.php、应用、主题和实际文件。
先开启维护模式,避免备份过程中持续写入:
cd /opt/nextcloud
docker compose exec -u www-data app php occ maintenance:mode --on
备份 PostgreSQL:
mkdir -p backups
docker compose exec -T db sh -c \
'pg_dump -U "$POSTGRES_USER" -d "$POSTGRES_DB" --format=custom' \
> "backups/nextcloud-db-$(date +%F-%H%M).dump"
备份数据卷:
docker run --rm \
-v nextcloud_data:/source:ro \
-v "$PWD/backups:/backup" \
alpine sh -c 'tar -C /source -czf /backup/nextcloud-data.tar.gz .'
关闭维护模式:
docker compose exec -u www-data app php occ maintenance:mode --off
备份不能只留在同一台 VPS。将备份加密复制到对象存储或另一台服务器,并定期做恢复演练。可直接沿用 VPS 备份恢复演练教程 的异地校验流程。
恢复时先停止 App 和 Cron,恢复数据卷与数据库,再启动服务并执行:
docker compose exec -u www-data app php occ maintenance:repair
docker compose exec -u www-data app php occ db:add-missing-indices
docker compose exec -u www-data app php occ status
恢复验收要检查登录、文件下载、分享链接、历史版本、手机上传和 WebDAV,而不只是首页。
Nextcloud 只能按支持的主版本路径升级,不要跨多个大版本直接替换镜像。升级前查看官方发布说明、应用兼容性和可用磁盘,并完成数据库与数据卷备份。
同一主版本内更新:
cd /opt/nextcloud
docker compose config >/dev/null
docker compose pull
docker compose up -d
docker compose exec -u www-data app php occ upgrade
docker compose exec -u www-data app php occ status
docker compose logs --tail=200 app cron
升级后检查管理概览、后台任务、客户端同步、分享链接和数据库告警。如果新版本失败,停止写入,恢复旧镜像标签、数据库和数据卷。只把镜像标签改回去但不恢复已升级数据库,通常不能完成可靠回滚。
- 管理员使用长密码并启用双因素认证;
- 普通用户按最小权限分组,不共享管理员账号;
- PostgreSQL、Redis 和 8080 不暴露公网;
- 只安装维护活跃且确实需要的 Nextcloud 应用;
- 定期更新 Nextcloud、数据库、Redis、Caddy 和宿主系统;
- 对登录接口设置合理限速,但不要误伤 WebDAV 和同步客户端;
- 监控磁盘、inode、数据库备份、Cron 和 HTTPS;
- 离职或设备丢失时撤销会话和应用密码。
自托管代表你负责安全、容量和恢复,不代表文件天然不会丢。RAID、快照、版本历史和回收站都不能替代异地备份。
docker compose ps
curl -I http://127.0.0.1:8080/status.php
docker compose logs --tail=200 app
sudo journalctl -u caddy --no-pager -n 100
如果 App 仍在初始化或执行升级,Caddy 会暂时返回 502。持续失败时检查数据库、Redis、内存和数据卷权限。
确认 .env 中域名与真实入口一致,并检查:
docker compose exec -u www-data app php occ config:system:get trusted_domains
docker compose exec -u www-data app php occ config:system:get trusted_proxies
docker compose exec -u www-data app php occ config:system:get overwritehost
docker compose exec -u www-data app php occ config:system:get overwriteprotocol
CDN、Caddy 和 Nextcloud 不要配置互相冲突的 HTTP/HTTPS 跳转。可信代理也不能放宽为任意公网地址。
先确认 Caddy 的 max_size,再检查容器环境和 PHP:
docker compose exec app php -i | grep -E 'upload_max_filesize|post_max_size|memory_limit'
docker compose logs --tail=200 app
df -h
如果上传到固定大小就失败,通常是代理或 PHP 限制;如果传到一半超时,继续检查客户端、反向代理超时、磁盘和网络稳定性。
docker compose ps cron
docker compose logs --tail=200 cron
docker compose exec -u www-data app php occ background:cron
确认 Cron 与 App 使用相同镜像版本和数据卷。升级 App 后也要同步更新 Cron 容器。
先检查 Redis 与数据库是否健康,不要直接删除数据库里的锁记录:
docker compose ps redis db
docker compose logs --tail=200 redis db app
docker compose exec redis sh -c 'redis-cli -a "$REDIS_HOST_PASSWORD" ping'
只有在确认没有客户端写入、已备份数据库且理解影响时,才进一步处理残留锁。
- Nextcloud 只监听
127.0.0.1:8080,数据库和 Redis 未暴露公网 - 域名、HTTPS、可信代理和覆盖 URL 配置一致
- 独立 Cron 容器正常运行,后台任务时间持续更新
- Redis 缓存和事务文件锁正常
- 桌面客户端、手机自动上传和 WebDAV 已测试
- 10GB 或所需大小的上传链路完成实际测试
- PostgreSQL 与 Nextcloud 数据卷都有异地备份
- 已做恢复演练并验证分享、版本、客户端同步
- 升级前固定版本并备份,升级后检查应用兼容性
完成这些步骤后,这套 Nextcloud 才不只是“网页能打开”,而是一套具备 HTTPS、缓存、定时任务、移动端同步、版本管理和可恢复能力的 VPS 私有云盘。
