

最近我想把网站服务器换成更便宜的 VPS,今天折腾了一下,写篇文章分享一下。
购买服务器
如果你对云服务器、VPS 没什么了解,可以看看我之前发的文章:
我的博客服务器
我现在的博客服务器厂商是 Vultr,1 CPU 1 GB 内存,每月 5 美元,一年 60 美元。今天我买了 RackNerd 的 VPS,配置翻倍了,每年 18 美元,一年可以省 300 块钱(本文非推广)。我想用这个 VPS 测试一番,可以的话就换到这个 VPS 上。
云服务器可以在购买时选择预装了 WordPress 的系统,但是 VPS 就得自己手动安装了。借着换服务器的机会,我也写篇文章讲下搭建一个 WordPress 博客的步骤吧。
在购买服务器时,默认的操作系统都是 Linux 系统(因为 Windows 要加钱),我选择的是 Debian 12。购买成功后,主机商会通过邮件把 root 密码发给你,你可以使用 SSH 客户端(如 MobaXterm)登录到服务器。
Linux 服务器的操作需要使用命令行,如果有不懂的地方就问 AI 吧,很方便。下面的内容我也都是问 AI 的。
配置新系统
查看系统版本
lsb_release -a
输出如下:
Distributor ID: Debian
Description: Debian GNU/Linux 12 (bookworm)
Release: 12
Codename: bookworm
更新软件包
apt update
apt upgrade
执行完毕后,再执行 reboot
重启系统。
修改主机名
默认的主机名是随机的,我要修改成自定义的主机名。
sudo hostnamectl set-hostname saber
该设置立即生效,不过 shell 里的提示没有立即变化,在重新登入后才会变成新的。
修改 SSH 端口
编辑配置文件:nano /etc/ssh/sshd_config
把原本的 #Port 22
取消注释,改为 Port 2222
,保存退出。
重启 SSH 服务:systemctl restart ssh
。
之后退出当前会话,修改端口号,重新登入。
使用私钥登录
我已经在本机创建了一对密钥。
在远程主机上编辑配置文件:
cd ~/.ssh
nano ~/.ssh/authorized_keys
复制公钥的内容,粘贴进来保存。
然后设置权限:
chmod 700 ~/.ssh
chmod 600 ~/.ssh/authorized_keys
编辑配置文件:nano /etc/ssh/sshd_config
在文件的中部,有一行已启用的 PasswordAuthentication yes
,把 yes
改为 no
。
然后新增一行:PubkeyAuthentication yes
。
这样会禁止使用密码登录,并要求公钥认证。
重启 SSH 服务:systemctl restart ssh
。
在 SSH 客户端里(我的是 MobaXterm)里勾选私钥登录,选择对应的私钥文件,并重新登录。
使用 ufw 配置防火墙
apt install ufw
# 允许端口:
# 2222 是修改后的 SSH 登录端口,请根据实际情况自行修改
ufw allow 2222/tcp
ufw allow 80/tcp
ufw allow 443/tcp
# 拒绝其他入站连接:
ufw default deny incoming
ufw default allow outgoing
#启用防火墙:
ufw enable
安装 lrzsz
apt install lrzsz -y
# 验证
rz --version
安装 lrzsz 之后可以使用 rz 命令上传本地文件,sz 命令下载远程文件。特别是上传文件很方便。
不过在 MobaXterm 里,不要直接使用 rz、sz 命令,而是按 ctrl 或 shift 的同时点击鼠标右键,选择对应的操作:
配置网站运行环境
如果你打算使用域名来访问网站,可以先注册一个域名,并将其解析到服务器的 IP。
安装 PHP
安装 PHP 和 PHP-FPM:
apt install php php-fpm
安装常用的 PHP 扩展模块:
apt install php-cli php-mbstring php-mysql php-gd php-curl php-xml php-zip
验证安装是否成功:
php -v
我看到的版本是:PHP 8.2.28 (cli) (built: Mar 13 2025 18:21:38) (NTS)
删除 Apache
列出运行中的服务:
systemctl list-units --type=service --state=running
我看到有两个 Apache 的服务,它们是正在运行的,占用了 80 端口。由于我打算使用 Nginx,所以需要禁止这两个服务,否则 Nginx 无法启动。后来我干脆删了 Apache。
systemctl stop apache2.service
systemctl stop apache-htcacheclean.service
apt remove --purge apache2 apache2-utils apache2-bin
apt autoremove
安装 Nginx
apt install nginx
systemctl start nginx
systemctl status nginx
之后在浏览器里访问入服务器的 ip 地址,即可看到 Nginx 的欢迎页面。
安装 MariaDB 数据库
MariaDB 是一个兼容 MySQL 的数据库,现在我需要安装它供 WordPress 使用。
apt install mariadb-server
systemctl status mariadb
运行安全脚本以提高 MariaDB 的安全性,这会引导你设置数据库的 root 密码和其他安全配置:
mysql_secure_installation
接下来创建给 WordPress 使用的数据库。你需要先想好数据库的字,比如我起名为 testsaberlove
。
然后运行 mysql -u root -p
并输入密码,进入数据库的命令行(在 Debian 12 上,如果你是 root 用户,不需要输入密码,直接按回车即可)。
然后输入这条命令并回车:
CREATE DATABASE testsaberlove;
这样就创建了 testsaberlove
数据库。然后输入 exit;
退出命令行即可。
获取 WordPress 安装包
假设我把网站文件保存到 /var/www 目录里(在安装 Nginx 时会自动创建这个目录,Nginx 的默认欢迎页面也在这里面)。
cd /var/www
wget https://wordpress.org/latest.tar.gz
tar -xzvf latest.tar.gz
这会下载 WordPress 并解压到 wordpress 文件夹里。可以把文件夹名字改为网站的域名,例如:
mv wordpress test.saber.love
你需要把 test.saber.love 换成你自己的域名。
然后授予 Nginx 用户的访问权限:
chown -R www-data:www-data /var/www/test.saber.love
chmod -R 755 /var/www/test.saber.love
创建 Nginx 配置文件
接下来创建该网站的 Nginx 配置文件。Nginx 的配置目录是 /etc/nginx/,它里面有两种方式可以存放网站的配置文件。
第一种是把配置文件放到 conf.d 目录里,并且文件的后缀名需要是 conf,例如 test.saber.love.conf
。
第二种是把配置文件放到 sites-available 目录里,然后使用 ln -s
在 sites-enabled 目录里创建一个软链接(快捷方式)。这样便于管理,原理是在 sites-available 目录里存储所有网站的配置文件,并把需要启用的文件在 sites-enabled 目录里创建软链接。如果不想启用某个网站了,只需要在 sites-available 目录里删除对应的软链接即可。
目前使用第二种方式似乎更为规范。
创建配置文件:
nano /etc/nginx/sites-available/test.saber.love
内容如下,这样当访问 test.saber.love 时,就会指向 /var/www/test.saber.love 目录:
server {
listen 80;
server_name test.saber.love;
root /var/www/test.saber.love;
index index.php index.html index.htm;
location / {
try_files $uri $uri/ /index.php?$args;
}
location ~ \.php$ {
include snippets/fastcgi-php.conf;
fastcgi_pass unix:/run/php/php-fpm.sock;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
}
location ~ /\.ht {
deny all;
}
}
启用该配置:
ln -s /etc/nginx/sites-available/test.saber.love /etc/nginx/sites-enabled/
移除默认配置(执行一次就可以了):
rm /etc/nginx/sites-enabled/default
测试 Nginx 的配置文件是否存在问题,如果没问题的话就重启 Nginx 服务:
nginx -t
systemctl reload nginx
之后在浏览器里访问你的域名(别忘了把域名解析到服务器),应该就可以看到 WordPress 的引导页面了:
配置 SSL 证书
如果你想尽快访问网站,可以先跳过此步骤,以后再处理。
目前我们还没有支持 https,所以只能通过 http 访问。有多种方法支持 https,比如我套了 Cloudflare 作为 CDN,可以使用它提供的 SSL 证书。如果不打算套 CDN,可以在服务器上安装 Let's Encrypt 为自己的网站创建 SSL 证书。
我现在使用的是 Cloudflare 的源服务器证书。为一个域名创建源服务器证书后,所有子域名都可以使用这个证书,这样就不必为每个子域名单独配置证书了。而且就算子域名分布于多个不同的服务器上,也只需要使用这一个证书,不需要为不同服务器生成单独的证书。而且源服务器证书的有效期是 15 年,不用担心过期和续期的问题,比 Let's Encrypt 爽多了。
创建源服务器证书的过程可以查看此文章:
也是用上 Cloudflare 了
当你有了 SSL 证书之后,可以编辑该网站的 Nginx 配置文件,添加 SSL 部分,格式如下:
# SSL configuration
listen 443 ssl;
ssl_certificate /etc/nginx/cert/saber.love.pem;
ssl_certificate_key /etc/nginx/cert/saber.love.key;
保存后重启 Nginx 服务:systemctl reload nginx
,之后在浏览器里访问域名时,就会自动跳转到 https 网址。
禁止通过 IP 访问网站
默认情况下,在浏览器里输入服务器 IP 可以访问网站。在我们没有配置网站时,会显示 Nginx 默认的欢迎页面。当我配置了一个网站时,我发现它会直接显示这个网站。
我不希望可以通过 IP 访问网站,所以我禁止了此行为。你可以根据自己的需要选择是否执行。
创建 default 配置:
nano /etc/nginx/sites-available/default
内容如下:(你需要把 SSL 证书的路径修改为自己的实际路径,其他的不用改)
server {
listen 80 default_server;
listen [::]:80 default_server;
listen 443 ssl default_server;
listen [::]:443 ssl default_server;
# 使用 Cloudflare Origin CA 证书(即使是默认服务器,也需要 SSL)
ssl_certificate /etc/nginx/cert/saber.love.pem;
ssl_certificate_key /etc/nginx/cert/saber.love.key;
server_name _; # 捕获所有未匹配的 Host(包括 IP 访问)
return 403; # 禁止访问
# 或者重定向到你的域名:
# return 301 https://your_domain.com$request_uri;
}
这样只允许使用配置过的域名访问网站。
启用该配置:
ln -s /etc/nginx/sites-available/default /etc/nginx/sites-enabled/
重启 Nginx:
systemctl reload nginx
这样,通过 IP 访问时会返回 403 页面:
安装 WordPress
经过前面的步骤,现在已经可以通过域名访问 WordPress 了。默认会显示安装向导,其中有个重要的步骤就是连接数据库:
前面我们已经创建了数据库 testsaberlove
,数据库用户名是 root,密码就是数据库的密码。下面的不用改,然后点击“提交”按钮即可。
提示:数据库必须提前手动创建,因为 WordPress 自己是不会创建数据库的。
然后跟着提示走就行,很快就可以登入 WordPress 后台了。此时访问域名就可以看到 WordPress 的默认页面了:
迁移网站
因为我要把网站换到新的服务器上,所以需要迁移网站。这部分只是记录,不需要看。
复制网站文件
把文件复制到新服务器上有多种方式,一种简单的方法是先把文件打包,放到网站下面,这样可以通过网址访问。然后在新服务器上使用 wget 下载即可。
# 打包网站目录
tar -zcvf www.tar.gz /var/www
# 移动到某个网站里
mv www.tar.gz /var/www/saber.love
# 在新服务器上下载。-continue 表示断点续传,--limit-rate=1m 限制下载速度为 1 MB/s,避免占满原服务器的带宽
wget --continue --limit-rate=1m https://saber.love/www.tar.gz
之后解压出来,并赋予 Nginx 用户权限:
# 把文件提取到 /var/www 目录,并移除 tar 文件里原本的目录,以免生成嵌套的文件夹
# 也可以把 tar 文件移动到 / 目录,然后直接解压,这样也不会产生嵌套的文件夹
tar -zxvf www.tar.gz -C /var/www --strip-components=2
chown -R www-data:www-data /var/www
chmod -R 755 /var/www
另一种复制方式是使用 scp 或 rsync,原理是通过 SSH 登录到原服务器上然后传输文件,这样可以直接复制指定目录,不需要在原服务器上创建压缩包。而且 rsync 还支持增量备份,即多次执行时,可以只复制新增的文件。但是我的原服务器使用了私钥登录,而这些方式要使用私钥登录会比较麻烦,需要安装别的软件包,我懒得搞,就选择了简单的 wget 方式。
迁移数据库
执行下面的命令导出指定数据库:
mysqldump -u root -p wordpress > wordpress_db_backup.sql
root 是数据库用户名,wordpress 是数据库名称,可以根据实际情况修改。
将其复制到新服务器上之后就可以进行恢复了。
由于导出的 sql 文件里不包含创建数据库的语句,所以我们需要先创建对应的数据库:
# 进入数据库命令行
mysqldump -u root -p
# 创建数据库,名字可以根据需要修改
CREATE DATABASE wordpress;
# 如果这个数据库的用户不是 root,还需要创建同名的用户,并把密码设置为旧密码。此处省略
# 退出命令行
EXIT;
导入数据库:
mysql -u root -p wordpress < /home/user/wordpress_db_backup.sql
root 是数据库的用户,wordpress 是数据库名,最后是 sql 文件的路径。
如果要验证是否导入成功,可以进入数据库命令行,查看表结构是否正确:
mysql -u wordpress_user -p
USE wordpress;
SHOW TABLES;
WordPress 网站的 wp-config.php 里保存着该站点的数据库配置:
define('DB_NAME', 'wordpress');
define('DB_USER', 'wordpress_user');
define('DB_PASSWORD', 'your_secure_password');
define('DB_HOST', 'localhost');
在上面的恢复过程里,创建同名数据库、用户名、密码就是为了符合这里的原配置,这样网站可以直接运行。当然,如果新的配置和原来不同,我们也可以修改这里的配置。
复制 Nginx 配置文件
把原服务器上的 Nginx 配置文件复制过来,视情况进行修改。之后重启 Nginx 服务:
nginx -t
systemctl reload nginx
一开始还发生了服务器内部错误,经过检查发现,之前在旧服务器上指定了 FastCGI 的端口为 9000,而新服务器上没有监听 9000 端口,是默认的 sock 通信方式。
# 旧配置里的两句
location ~ \.php$
{
fastcgi_index index.php;
fastcgi_pass 127.0.0.1:9000;
}
# 修改为
location ~ \.php$ {
include snippets/fastcgi-php.conf;
fastcgi_pass unix:/run/php/php-fpm.sock;
}
fastcgi_pass unix:/run/php/php-fpm.sock;
表示 PHP-FPM 通过 Unix 套接字(socket)方式与 Nginx 通信,这是默认的方式,性能更高,延迟更低,适合 Nginx 和 PHP-FPM 运行在同一台服务器上的场景。如果两者运行在不同的电脑上或容器里,以及运行在 windows 上时,则更适合使用端口通信。
解决迁移后遇到的问题
这部分只是折腾的记录,不需要看。
我把博客复制到了新服务器上,并使用新的域名来访问,但是首页打不开,WordPress 显示“此站点遇到了致命错误”。
原服务器上的 PHP 版本是 7.0(新服务器上是 8.2),而且受限于 PHP 版本,WordPress 版本也停留在了 5.8(最新版本是 6.8)。
我解决了一些错误才让网站能正常访问,到最后我发现这些错误都是主题和插件里的代码引起的,WordPress 本身只是引发了一些 Deprecated 问题,不背锅。
显示错误日志
编辑 wp-config.php,添加这两行以显示调试信息:
define( 'WP_DEBUG', true );
define('WP_DEBUG_LOG', true);
保存后刷新页面,就可以看到错误信息了。我一看,满屏的错误日志,有 Deprecated、Warning、Notice、Fatal error,真是群贤毕至,少长咸集啊。
前后台都有大量的 Deprecated 报错,如:
Deprecated: Creation of dynamic property WP_Term::$category_description is deprecated in saber.love/wp-includes/category.php
这是因为在 WP_Term 对象上创建动态属性导致的,PHP 8.2 已弃用动态分配对象属性的功能。这些代码可能会在未来的 PHP 版本中出现问题。
这主要是 WordPress 本身的代码导致的(还有个一个插件),后来升级 WordPress 和插件之后就消除了所有的 Deprecated。
mb_strimwidth 重复定义
打开前台页面时,显示了一个致命错误:
错误类型 E_COMPILE_ERROR 发生在文件 saber.love/wp-content/themes/clearisionbrown/functions.php。错误信息:Cannot redeclare mb_strimwidth()
原来是因为 PHP 的 mbstring 模块内置了 mb_strimwidth,而我的主题文件里有个自定义的 mb_strimwidth,导致了重复定义的报错。
调用 mb_strimwidth 的地方是 header.php 里用于生成 description 的代码。我先删除了自定义的 mb_strimwidth 函数,然后把这处调用的参数改为和内置的 mb_strimwidth 函数一致,解决了这个错误。
$description = mb_strimwidth(strip_tags(apply_filters('the_content',$post->post_content)),0,220,'...','UTF-8')
中文引号导致的致命错误
Fatal error: Uncaught Error: Undefined constant "‘init’" in saber.love/wp-content/themes/clearision/functions.php
让我大吃一惊的是,有一个函数里的单引号全是中文引号:
function disable_emojis() {
remove_action( ‘wp_head’, ‘print_emoji_detection_script’, 7 );
remove_action( ‘admin_print_scripts’, ‘print_emoji_detection_script’ );
remove_action( ‘wp_print_styles’, ‘print_emoji_styles’ );
remove_action( ‘admin_print_styles’, ‘print_emoji_styles’ );
remove_filter( ‘the_content_feed’, ‘wp_staticize_emoji’ );
remove_filter( ‘comment_text_rss’, ‘wp_staticize_emoji’ );
remove_filter( ‘wp_mail’, ‘wp_staticize_emoji_for_email’ );
add_filter( ‘tiny_mce_plugins’, ‘disable_emojis_tinymce’ );
}
add_action( ‘init’, ‘disable_emojis’ );
这个错误可能是以前在浏览器里,通过后台管理页面直接编辑 functions.php 导致的。我记得这样修改后保存时,偶尔会产生奇怪的问题。
把引号都替换成英文单引号解决了这个错误。此时前台页面可以打开了,只是依然有大量的 Deprecated 提示。
发送 HTTP 头之前的输出导致的错误
后台页面打不开,其中有条报错:
Warning: Cannot modify header information - headers already sent by (output started at saber.love/wp-includes/class-wp-block-list.php:14) in saber.love/wp-includes/pluggable.php on line 1335
错误原因:PHP 在发送 HTTP 头(如 header() 调用)之前,不能有任何输出(例如 HTML、空格、错误信息或 echo)。错误提示指出,输出始于 saber.love/wp-includes/class-wp-block-list.php:14,这导致 pluggable.php 中第1335行和第1338行的 header() 调用失败。
但是 class-wp-block-list.php 里的代码看起来很正常,我以前也没修改过这个文件。
可能是其他地方或插件里的代码引发了这个警告,但直接原因应该是:在这个 Warning 之前有大量的 Deprecated 错误,可能是前面输出的这些错误日志导致了此问题。当我关闭调试日志后就可以打开后台页面了。
避免后台页面跳转
我的原网站域名是 saber.love,新服务器上的测试域名是 test.saber.love。当我登录测试网站的后台时,会重定向到原域名的后台网址:https://saber.love/wp-login.php。
原因是原网站的数据库里保存着域名配置,也就是 saber.love,需要将其修改为测试域名:
登入数据库:
mysql -u root -p your_database
更新站点网址和首页网址:
UPDATE wp_options SET option_value = 'https://test.saber.love' WHERE option_name = 'siteurl';
UPDATE wp_options SET option_value = 'https://test.saber.love' WHERE option_name = 'home';
之后就可以登录测试网站的后台了:https://test.saber.love/wp-login.php
插件导致的致命错误
在我登入后台之后,直接显示了遇到了致命错误。我不得不又打开调试模式,然后看到了错误信息:
Fatal error: Uncaught Error: Undefined constant "WPLANG" in saber.love/wp-content/plugins/comment-reply-notification/comment-reply-notification.php:376
这是因为 comment-reply-notification 插件使用了一个已经从 WordPress 4.0 版本就已经废弃的 WPLANG 常量(但是在旧服务器上怎么没报错呢?)。
编辑该文件,发现有两处 if( strpos(WPLANG, 'zh_') === false)
,将其修改为 if( strpos(get_locale(), 'zh_') === false)
解决了此问题。
然后关闭调试日志,否则在一开始输出的 Deprecated 错误日志会导致“在发送 HTTP 请求头之前的输出内容”错误。
测试网站功能
当前后台页面都可以正常显示后,我测试了发表新文章、发表评论等功能,一切正常。
升级 WordPress 和插件
现在终于可以升级 WordPress 了,我顺利更新到了最新的 6.8 版本。
然后重新打开调试日志,此时 WordPress 本身不会产生任何 Deprecated 错误了,因此不会导致“在发送 HTTP 请求头之前的输出内容”错误。
此时还有某个插件导致的 Deprecated 错误,升级插件就解决了。
最后只剩下一条错误:
Notice: 函数 wp_deregister_script 的调用方法不正确。 脚本和样式应在 wp_enqueue_scripts 、 admin_enqueue_scripts 和 login_enqueue_scripts 钩子函数之后再加入加载队列(enqueue)或注册(register)。 此通知由 l10n 触发。 请查阅调试 WordPress来获取更多信息。 (这个消息是在 3.3.0 版本添加的。) in saber.love/wp-includes/functions.php on line 6121
引发此问题的代码其实在主题的 functions.php 里,这行代码 wp_deregister_script( 'l10n' );
的执行时机过早,导致了这个错误。
把这行代码改为下面的代码,解决了此问题。
/**
* 正确地注销脚本
* 将注销操作挂载到 wp_enqueue_scripts 钩子上
*/
function my_theme_deregister_scripts() {
wp_deregister_script( 'l10n' );
}
add_action( 'wp_enqueue_scripts', 'my_theme_deregister_scripts', 100 );
在折腾了好久之后,终于 0 error 0 warning 了,开心!这下博客的代码又跟上了时代的脚步,可以继续前进了。
在 VPS 上搭建 WordPress 博客的简单教程
-
Google Chrome 137
Windows 10/11
同vutlr,3.5刀+1.5刀外挂HHD,挺贵的,只是上上网挂挂录播而已