悠悠时光整站迁移实操:六步完成纯 PHP 个人站搬家

去年我把「悠悠时光」从一台老机器搬到了现在的服务器。这套站点是纯 PHP、零前端框架的个人内容站(博客 / 投资 / 读书 / 健康 / 老黄历五个模块),由 1Panel 管理 nginx(openresty) + php-fpm + MySQL。迁移本身不复杂,但架不住细节多、坑也多——尤其是那个让我白折腾半天的 404。

所以我把整套流程沉淀成下面这六步。照着走,你基本能避开我踩过的所有坑。

一句话定性:整站迁移 = 代码 + 数据库 + 服务器配置,三件套一个都不能少。 只搬代码不搬库,打开全是空;只搬库不搬配置,路由页全 404。

第一步:从 1Panel 面板导出 MySQL 数据库

登录源服务器的 1Panel → 进入「网站」或直接用「数据库」模块,打开 phpMyAdmin(或 1Panel 自带的 SQL 管理),选中本站库。

导出时关键选项:

  • 导出内容:选「结构和数据」(光结构没内容,光数据没表);
  • 字符集:utf8mb4(后面建库也要对应,否则 emoji 会被截断);
  • 导出格式:SQL,存成一个 .sql 文件备用。

小提示:如果面板不支持一键导出,也可以临时传一个一次性导出脚本到 web 根跑一下——但 phpMyAdmin 是最省事的正路。

第二步:从 1Panel 面板下载整站源代码

用 1Panel 的文件管理或 SFTP,把整个 web 根目录下载到本地。一个标准站点根应该长这样(本站是 7 个条目):

index.php          # 前端控制器(路由入口,必须在根)
admin/             # 后台
app/              # 函数库(数据库连接等敏感代码都在这里)
assets/           # CSS / JS / 字体等静态资源
views/            # 页面模板
uploads/          # 媒体库真实文件(图片/音频/视频)
404.html          # 自定义错误页

⚠ 最容易漏的是 uploads/:那是媒体库的真实二进制文件,不走数据库。只下代码不下它,搬过去之后所有图片、音频、视频全变碎图。另外注意别漏掉隐藏文件(如 .htaccess 之类,如果有的话)。

第三步:在新的 1Panel 面板中新建同名数据库并导入

到目标服务器的 1Panel:

  1. 新建一个数据库,名字和源站一致;
  2. 字符集必须显式选 utf8mb4——很多面板默认 utf8(3 字节),遇到 emoji 直接报错或截断;
  3. 把第一步导出的 .sql 导入进去。

⚠ 这一步是迁移后 404 的隐藏雷区:如果导入不完整、或者中途报错中断,数据库里表/数据残缺,站点路由查询就会返回空,表现和「nginx 没配 try_files」几乎一模一样(都是路由页全 404)。所以导入后务必确认 17 张表都在、没有报错,再往下走。我上次就是栽在这儿,先去查了半天 nginx,最后发现是库没导全。

第四步:上传整站源代码到新的服务器网站根目录中

在目标 1Panel 新建一个 PHP 运行环境类型的网站(注意:别选「静态网站」,否则 PHP 文件不会被解析,/admin/login.php 打开会是源码或 404)。

然后把第二步下载的源码整目录上传到新站点的 web 根,保持结构不变——尤其 index.php 必须待在根目录。

⚠ 上传建议用「整目录同步、只传不删」的方式,避免误删目标机已有文件;uploads/ 目录要存在且 php-fpm 能写(媒体库上传依赖它)。

第五步:在 config.php 文件中更改数据库连接信息

打开 site/app/config.php,改这一组:

define('DB_HOST', '这里填目标机的数据库地址');
define('DB_PORT', '3306');
define('DB_NAME', 'myuutime2026');
define('DB_USER', '数据库账号');
define('DB_PASS', '数据库密码');

