从零开始搭建Wordpress个人博客

简介

  简单的介绍一下自己这几天搭建LNMP+Wordpress的经历,踩的各种坑,还有总结出来的各种优化方案。会比较偏向于教学的感觉?但其实主要是好不容易基本上搭完了,写这么一篇记录一下,防止下次再像这样浪费人生。毕竟比我懂得多的大佬多的是。

背景

  最近看着自己平均一个月支出10刀有余的两个服务器搁置在那里,有一种拿着打火机点纸币玩的感觉,所以决定在上面做点什么,所以就考虑要不要搭一个博客。正巧之前不知道什么时候脑子抽风买了个Avada主题,所以就考虑在性能相对较好的那个服务器上搭一个Wordpress博客。而事实证明,如果不是最近每天在家里过颠倒日夜,扭转乾坤,起床只要吃了饭就可以什么都不做的啃老生活的话,这个博客是绝对搭不起来的。

PS:舰C活动还没打完啊,喂,你在想什么啊,有那闲工夫快把舰C活动打了。

重装系统

  这部分其实没有什么好讲的,因为之前的VPS里也没有什么重要的东西,在vps的管理页面一键重装为CentOS8,另一台装的是CentOS Stream,可以相对体验一些新的东西,不过毕竟是CentOS,官方源里不太会有非常新的东西,差别不会很大,也许在升级下个大版本比如说CentOS9什么的时候就能看出CentOS Stream的优势了也说不准。

  顺便和不清楚CentOS Stream的人讲一下,这是和CentOS8一起推出的第一个所谓的CentOS家族的“滚动发行”的系统,软件包会比RHEL上游的新,比Fedora的稳定。如果打个比方的话,RHEL/CentOS是Stable,CentOS Stream是Beta,Fedora是Alpha这种感觉吧。不过既然它是滚动版本,我就有足够的理由相信在下一个CentOS大版本发行的时候它也能平滑升级,大概就是这样。

MySQL(MariaDB)

  先讲数据库,因为这里是遇到坑最少的地方。我这次选择安装的是MariaDB,倒不是因为什么开源信仰之类的,单纯就是因为一直用的都是这个而已。而且MariaDB也有商业服务,可以相信它的安全性和稳定性。

  进入正题,最简单的方法,从官方AppStream源进行安装。

1
sudo dnf install mariadb

  但是我们建博客是为了稳定吗?是为了写博客吗?不是,是为了折腾,所以我们当然不能满足于这样就结束。我们一定要装最新版!

  MariaDB官网给许多发行版都提供了预编译的软件包,其中就包括给CentOS8的dnf(yum)源。打开Setting up MariaDB Repositories这个官方提供的链接,选择自己的系统加版本,然后按着提示做就好。顺便记得在安装时检查一下GPG Key是否匹配。

  安装完了MariaDB,还需要进行初期配置,执行:

1
sudo mysql_secure_installation

  然后按提示操作就好,没什么难度。需要注意的是,配置中有一项仅允许unix socket连接的选项,推荐打开以微弱的增加安全性。

PHP

安装

  Fuck PHP,这个东西编译起来是真的费劲,我有一半的时间都花在安装PHP上了。这里列出我个人基于LNMP与Wordpress以及Avada主题的编译选项。

1
./configure --with-curl --enable-exif --enable-gd --with-webp --with-jpeg --with-freetype --enable-gd-jis-conv --enable-mbstring --with-mysqli --with-sodium --with-openssl --with-zip --with-zlib  --enable-ftp --enable-fpm --with-fpm-user=nginx --with-fpm-group=nginx --with-fpm-systemd --disable-short-tags --with-libdir=lib64

  我需要提醒的是,这个编译选项仍然不是完美的,因为有一些选项我仍然不知道究竟有没有用,but,it works。

