很多早期部署的 Nextcloud 都采用子目录模式。
例如:
https://<MAIN_DOMAIN>/nextcloud/
这种部署方式简单,而且可以和其他服务共用同一个主域名。
但随着 Nextcloud 和现代浏览器安全机制不断演进,子目录架构会逐渐暴露一些限制。
这次迁移最终把正式地址从:
https://<MAIN_DOMAIN>/nextcloud/
迁移为:
https://<CLOUD_DOMAIN>/
也就是独立子域名根路径部署。
整个过程涉及:
DNS
TLS
Nginx
Apache
Nextcloud
Cookie
WebDAV
CalDAV
CardDAV
.well-known
客户端
兼容跳转
Security Scan
并不是简单地修改一个域名字符串。
一、为什么要从子目录迁移到子域名
最直接的原因之一是:
__Host- Cookie
现代浏览器支持:
__Host-
Cookie 前缀。
它要求:
Secure
Path=/
不能设置 Domain
而如果 Nextcloud 长期运行在:
/nextcloud/
路径下,就很难完全满足:
Path=/
这一要求。
安全扫描因此一直存在一个:
__Host-Prefix
相关提示。
这意味着即使 HTTPS、HSTS、Content-Security-Policy 等其他配置全部正常,子路径本身仍然会限制部分 Cookie Hardening。
因此最终决定把 Nextcloud 放到独立子域名根目录。
二、DNS:先创建新的正式入口
首先创建:
<CLOUD_DOMAIN>
对应 DNS A 记录:
<CLOUD_DOMAIN> → <PUBLIC_IP>
TTL 采用普通值,例如:
3600
在真正修改 Web Server 之前,先确认:
DNS 已经能够正确解析
这样后续 TLS 和反向代理才能正常验证。
三、TLS:把新域名加入现有多域名证书
原系统使用一张包含多个服务域名的证书。
因此没有单独创建一套证书,而是把:
<CLOUD_DOMAIN>
加入现有 SAN 列表。
Certbot 命令形式类似:
certbot certonly --standalone \
-d <MAIN_DOMAIN> \
-d <SERVICE_1_DOMAIN> \
-d <SERVICE_2_DOMAIN> \
-d <CLOUD_DOMAIN> \
--agree-tos \
--email <ADMIN_EMAIL> \
--cert-name <CERT_NAME> \
--expand
证书成功签发后,不能只看 Certbot 输出。
还需要检查:
Certbot live 目录中的证书
和:
Nginx 实际加载的证书
是否已经是同一个新证书。
最终确认:
Serial 一致
SAN 中包含 <CLOUD_DOMAIN>
四、反向代理主机与应用主机保持职责分离
整个架构中存在两个角色。
前端:
<REVERSE_PROXY_HOST>
运行:
Nginx
Certbot
后端:
<NEXTCLOUD_HOST>
运行:
Apache
PHP-FPM
Nextcloud
Database
这两个角色在迁移中必须严格区分。
新域名首先在反向代理主机建立独立 Nginx Server Block。
五、没有把新域名永久塞进旧 Server Block
最开始可以临时让:
server_name <MAIN_DOMAIN> <CLOUD_DOMAIN>;
共同命中同一个 Server Block。
但最终没有保留这种结构。
而是为:
<CLOUD_DOMAIN>
建立独立配置。
形式类似:
server {
listen 443 ssl http2;
server_name <CLOUD_DOMAIN>;
client_max_body_size 1G;
access_log /var/log/nginx/cloud.access.log;
error_log /var/log/nginx/cloud.error.log;
ssl_certificate <FULLCHAIN_PATH>;
ssl_certificate_key <PRIVATE_KEY_PATH>;
ssl_protocols TLSv1.2 TLSv1.3;
location / {
proxy_pass http://<NEXTCLOUD_LAN_IP>;
proxy_http_version 1.1;
proxy_hide_header Upgrade;
proxy_hide_header Connection;
proxy_set_header Host $http_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 https;
proxy_set_header X-Forwarded-Host $http_host;
proxy_set_header X-Forwarded-Port 443;
proxy_set_header X-Nginx-Proxy true;
proxy_redirect off;
proxy_connect_timeout 3600s;
proxy_send_timeout 3600s;
proxy_read_timeout 3600s;
}
}
这种结构和其他独立服务保持一致。
也避免以后维护:
主域名
时意外影响:
Nextcloud 子域名
六、没有额外覆盖 Nextcloud 自己的 Referrer-Policy
旧主域名 Nginx 配置中曾经存在类似:
add_header Referrer-Policy strict-origin-when-cross-origin always;
但新的 Nextcloud Server Block 没有继续加。
原因是 Nextcloud 自己已经发送:
Referrer-Policy: no-referrer
如果 Nginx 再加一层,就可能产生重复 Header。
因此这里选择让 Nextcloud 自己控制应用级 Security Header。
七、Apache 从 Alias 架构转为独立 VirtualHost
后端最初采用:
Alias /nextcloud "<NEXTCLOUD_ROOT>/"
也就是说:
http://<BACKEND>/nextcloud/
才对应 Nextcloud。
迁移时,在原有 Apache 配置基础上增加独立 VirtualHost:
<VirtualHost *:80>
ServerName <CLOUD_DOMAIN>
DocumentRoot <NEXTCLOUD_ROOT>
SetEnvIf X-Forwarded-Proto https HTTPS=on
<Directory <NEXTCLOUD_ROOT>/>
Options +FollowSymlinks
AllowOverride All
Require all granted
</Directory>
ErrorLog <APACHE_CLOUD_ERROR_LOG>
CustomLog <APACHE_CLOUD_ACCESS_LOG> combined
</VirtualHost>
随后执行:
apache2ctl configtest
确认:
Syntax OK
再通过:
apache2ctl -S
确认:
<CLOUD_DOMAIN>
已经作为独立 NameVirtualHost 生效。
八、Nextcloud 增加新 Trusted Domain
Nextcloud 自己也必须允许新的 Host。
例如:
trusted_domains:
<BACKEND_LAN_IP>
<MAIN_DOMAIN>
<CLOUD_DOMAIN>
这里没有立即删除旧域名。
因为旧地址仍然需要保留一段时间作为兼容入口。
九、Nextcloud 正式 Canonical URL 改为新域名
随后更新:
sudo -E -u <WEB_USER> php occ \
config:system:set overwrite.cli.url \
--value='https://<CLOUD_DOMAIN>/'
再设置:
sudo -E -u <WEB_USER> php occ \
config:system:set htaccess.RewriteBase \
--value='/'
然后:
sudo -E -u <WEB_USER> php occ maintenance:update:htaccess
最终:
overwrite.cli.url = https://<CLOUD_DOMAIN>/
htaccess.RewriteBase = /
十、没有设置 overwritehost / overwriteprotocol / overwritewebroot
这次迁移并没有额外设置:
overwritehost
overwriteprotocol
overwritewebroot
原因是前端 Nginx 已经正确提供:
Host
X-Forwarded-Proto
X-Forwarded-Host
X-Forwarded-Port
Nextcloud 可以正确识别真实请求。
如果反向代理本身已经配置正确,再强制写:
overwritehost
反而会增加以后配置复杂度。
十一、.well-known 全部迁移到新地址
原有:
CalDAV
CardDAV
WebFinger
NodeInfo
也需要同步调整。
Apache 中最终类似:
Redirect 301 /.well-known/carddav \
https://<CLOUD_DOMAIN>/remote.php/dav
Redirect 301 /.well-known/caldav \
https://<CLOUD_DOMAIN>/remote.php/dav
Redirect 301 /.well-known/webfinger \
https://<CLOUD_DOMAIN>/index.php/.well-known/webfinger
Redirect 301 /.well-known/nodeinfo \
https://<CLOUD_DOMAIN>/index.php/.well-known/nodeinfo
之后从外部验证:
/.well-known/carddav
→ 301
→ https://<CLOUD_DOMAIN>/remote.php/dav
其他几个入口也得到正确跳转。
十二、旧 Alias 最终从后端彻底删除
迁移初期,为了避免一次变化过多,旧:
Alias /nextcloud "<NEXTCLOUD_ROOT>/"
仍然暂时存在。
当新域名经过完整验证之后,这条 Alias 被删除。
此后:
https://<CLOUD_DOMAIN>/nextcloud/
应该直接返回:
404
这一步很重要。
它意味着:
新域名只有一个正式根路径
而不是:
/
和
/nextcloud/
两套入口同时存在。
十三、旧公网地址没有立即删除,而是由 Nginx 保留 301
虽然后端已经删除旧 Alias,但前端反向代理仍然保留兼容跳转。
在:
<MAIN_DOMAIN>
对应 Server Block 中加入:
location = /nextcloud {
return 301 https://<CLOUD_DOMAIN>/;
}
location ^~ /nextcloud/ {
rewrite ^/nextcloud/(.*)$ \
https://<CLOUD_DOMAIN>/$1 permanent;
}
这样:
https://<MAIN_DOMAIN>/nextcloud/
会变成:
301
https://<CLOUD_DOMAIN>/
而:
https://<MAIN_DOMAIN>/nextcloud/status.php
则变成:
301
https://<CLOUD_DOMAIN>/status.php
也就是保留后续路径。
十四、为什么兼容跳转只放在反向代理
兼容逻辑没有继续保留在后端 Apache。
这是有意设计。
最终结构变成:
公网旧 URL
↓
前端 Nginx 301
↓
新 URL
↓
后端 Apache
↓
Nextcloud
而不是:
前端跳一次
后端再判断一次旧路径
Nextcloud 再处理一次
这样职责更清晰。
十五、客户端迁移期间出现 Cookie 和 Session 错误
正式切换后,需要在很多位置更新地址:
浏览器书签
密码管理器
桌面客户端
手机客户端
WebDAV
CalDAV
CardDAV
自动化脚本
旧登录 Session
因此迁移后的短时间内,Nextcloud 日志出现:
Request does not pass strict cookie check
来自多个桌面客户端。
也出现:
Could not find the session token to renew
异常类型:
OC\Authentication\Exceptions\ExpiredTokenException
主要发生在:
Tasks
Calendar
/
login
几个请求上。
这些错误高度集中在很短时间窗口内。
随着浏览器重新登录、客户端服务器地址更新,这些错误停止继续产生。
因此最终确认它们属于客户端迁移阶段的旧 Cookie / Session 残留,而不是服务器配置错误。
十六、Cookie 变化是这次架构迁移最关键的验证之一
新域名根路径部署生效后,返回的 Cookie 类似:
__Host-nc_sameSiteCookielax=true
Path=/
Secure
HttpOnly
以及:
__Host-nc_sameSiteCookiestrict=true
Path=/
Secure
HttpOnly
这说明:
__Host-
Cookie 前缀已经真正满足规范要求。
此前因为:
/nextcloud/
子路径结构无法完全满足的条件,现在已经消失。
十七、Security Scan 最终从 A 变为 A+
旧地址进行 Nextcloud Security Scan 时,整体已经达到:
A
但仍存在:
__Host-Prefix
红色提示。
迁移后曾经一度错误地扫描:
https://<CLOUD_DOMAIN>/nextcloud/
因此仍然看到旧问题。
后来改为真正的:
https://<CLOUD_DOMAIN>/
重新扫描。
最终结果为:
A+
所有主要 Hardening 项全部绿色,包括:
__Host-Prefix
这也从外部验证了新的根域名架构已经正确生效。
十八、最终架构
迁移结束后的结构可以概括为:
https://<CLOUD_DOMAIN>/
↓
正式 Nextcloud
https://<CLOUD_DOMAIN>/nextcloud/
↓
404
https://<MAIN_DOMAIN>/nextcloud/
↓
301
↓
https://<CLOUD_DOMAIN>/
也就是说:
新域名根路径 = 唯一正式入口
旧域名子路径 = 临时兼容入口
新域名旧子路径 = 不存在
这是一个非常干净的状态。
十九、旧兼容地址不会永久存在
301 兼容跳转只是迁移期措施。
需要给:
浏览器
Desktop Client
Mobile Client
WebDAV
CalDAV
CardDAV
历史文档
自动化脚本
留出更新时间。
等迁移窗口结束之后,再从:
<REVERSE_PROXY_HOST>
删除:
location = /nextcloud
和:
location ^~ /nextcloud/
两段规则。
到那时:
旧公网地址
才算真正退役。
二十、这不是一次简单的“换域名”
整个过程实际上完成了:
子路径部署
↓
独立子域名根路径部署
旧 Cookie 结构
↓
__Host- Cookie
Apache Alias
↓
独立 VirtualHost
多入口
↓
单一 Canonical URL
后端兼容逻辑
↓
前端统一 301
Security Scan A
↓
Security Scan A+
因此它更准确的描述应该是:
一次 Nextcloud URL、反向代理、安全 Cookie 和 Web 服务架构的整体重构。
而不仅仅是“把地址换了一个域名”。