⚠ 容器环境的大坑:如果你的 MySQL 跑在 Docker 里,绝对不要填 127.0.0.1——容器内的回环和宿主机的回环不是一回事,填了会直接 Connection refused。要用容器名(如 1Panel-mysql-xxxx)或 Docker 内网 IP。

本站为了方便换机器,其实还支持用环境变量 YT_DB_HOST / YT_DB_PORT / YT_DB_NAME / YT_DB_USER / YT_DB_PASS 覆盖配置文件。也就是说,换服务器时可以不碰 config.php 文件本体,只注入环境变量就行,更干净。

第六步:复制 nginx 设置代码到新的服务器 nginx 网站配置文件中

这是最容易翻车的一步。把下面这段配置复制进目标站点(1Panel 里是「网站 → 你的站点 → 配置文件」),保存后重载 openresty:

server {
    listen 80;
    server_name 你的域名;

    root  /path/to/site;        # 指向解压后的 site/ 目录,index.php 在根
    index index.php index.html;
    charset utf-8;

    # ① 路由核心:未命中静态文件 → 交给前端控制器
    location / {
        try_files $uri $uri/ /index.php?$query_string;
    }

    # ② 函数库对外不可见(防 app/config.php 被下载泄露数据库密码)
    location ^~ /app/ {
        deny all;
    }

    # ③ PHP 解析(正则,优先级高于 location /)
    location ~ [^/]\.php(/|$) {
        fastcgi_split_path_info ^(.+\.php)(/.+)$;
        fastcgi_pass   unix:/run/php/php8.2-fpm.sock;   # 按实际 PHP 版本/监听方式改
        include        fastcgi_params;
        fastcgi_param  SCRIPT_FILENAME $document_root$fastcgi_script_name;
        fastcgi_param  PATH_INFO $fastcgi_path_info;
        fastcgi_index  index.php;
    }

    # ④ 媒体库 uploads 加固(防后门 .php 被执行)
    location ~* ^/uploads/.*\.(?:php|phtml|phps|phar|asp|aspx|jsp|html?|svg|xml|htaccess|user\.ini)$ { deny all; }
    location /uploads/ { try_files $uri =404; access_log off; expires 30d; }
}

关键就一行:location / { try_files $uri $uri/ /index.php?$query_string; }。

⚠ 这就是我说的「迁移头号坑」:如果目标机的 nginx 没把 /invest、/calendar、/reading、/health、/post/xxx 这些路径交给 index.php,它们就会全部 404,而 /admin/login.php、/assets/* 这种真实文件页却正常——口诀是「能开的都是实体文件页、打不开的都是路由页 → 查 try_files」。

第 ② 条 ^~ /app/ { deny all; } 也别省:否则别人能直接 https://你的域名/app/config.php 把数据库密码扒走。

迁移后验收清单

改完用 curl 在目标机验一遍(把域名换成你的):

curl -s -o /dev/null -w '%{http_code}\n' https://你的域名/
curl -s -o /dev/null -w '%{http_code}\n' https://你的域名/invest
curl -s -o /dev/null -w '%{http_code}\n' https://你的域名/calendar
curl -s -o /dev/null -w '%{http_code}\n' https://你的域名/reading
curl -s -o /dev/null -w '%{http_code}\n' https://你的域名/health
curl -s -o /dev/null -w '%{http_code}\n' https://你的域名/app/config.php   # 期望 403

前五个都回 200、最后一个回 403,就算稳了。

写在最后

纯 PHP 个人站的好处是没有构建步骤、没有依赖地狱,整个站点就是一个目录 + 一个库。只要把「代码、数据库、nginx 配置」这三样原样搬到新机器,它就能原样跑起来。真正费时间的从来不是搬,而是那些「看起来像 A 实际是 B」的坑——比如 404,既可能是 nginx 没配 try_files,也可能是数据库没导全。

把这份清单收好,下次搬家你大概十分钟就能搞定。