PS:如果你对我的具体安装步骤与感兴趣的话,我把相对详细的步骤Push到我的GitHubRepo上了,它在shell文件夹下。

  如果你想要使用我的编译选项,那么你需要安装“一点点”依赖。我的系统安装在minimal的安装配置下,所以它应该只会多而不会少,它也应该足够编译和执行后面需要的一切东西了。

1
2
3
dnf config-manager --set-enabled PowerTools
dnf install epel-release
dnf install gcc gcc-c++ autoconf automake tar xz libtool bison libcurl libcurl-devel libsodium libsodium-devel openssl openssl-devel libzip libzip-devel libssh2 libssh2-devel gd ImageMagick ImageMagick-devel ghostscript zlib zlib-devel systemd-devel libxml2 libxml2-devel sqlite sqlite-devel re2c oniguruma oniguruma-devel memcached libmemcached-devel

  OK,我们安装完了依赖,使用Configure进行了Makefile的生成,接下来该编译了。什么?你说你还需要其他的PHP插件?问题不大,你可以用configure文件的帮助来查看相关的编译选项。

1
./configure --help

  不要去看PHP官网上的SB文档!!! 听我一句劝,鉴于官网上的文档实在太SB了,我没有任何理由推荐你去看。你只需要检查一下configure的帮助选项就好了,这比文档写的清楚得多,当然不要忘记在configure执行出错后找到缺少的依赖库并且安装就好。如果你在选项中找不到的话,你可能需要去PECL上找一下这个包。如果还没有的话,well,你可以选择看一下PHP官方文档了,因为这个依赖很可能已经捆绑在PHP本身上了,但是我仍要提醒你的是不要相信PHP那个SB文档任何一个字! 你应该在PHP编译安装完成之后采取 -m 选项来检查已经安装的插件或者说模块,而不是相信那个文档认为你已经拥有了这个模块。

1
2
3
make
make test
sudo make install

  半个小时过去了,我们总算编译并且安装完了PHP,如果make test只有十几个或几十个错误的话,你完全可以忽略掉,我相信你遇到的那些错误和我一样,那只是测试的问题。如果你还是担心测试结果的话,你可以自己查询相关参数来查看失败测试的输出及原因。

  接下来,处理配置文件。

1
2
3
4
5
6
sudo cp php.ini-production /usr/local/lib/
sudo cp sapi/fpm/php-fpm.service /etc/systemd/system/
...
sudo cp php-fpm.conf.default php-fpm.conf
sudo cp php-fpm.d/www.conf.default php-fpm.d/www.conf
...

  按照你的需求配置好各个配置文件,我们开始进入PHP安装的下一个环节,插件安装!

  记得先检查你的PHP是否按照你的期望安装好了。

1
php -ini

  其实我们需要的只有图像处理用的imagick,文件传输用的ssh2,还有如果你打算启用PHP的memcache才需要的memcached库。

  编译安装插件非常简单下载,解压,进入插件文件夹。

1
2
3
phpize
make
sudo make install

  好的,我们的插件安装完成了,就是这么简单。当然,想要启用这个插件,你还需要在php.ini中进行设置。

  我们的下一个问题是,php.ini应该放在什么地方。我折腾了半天还是没有搞懂configure选项中,究竟默认配置文件搜索路径有什么用,总之,在我这个版本它的默认路径为 (prefix)/lib/php.ini 放在这里就好了。

配置文件

  这里简单的来讲一下配置文件的关键设置,具体详细设置请根据情况自行参考。

  首先是php.ini

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
;不向客户端报告PHP
expose_php = Off
;命令最长执行时间
max_execution_time = 180
;接受最多参数个数
max_input_vars = 1500
;单进程最多使用内存
memory_limit = 128M
;上传最大文件大小
upload_max_filesize = 32M
post_max_size = 32M
;其他安全设置
session.use_strict_mode = 1
session.use_only_cookies = 1
session.cookie_secure = 1
;这里特意提一下 cgi.fix_pathinfo 是因为,有太多的应该放在博物馆里的化石教程里面讲这个选项需要关闭,但是这个问题已经早~~~~~~~~就在默认配置的情况下修复了,推荐开启。在开启的情况下,nginx的设置会省事很多。
cgi.fix_pathinfo = 1

  接下来是php-fpm.conf。主要需要修改pid、error_log还有include路径,这个根据自己情况改就好。

  最后是php-fpm.d/www.conf。一个是listen考虑改为unix管道,当然要和nginx的设置对应上。还有一个是pm下面的几个设置,主要影响子进程的数量,在默认设置下,如果是比较差的机器,可能会出现php-fpm进程吃光内存导致机器卡死,可以根据具体情况调节。

NGINX

安装

  安装十分简单。NGINX官网为我们提供了官方的预编译源配置,只要按着要求做就好了

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
[nginx-stable]
name=nginx stable repo
baseurl=http://nginx.org/packages/centos/$releasever/$basearch/
gpgcheck=1
enabled=1
gpgkey=https://nginx.org/keys/nginx_signing.key
module_hotfixes=true

[nginx-mainline]
name=nginx mainline repo
baseurl=http://nginx.org/packages/mainline/centos/$releasever/$basearch/
gpgcheck=1
enabled=0
gpgkey=https://nginx.org/keys/nginx_signing.key
module_hotfixes=true

  这里包含了两个配置,分别是稳定版stable和最新版mainline,这里我选择稳定版,不对配置文件做任何修改(甚至可以不把mainline的源写进去)。

1
sudo dnf install nginx

  我们的安装虽然完成了,但是我们的安装还没有完成。(精神错乱)

  我们这里为了之后使用nginx的fastcgi缓存功能还需要安装一个ngx_cache_purge的模块,这个模块主要用来提供开源版nginx没有提供的自动清除缓存功能,这样我们就可以在我们更新博客界面时(例如发送,更新,删除博文或评论等)刷新缓存,返回最新的界面。虽然这是一个第三方模块,网上能找到的教程也都是教你如何从源码编译安装这个模块,但是从nginx 1.11.5开始,就正式支持动态模块加载了,并且目前社区也提供了实用的实验性工具将静态模块转换成动态模块。

PS:动态模块加载需要在编译时添加 –with-compat 选项,不过nginx源中提供的版本已经添加了这个选项。

  我们需要下载nginx社区提供的模块编译脚本,它包含在pkg‑oss中,这个工具是用来生成nginx动态模块的安装包的。然后只需要:

1
./build_module.sh -v 1.18.0 https://github.com/FRiCKLE/ngx_cache_purge.git

  注意这里的 -v 参数指定的是nginx的版本号,你应该根据你安装的nginx版本修改。

  之后按照官方提供的方法生成本地dnf(yum)源即可。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
sudo dnf install createrepo
sudo mkdir /opt/rpm
sudo bash -c "cat << EOF > /etc/yum.repos.d/localrepo.repo
[localrepo]
name=Dynamic modules for NGINX
baseurl=file:///opt/rpm
gpgcheck=0
enabled=1
EOF"
sudo cp ~/rpmbuild/RPMS/x86_64/nginx-module-nickname-1.18.0-1.el8.ngx.x86_64.rpm /opt/rpm
createrepo -v /opt/rpm

  其中,上面的 nickname 应当为你在执行编译脚本时为这个模块取的名字。(不过其实按一下Tab就都补全出来了。当然,还会有一个我们不需要的debug版本。)

  之后我们只需要安装这个模块即可。

1
sudo dnf install nginx-module-nickname

至此,安装就算是完成了。

配置文件

  OK,麻烦的东西来了,配置文件。

  具体配置文件怎么写在这里就不赘述了,这里主要讲一些比较重点的部分。

  首先是在所有块的外面,配置文件的顶部,我们需要加载我们刚才安装的模块:

1
load_module modules/ngx_http_cache_purge_module.so;

  在http块中开启一些重要的选项,想知道具体有什么作用可以自行查询。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
sendfile on;
tcp_nopush on;
tcp_nodelay on;
client_max_body_size 32m;
# 开启gzip压缩
gzip on;
gzip_vary on;
gzip_comp_level 6;
gzip_buffers 16 8k;
gzip_min_length 1000;
gzip_proxied any;
gzip_disable "msie6";
gzip_http_version 1.0;
gzip_types text/plain text/css application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript application/javascript;

  我这里是将每个站点单独写一个配置文件,并在主配置文件中使用 include 包含。在站点文件中:

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
fastcgi_cache_path /run/nginx-cache levels=1:2 keys_zone=WPCACHE:300m inactive=60m max_size=10g;
fastcgi_temp_path /run/nginx-cache-temp;
fastcgi_cache_key "$scheme$request_method$host$request_uri";
server {
    listen 443 ssl http2 fastopen=50 reuseport;
    server_name example.net;
    root /your/wordpress/root;

    #这里省略TLS的配置部分。

    # 禁止非本域名访问
    if ($host != "example.net") {
        return 444;
    }
    index index.php index.html;
    set $skip_cache 0;
    location ~ /purge(/.*) {
            allow 127.0.0.1;
            allow your_server_ip;
            deny all;
            fastcgi_cache_purge WPCACHE "$scheme$request_method$host$1";
    }
    location ~* ^.+\.(ogg|ogv|svg|svgz|eot|otf|woff|mp4|ttf|rss|atom|jpg|jpeg|gif|png|ico|zip|tgz|gz|rar|bz2|doc|xls|exe|ppt|tar|mid|midi|wav|bmp|rtf)$ {
    access_log off; log_not_found off; expires max;
    }
    location = /robots.txt {
        allow all;
        log_not_found off;
        access_log off;
    }
    location = /ads.txt {
        allow all;
        log_not_found off;
    }
    location = /human.txt {
        allow all;
        log_not_found off;
        access_log off;
    }
    location ~ /\. {
        deny all;
    }

    # 这一段是用来bypass掉一部分不应该使用缓存的请求,但是这段的配置我是直接抄的,我并不清楚是否完善或者有问题,但是至少看起来是没有什么问题。
    set $skip_cache 0;
    if ($request_method = POST) {
        set $skip_cache 1;
    }
    if ($query_string != "") {
        set $skip_cache 1;
    }
    if ($request_uri ~* "/wp-admin/|/xmlrpc.php|wp-.*.php|/index.php|/feed/|sitemap(_index)?.xml") {
        set $skip_cache 1;
    }
    if ($http_cookie ~* "comment_author|wordpress_[a-f0-9]+|wp-postpass|wordpress_no_cache|wordpress_logged_in") {
        set $skip_cache 1;
    }

    location / {
        try_files $uri $uri/ /index.php?$args;
    }
    location ~ \.php$ {
        try_files $uri =404;
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME $request_filename;
        fastcgi_param PATH_INFO $fastcgi_path_info;
        # fastcgi_pass路径要与php-fpm的设置相匹配
        fastcgi_pass unix:/run/php-fpm.sock;
        fastcgi_cache_bypass $skip_cache;
        fastcgi_cache WPCACHE;
        fastcgi_cache_valid 200 6h;
        fastcgi_cache_use_stale error timeout invalid_header updating http_500 http_503;
        fastcgi_ignore_headers Cache-Control Expires Set-Cookie;
        proxy_cache_background_update on;
        proxy_cache_lock on;
    }
    # 如果你建立的是个人博客并且有固定ip的话,你应该不想让其他人访问你的登录页面。
    location = /wp-login.php {
    allow your_ip;
    deny all;
    try_files $fastcgi_script_name =404;
    fastcgi_intercept_errors on;
    fastcgi_index  index.php;
    include        fastcgi_params;
    fastcgi_param  SCRIPT_FILENAME  $request_filename;
    fastcgi_param  PATH_INFO $fastcgi_path_info;
    fastcgi_pass   unix:/run/php-fpm.sock;
    fastcgi_read_timeout 300s;
    }
}
# 将非HTTPS访问跳转到HTTPS。如果需要最大兼容性的话,这里应该使用301跳转而不是308跳转。就我的体感看来,两种跳转在使用中并没有什么实质性的区别,所以也许用301跳转会更好一些?
server {
    listen 80;
    server_name example.com;
    return 308 https://$server_name$request_uri;
}
#禁止非本域名访问。这是一个server块,你应该只在主配置文件中添加一次,如果你像我一样只有一个网站的话,你也可以像我一样放在站点的配置文件中(但是不推荐)。
server {
    listen 80;
    server_name "";
    return 444;
}

  标注出来的行是你直接抄来用时必须要修改的部分,当然,同样,具体配置的效果你可以自己查询。

  在fastopen处,我考虑到网站不会有什么人访问,我就设成了50的最高同时连接数,实际上你加个零,甚至配置好加几个零,问题都是不大的。

  然后我们还需要建立fastcgi缓存目录:

1
2
3
sudo mkdir /run/nginx-cache
sudo mkdir /run/nginx-cache-temp
sudo chown nginx:nginx /run/nginx-cache /run/nginx-cache-temp

  之后,再配置好你的TLS证书,我们就可以上路了!

Wordpress

  接下来就是怎么要让Wordpress跑起来了。将最新版wordpress下载解压到你的网站根目录。

注意:你的php-fpm子进程用户应该对你的网站整个目录有读写权限,你的nginx子进程应该对你的网站整个目录有读取权限。另外为了wordpress可以使用ssh上传文件,你的php-fpm子进程的执行用户需要可以登录并且有自己的家目录,或者也可以建立一个对你的网站有读写权限的新账号专门用来使用ssh上传文件。听起来太复杂?下面是一个例子。
例如php-fpm与nginx都是www:www用户运行,你的网站目录在 /usr/share/nginx/www/ 下。那么:

  1. 如果www用户可以登录,并且有自己的家目录:
    那么你的网站目录下的权限可以设置为700/600,并且保证www用户对所有上层目录拥有读取与执行权限。
    然后你就只需要在你的www用户的家目录下建立.ssh目录,并为其配置单独的ssh密钥。将另一份公钥与没有被密码保护的私钥放在www用户可以读取的位置(推荐家目录下建立专用文件夹)。

  2. 相对更安全,并且在www用户无法登录时适用的方法:
    你需要建立一个属于www组的新用户,这里假设是www-data。
    那么你的网站目录权限需要设置为770/660,并且保证www组对所有上层目录拥有读取与执行权限。
    然后你需要在你的www-data用户的家目录下创建一个保存ssh公钥与私钥的文件夹,将其所有者设置为www用户,并且给予这个文件夹700权限,里面的密钥400权限。然后同样在.ssh目录下配置好登录用的公钥。最后,将www-data家目录的根目录设为770权限。(这里假设不存在www-data组,www-data用户的默认组为www,具体可参见useradd的 -g 选项

  接下来,创建数据库,编辑wp-config.php文件,这里的wp-config.php中,除了常规设置外,我们还需要添加一些配置:

1
2
3
4
5
6
7
8
9
define( 'FS_METHOD', 'ssh2' ); // 上传文件方式,我们这里使用sftp。
define( 'FTP_BASE', 'root' ); // Wordpress网站根目录
define( 'FTP_CONTENT_DIR', 'root/wp-content/' ); // wp-content文件夹位置,默认情况下可省略。
define( 'FTP_PLUGIN_DIR ', 'root/wp-content/plugins/' ); // plugins文件夹位置,默认情况下可省略。
define( 'FTP_PUBKEY', 'sshkey/id_ecdsa.pub' ); // 公钥位置
define( 'FTP_PRIKEY', 'sshkey/id_ecdsa' ); // 私钥位置
define( 'FTP_USER', 'www-data' ); // ssh用户
define( 'FTP_PASS', '' ); // 密码,若使用带密码保护的私钥的密码可放在这里,但不推荐,据Wordpress官网说法,带密码保护的私钥会有兼容性问题。可省略。
define( 'FTP_HOST', 'localhost:22' ); //ssh端口

  顺便,最新版的Wordpress支持cookie加盐,具体可参见默认的配置文件中的介绍。

  接下来我们就可以进我们的Wordpress网站了!

  当然没有那么简单,我们还需要做一些准备工作,如果你没有做的话。

1
2
3
4
5
6
7
8
sudo firewall-cmd --add-port=80/tcp --add-port=443/tcp
sudo firewall-cmd --reload

sudo systemctl enable nginx --now
sudo systemctl enable mariadb --now
sudo systemctl enable php-fpm --now

sudo setenforce 0

  如果各个服务都正常启动了,那么我们就可以进入我们的Wordpress网站,并且安装我们可爱的主题和插件,进行初期配置了。

  初期配置结束以后,我们还有最后一步要做,我们在上面把SELinux设置为了Permissive,这是因为默认的SELinux配置不允许nginx访问unix管道,也不允许nginx访问除了大多数常用位置以外的文件,如果你使用了unix管道或者将网站文件夹放在了非常用位置的情况下:

  查看nginx所需要的额外权限:

1
sudo grep nginx /var/log/audit/audit.log | audit2why

PS:对于网站文件来说nginx不应该拥有读取或修改拥有user_t等上下文的文件,而只应当拥有对httpd_sys_content_t的读取权限、对httpd_sys_rw_content_t与httpd_user_content_t的读写权限。如果你的网站目录与文件不具有这样的上下文,那么你应该去修改你网站文件的上下文,而不是给予nginx它不应该具有的权限。

  在有必要的情况下,修改网站文件的SELinux上下文后,通过grep命令与操作id,将nginx所需要的额外权限过滤出来。然后:

1
sudo grep -E "some_id|another_id..." /var/log/audit/audit.log | audit2allow -m nginx

  再次检查所需要添加的权限是否正确。最后:

1
2
sudo grep -E "some_id|another_id..." /var/log/audit/audit.log | audit2allow -M nginx
sudo semodule -i nginx.pp

  经过一段较长时间的等待后,安装上新的SELinux模块,现在nginx应该能在SELinux下正常执行了。

1
sudo setenforce 1

  至此,我们的LNMP+Wordpress就算是搭建完成了,还有一些设置则是网站内的设置了,这里就不详细提及了。

  特别的,上文所述的ngx_cache_purge模组需要在Wordpress插件Nginx Helper的帮助下才能正常使用。

  这样搭建成的网站应该能在Wordpress后台进行所有的上传,更新,安装,更换语言等操作。并且也具有相当的安全性与客观的加载速度。在nginx的fastcgi-cache与http/2的帮助下,我们不再需要加速插件,加速插件可以说很难起到什么大的实质性的作用。在Avada这样“重量级”的主题下,我配置好的主页加载速度在Google Pagespeed Insights中取得了桌面91分,移动端54分的得分。可以说是相当不错了。

后记

  舰C的这次活动真是太难了,海伦娜立绘的事也闹得是满城风雨的,希望都能有个好结果吧……

  嗯?后记不应该说这个?哦哦!想起来了,这个是搭Wordpress的文章来着。这次查了数不清的资料,看了数不清的文档,踩了数不清的坑,但就是这样,网站还是没有像个样子的搭起来。这篇文章也写了很久,不过还是有很多没有涉及到的地方,有很多地方也是草草跳过,不过至少下次不会再踩这么多坑了,也算是小有收获吧。顺便,希望这篇文章也能多多少少帮得到你,让你少踩几个坑吧。总之,遇到问题了-> 看帮助 -> 看日志 -> 查文档 -> 用英文在网上搜索,这么一圈转下来,总能解决。