Hexo-自定义部署

Hexo-自定义部署

1. 多Page仓库部署

Hexo-初始化静态网站 中提到,GitHub 允许一个账号拥有 1 个 账号 Page 和多个 项目 Page,因此可以利用多个项目 Page 达到分内容、分域名、冗余部署等。

开启项目 Page 的前提是已经开启了账号 Page,确保可用默认域名 [username].github.io 访问账号 Page。

1.1 开启项目Page

新建一个 GitHub 仓库用作项目 Page,GitHub 对项目 Page 的仓库名没有限制。

新建完成后需要初始化仓库以及提交一个初始 Commit,以一个简单的 HTML 源文件为例,在本地创建如下代码并上传到项目 Page 仓库的 main 分支中:

1
2
3
<html>
<h1>This is Project Page.</h1>
</html>

在项目 Page 仓库的 Settings - Pages 中的部署源选项 Source 选择 main 分支,则项目 Page 就已经建立好了,默认情况下这个项目 Page 的访问域名为:[username].github.io/DemoPage

1.2 自定义域名

GitHub Page 设置域名的方式是在 Page 仓库根目录下创建一个没有后缀的文件 CNAME,并在文件中写入一行(且只能有一行)需要自定义的域名,例如:

1
xxx.com

然后在 DNS 添加一条 CName记录,让 xxx.com 指向 [username].github.io 即可。需要注意的是,CNAME 文件中指定的是纯域名,不携带任何协议网络(例如 httphttps 等)。

假设账号 Page 设置了自定义域名 xxx.com、项目 Page 的仓库名为 DemoPage

默认情况下,项目 Page 的访问路径是作为账号 Page 的子路径访问的,则项目 Page 的访问路径会自动设置为 xxx.com/DemoPage

如果想要用自定义域名访问,只需要在 DemoPage 仓库根目录下创建 CNAME 文件,并在其中写入对应的域名。

  • 作为子域名访问时,写入例如:demo.xxx.com
  • 作为独立的顶级域名访问时,写入例如:yyy.com

然后在对应域名的 DNS 中添加一条 CName 记录,同样指向账号 Page 的默认域名 [username].github.io,GitHub 会自动判断请求的域名是否指向了某个 Page,如果是则自动转发。


2. 云服务器部署

考虑到 GitHub Page 在国内访问不太稳定,受不同运营商、不同地域的影响差别很大,因此也希望能部署到云服务器上。

2.1 搭建云服务器

云服务器按照个人需求选择即可。

以腾讯云轻量应用服务器 + Ubuntu Server 18 LTS 为例。

2.1.1 准备部署环境

  • 确保服务器已经可用,且具有公网 IP,允许公网访问。
  • 确保本地已有可用 SSH 密钥。
  • 服务器绑定 SSH 公钥,用于远端部署。

2.1.2 配置Ubuntu默认账号

腾讯云轻量应用服务器的默认账号为 ubuntu,如果创建服务器实例时已经设置了该账号的密码则可以跳过。否则需要通过「重置密码」设置,注意设置时选择用户名 系统默认 ubuntu

2.1.3 登录服务器

腾讯云轻量应用服务器在选择 Ubuntu 镜像时,默认会创建一个用户 lighthouse,并且腾讯云默认禁用了 root 用户,尽管可以通过配置开启,但远端登录时仍有许多问题。因此建议切换为标准的默认用户 ubuntu,否则后续需要多次调整权限。

网页端控制台可以在登录后通过 su ubuntu 切换至 ubuntu 账号,远端登录时可以通过 SSH 连接直接指定登录用户:

1
2
3
// [username] 即为需要登录的用户,前提是该用户已创建。
// [server ip] 即为服务器的公网 IP,如果没有则需要购买公网 IP。
ssh [username]@[server ip]

(1)如果首次从本地远端登录,可能会提示密钥交换信息:

1
2
3
The authenticity of host 'XXX.XXX.XXX.XXX (XXX.XXX.XXX.XXX)' can't be established.
RSA key fingerprint is XXXXXXXXXXXXXXXXXXXXXXXXXXXXX.
Are you sure you want to continue connecting (yes/no/fingerprint)?

输入 yes 继续即可。

(2)如果曾经连接过,但是服务器重置了或者公网 IP 有变更,则远端登录时可能提示错误:

1
2
3
4
5
6
7
8
9
10
11
12
13
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@ WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED! @
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
IT IS POSSIBLE THAT SOMEONE IS DOING SOMETHING NASTY!
Someone could be eavesdropping on you right now (man-in-the-middle attack)!
It is also possible that a host key has just been changed.
The fingerprint for the RSA key sent by the remote host is
SHA256:XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX.
Please contact your system administrator.
Add correct host key in ~/.ssh/known_hosts to get rid of this message.
Offending RSA key in ~/.ssh/known_hosts:4
RSA host key for XXX.XXX.XXX.XXX has changed and you have requested strict checking.
Host key verification failed.

根据提示编辑 ~/.ssh/known_hosts 文件,删除曾经保存的服务器 IP 对应的 Host 信息:

1
2
// 删除公网 IP 对应的这条 Host 记录并保存
XXX.XXX.XXX.XXX ecdsa-sha2-nistp256 XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

然后再重新登录即可。

2.2 配置云服务器

配置云服务器有两种方式:

  1. 使用默认 ubuntu 用户 + sudo 操作,但某些脚本可能需要单独设置 root 权限。
  2. 使用 root 用户配置,但远端使用 SSH 鉴权 Push 时也需要指定为 root 用户,可能会有安全性问题,不推荐。

结合上述问题,本文以第 2 种方式为例,使用 root 用户配置服务器,但禁用远端登录 root 用户,配置完后再对远端 Push 涉及的目录设置为 ubuntu 用户权限。

2.2.1 开启Root用户

腾讯云轻量应用服务器默认禁用了 root 用户,需要先以普通用户(例如 ubuntu)登录后再手动开启:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 1. 设置 root 用户密码,可以与其他用户的相同:
ubuntu@~$ sudo passwd root
// 二次输入密码后提示设置成功:
passwd: password updated successfully

// 2. 编辑配置文件 Authentication 部分,开启 root 密码登录:
ubuntu@~$ sudo vi /etc/ssh/sshd_config

// 2.1 取消 PermitRootLogin 选项的注释,
// 默认配置 prohibit-password,禁止 Root 用户使用密码登录:
PermitRootLogin prohibit-password

// 2.2 如果需要允许 Root 用户登录,则修改 PermitRootLogin:
PermitRootLogin yes
// 然后添加一条配置,允许使用密码认证:
PasswordAuthentication yes

// 3. 保存配置退出编辑模式后,重启 SSH 服务
ubuntu@~$ sudo service ssh restart

断开远端连接重新登录;如果允许直接通过密码远端登录 root 用户,则可以直接通过 ssh root@[server ip] 登录。本文为了安全性保持禁用 root 用户直接密码登录,因此仍需先以 ubuntu 用户登录,然后再切换至 root 用户:

1
2
3
4
5
6
7
8
// 从本地登录至远端 ubuntu 用户:
[luis@~]# ssh ubuntu@XXX.XXX.XXX.XXX

// 登录后切换至 root 用户,输入 root 密码:
ubuntu@~$ su root

// 切换成功后可以看到已经切换至 root 用户,且命令进入特权模式:
root@/home/ubuntu#

2.2.2 安装环境

在云服务器部署静态网站,主要需要用到两个开发环境:

  • Nginx: 用于自动把公网请求映射到具体的静态网页资源。
  • Git: 用于从本地推送静态网站资源。

腾讯云轻量应用服务器的 Ubuntu 系统默认自带了 Git 环境,因此只需要安装 Nginx:

1
2
3
4
// 更新 apt:
root@/home/ubuntu# apt-get update
// 安装 nginx:
root@/home/ubuntu# apt-get install git nginx -y

Nginx 常用命令:

  • 启动服务:service nginx start
  • 停止服务:service nginx stop
  • 重启服务:service nginx restart
  • 刷新配置:nginx -s reload(需要先启动 Nginx 服务)

注意:启动服务并不会自动读取最新配置,每次修改 Nginx 配置后都需要刷新配置才能生效。

2.2.3 配置网站目录

采用 Nginx + Git 托管静态网站需要用到两个目录:

  • Nginx 最终代理和转发的网站资源文件目录。
  • Git 接收远端 Push 网站资源文件的 Git 仓库目录。通过配置 Git Hook 将收到的文件自动关联到 Ngnix 的资源文件目录。

(1)创建 Nginx 网站资源目录并授权;

1
2
3
# 实际目录可自定义:
ubuntu$ sudo mkdir -p /xxx/WebServer
ubuntu$ sudo chmod -R 755 /xxx/WebServer

(2)创建 Git 接收裸仓库的目录并授权;

1
2
3
4
5
6
# 注意创建的是裸仓库,添加 --bare 参数
ubuntu$ sudo git init --bare /xxx/Web.git
# 提示创建成功
Initialized empty Git repository in /xxx/Web.git/

ubuntu$ sudo chmod -R 755 /xxx/Web.git

Git 裸仓库是指该仓库将只保存仓库的提交历史(.git 文件),而不会保存实际文件,但同样支持 Push 和 Pull,通常作为服务器存储仓库。

(3)配置 Nginx 代理和转发目录(注意备份);

1
ubuntu$ sudo vim /etc/nginx/sites-available/default

修改 server 部分

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
server {
# 监听普通 HTTP 的 80 端口:
listen 80 default_server;
listen [::]:80 default_server;

# SSL configuration(监听 HTTPS 的 443 端口):
listen 443 ssl default_server;
listen [::]:443 ssl default_server;

# 设置代理和转发的目录:
root /xxx/WebServer;

# 指定监听的域名:
server_name example.com;
}

然后重启 Nginx 服务:

1
ubuntu$ sudo service nginx restart

2.2.4 验证网站访问性

(1)在 WebServer 目录下创建一个测试网页:

1
ubuntu$ vim /xxx/WebServer/index.html

随便填充一个元素:

1
2
3
<html>
<h1>This is My Web.</h1>
</html>

然后访问公网 IP,如果可以正常访问说明配置正确;如果网页报 404 错误,则需要查看 Nginx 错误日志。

(2)Nginx 日志存放目录配置在 Nginx 配置文件中:

1
ubuntu$ sudo vim /etc/nginx/nginx.conf

找到 http 部分的 Logging 设置:

1
2
3
4
http {
access_log /var/log/nginx/access.log;
error_log /var/log/nginx/error.log;
}

然后查看错误日志:

1
2
3
ubuntu$ sudo vim /var/log/nginx/error.log

yyyy/MM/dd hh:mm:ss [crit] 18205#18205: *1 stat() "/xxx/WebServer/" failed (13: Permission denied), client: XXX.XXX.XXX.XXX, server: example.com, request: "GET / HTTP/1.1", host: "XXX.XXX.XXX.XXX"

错误日志提示发生了 403 权限拒绝问题。

(3)查看 Nginx 的运行和启动用户:

1
2
3
4
ubuntu$ sudo ps aux | grep "nginx: worker process" | awk '{print $1}'

www-data
root

发现 Nginx 启动用户是 root,但运行用户是 www-data,所以出现了用户权限被拒绝的情况。

(4)将 Nginx 的运行用户也修改为 root 用户:

1
ubuntu$ sudo vim /etc/nginx/nginx.conf

将 user 修改为 root 用户:

1
user root;

然后重启 Nginx 服务:

1
ubuntu$ sudo service nginx restart

再次访问云服务器的公网 IP,可以正确访问了。

2.2.5 创建Git-Hook

由于 Nginx 只能将网络请求代理和转发到 WebServer 目录下的网站资源文件,而本地的网站资源文件是通过 Git Push 到 Web.git 仓库下的,因此需要创建一个 Git-Hook,自动将 Web.git 下的资源文件关联到 WebServer 目录中:

(1)创建一个 Hook 文件 post-receive

1
ubuntu$ vim /xxx/Web.git/hooks/post-receive

(2)在 post-receive 中写入一行:

1
git --work-tree=/xxx/WebServer --git-dir=/xxx/Web.git checkout -f

(3)保存退出后,给 post-receive 文件添加执行权限:

1
ubuntu$ chmod +x /xxx/Web.git/hooks/post-receive

2.3 本地Push至云服务器

经过上述配置,服务器中的 Git 接收和 Nginx 代理转发已经配置好了,即可从本地将 Hexo 生成的静态网站 Push 至服务器。

(1)在 Hexo 站点配置文件中添加云服务器 Git 部署配置:

1
2
3
4
5
6
7
8
deploy:
## 部署至 GitHub Page 仓库:
- type: git
repository: git@github.com:[username]/[username].github.io.git
branch: main
## 部署至云服务器:
- type: git
repository: ubuntu@[server ip]:/xxx/Web.git

(2)本地执行 Hexo 命令部署:

1
luis$ hexo clean && hexo g -d

有可能报错 unable to create temporary object directory

1
2
error: remote unpack failed: unable to create temporary object directory
error: failed to push some refs to 'ubuntu@43.129.198.91:/xxx/Web.git'

也有可能报错 Permission denied

1
error: unable to create file xxx: Permission denied

向云服务器 Push 时报错,如果原因为 unable to createunable to accesspermission denied 等,绝大多数是因为 Git Push 的用户没有目标路径的权限导致的,在本例中,WebServerWeb.git 所在目录、以及其内所有子文件都是 root 用户权限,而云服务器为了安全禁用了远端登录 root 用户,所以 Git 在以 ubuntu 用户 Push 时就会导致权限不足。

(3)将云服务器中 WebServerWeb.git 所在目录、以及内部递归所有子文件都改为 ubuntu 用户权限:

需要修改 WebServer、Web.git、以及各自所在的所有父级目录。

1
2
# 递归修改该目录及其内部所有子文件的权限,
ubuntu$ sudo chown -R $USER:$USER /xxx/xxx

(4)再次查看文件权限,确保上述各个目录均可被 ubuntu 用户访问:

1
2
3
ubuntu$ ll

drwxr-xr-x 4 ubuntu ubuntu 4096 Aug 1 hh:mm /xxx/xxx

(5)再次从本地执行 Hexo 命令,即可成功部署。

2.4 GitHub-Page开启HTTPS

GitHub Page 只需要确保域名已绑定 SSL 证书,然后在 Page 仓库的 Settings - Pages 中勾选 Enforce HTTPS 即可。

2.5 云服务器开启HTTPS

云服务器开启 HTTPS 需要自行申请 SSL 证书:

  • 使用域名提供商颁发的证书:
    • 通常有免费证书(一般有效期最大 90 天),不支持原证书续签,每次都要重新申请后手动替换证书公私钥;
    • 购买付费证书;
  • 使用 LetsEncrypt 颁发的免费证书(有效期 90 天),可以直接在服务器使用脚本配置:
    • 由于地区限制,.cn 顶域只能通过 DNS 的方式验证,因此域名过期后只能重新申请证书并手动更新 DNS 验证,但无需替换公私钥文件(Certbot 会用新证书覆盖);
    • 其他国际域名可通过 HTTP + WebRoot 的方式自动验证域名,可以配合 crontab 实现自动续签;

注意:

  • 根域名(顶级域名,如 example.com)和 子域名/泛域名(如 xxx.example.com)需要分别申请各自的证书。
  • 使用 LetsEncrypt 颁发的证书时,如果服务器在大陆境内则需要先为服务器备案。
  • Nginx 配置 SSL 需要证书的公钥和私钥。

2.5.1 从域名提供商获取SSL证书

如果使用域名提供商的证书(不论 免费/付费),优先选择标明了 Nginx 场景的证书,或者下载包含 pem 文件crt 文件key 文件 的证书:

  • 公钥:domain.crtdomain.pem 文件;
  • 私钥:domain.key 文件;

然后将证书从本地上传到远端:

1
2
3
# 在本地终端执行以下代码:
scp ./domain.crt ubuntu@xx.xx.xx.xx:/xxx/domain.crt
scp ./domain.key ubuntu@xx.xx.xx.xx:/xxx/domain.key

注意,这一步命令可能会遇到报错 Permission denied (publickey),则检查以下配置:

  • 检查目标路径的用户权限,例如服务器当前用户是 ubuntu,但 /DemoDir 这个目录可能是 root 用户权限,解决办法为:

    • scp 命令改为指定 root 用户登录服务器,但通常服务器为了安全会禁用 root 用户的远端登录,因此不推荐。
    • scp 目标路径临时选择一个 ubuntu 用户有权限的目录,然后登录到服务器后再移动到其他目录,此时可以自由切换 root 用户了。
  • 检查服务器用户的 SSH 配置中是否绑定了本地公钥:

    • 查看服务器的 ~/.ssh/authorized_keys 文件,是否包含本地 ~/.ssh/id_rsa.pub 公钥内容。
    • 如果没有,则将本地 id_rsa.pub 公钥内容追加到服务器 authorized_keys 文件后(该文件可以包含多个公钥的内容)。

2.5.2 LetsEncrypt证书+DNS校验

使用 LetsEncrypt 颁发的证书本质也是在服务器内下载证书,不过 LetsEncrypt 提供了 certbot 脚本,可以在服务器内完成:

(1)安装 Certbot:

参考:

Ubuntu 下默认没有 yum 源:

Apache 服务器可以参考:

其他 OS 可以使用 yum 安装,参考:

使用 Docker 可以参考:

1
sudo apt-get install certbot

(2)为根域名 example.com 和 泛域名 *.example.com 同时申请证书:

1
sudo certbot certonly -d example.com -d *.example.com --manual --preferred-challenges dns

LetsEncrypt 会询问是否接受服务器 IP 被记录,必须接受才能继续申请:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Saving debug log to /var/log/letsencrypt/letsencrypt.log
Plugins selected: Authenticator manual, Installer None
Obtaining a new certificate
Performing the following challenges:
dns-01 challenge for example.com
dns-01 challenge for example.com

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NOTE: The IP of this machine will be publicly logged as having requested this
certificate. If you're running certbot in manual mode on a machine that is not
your server, please ensure you're okay with that.

Are you OK with your IP being logged?
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
(Y)es/(N)o:

输入 Y 后 LetsEncrypt 会分别为根域名和泛域名生成 DNS 的 TXT 记录用于校验,需要手动将其添加到域名 DNS 解析中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
(Y)es/(N)o: Y

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Please deploy a DNS TXT record under the name
_acme-challenge.example.com with the following value:

******************************************

Before continuing, verify the record is deployed.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Press Enter to Continue

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Please deploy a DNS TXT record under the name
_acme-challenge.example.com with the following value:

******************************************

Before continuing, verify the record is deployed.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Press Enter to Continue

(3)在下一步之前,确保已经在 DNS 解析中为根域名和泛域名分别添加了一条 TXT 记录

(4)输入回车继续,LetsEncrypt 会通过 DNS 校验域名的合法性,稍等片刻校验通过后将会自动下载证书到本地:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Waiting for verification...
Cleaning up challenges

IMPORTANT NOTES:
- Congratulations! Your certificate and chain have been saved at:
/etc/letsencrypt/live/example.com/fullchain.pem
Your key file has been saved at:
/etc/letsencrypt/live/example.com/privkey.pem
Your cert will expire on YYYY-MM-DD. To obtain a new or tweaked
version of this certificate in the future, simply run certbot
again. To non-interactively renew *all* of your certificates, run
"certbot renew"
- If you like Certbot, please consider supporting our work by:

Donating to ISRG / Let's Encrypt: https://letsencrypt.org/donate
Donating to EFF: https://eff.org/donate-le

其中:

  • /etc/letsencrypt/live/example.com/fullchain.pem 为公钥
  • /etc/letsencrypt/live/example.com/privkey.pem 为私钥

(5)使用 DNS 校验的证书续签时需要重新执行一遍申请流程,但由于证书文件路径不变(新证书将覆盖旧证书),只需要重新使用 Certbot 生成新的校验 Key 并更新到 DNS 即可。

国内域名只能使用 DNS 校验,其他校验方式会报错(参考:Let's encrypt的TLS-SNI安全问题):

1
Client with the currently selected authenticator does not support any combination of challenges that will satisfy the CA. You may need to use an authenticator plugin that can do challenges over DNS.

但 Github 上有不少工具可以通过 DNS 服务商提供的 API 接口实现续签时自动化更新 DNS 记录,例如腾讯云:

查看所有 certbot 申请的证书的有效期:

1
sudo certbot certificates

2.5.3 LetsEncrypt证书+HTTP校验

(1)指定验证方式为 http:

1
ubuntu$ sudo certbot certonly --preferred-challenges http -d example.com

(2)三种方式任选一项即可:

1
2
3
4
5
6
7
8
9
Saving debug log to /var/log/letsencrypt/letsencrypt.log

How would you like to authenticate with the ACME CA?
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1: Nginx Web Server plugin - Alpha (nginx)
2: Spin up a temporary webserver (standalone)
3: Place files in webroot directory (webroot)
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Select the appropriate number [1-3] then [enter] (press 'c' to cancel):

(3)其中,方式 1(nginx)和方式 2(standalone)均为全自动完成,而方式 3(webroot)则需要指定一个目录;在证书续期(renew)时,LetsEncrypt 会在该目录下存放一个临时文件,并告知服务器通过访问域名的一个特定路径来验证是否可以读取到临时文件,因此这种方式还需要配置 Nginx,确保 LetsEncrypt 通过 HTTP 请求验证域名时可以被正确路由到该目录。

(4)以方式 3(WebRoot)为例,按要求指定 WebRoot 的路径:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
Plugins selected: Authenticator webroot, Installer None
Obtaining a new certificate
Performing the following challenges:
http-01 challenge for example.com
Input the webroot for example.com: (Enter 'c' to cancel): /xxx/webroot
Waiting for verification...
Cleaning up challenges

IMPORTANT NOTES:
- Congratulations! Your certificate and chain have been saved at:
/etc/letsencrypt/live/example.com/fullchain.pem
Your key file has been saved at:
/etc/letsencrypt/live/example.com/privkey.pem
Your cert will expire on 2025-xx-xx. To obtain a new or tweaked
version of this certificate in the future, simply run certbot
again. To non-interactively renew *all* of your certificates, run
"certbot renew"
- If you like Certbot, please consider supporting our work by:

Donating to ISRG / Let's Encrypt: https://letsencrypt.org/donate
Donating to EFF: https://eff.org/donate-le

其中:

  • /etc/letsencrypt/live/example.com/fullchain.pem 为公钥
  • /etc/letsencrypt/live/example.com/privkey.pem 为私钥

(5)使用 certbot 手动更新证书

1
2
3
4
5
# 更新系统上所有 certbot 申请的证书:
sudo certbot renew

# 模拟 certbot 更新证书(但不会实际更新):
sudo certbot renew --dry-run

如果 renew 命令没有报错,就可以使用 crontab 周期性执行 renew 进行自动续签了。

(6)使用 crontab 定期自动更新证书

查看已配置的定期任务:

1
sudo cat /etc/crontab

配置定期任务:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
ubuntu$ sudo crontab -e

## 根据提示选择习惯的编辑器:
Select an editor. To change later, run 'select-editor'.
1. /bin/nano <---- easiest
2. /usr/bin/vim.basic
3. /usr/bin/vim.tiny
4. /bin/ed

Choose 1-4 [1]: 2

## crontab 通过 6 个选项配置定期任务,其中各项的含义如下(使用 * 表示任意满足):
## Minute(0~59) Hour(0~23) DayOfMonth(1~31) Month(1~12) DayOfWeek(0~6) Command
## crontab 的默认配置内容如下,
*/5 * * * * flock -xn /tmp/stargate.lock -c '/usr/local/qcloud/stargate/admin/start.sh > /dev/null 2>&1 &'

在打开的编辑器中修改定期时间,并保存退出(以下配置表示每月 1 日执行 certbot 更新证书):

可以使用 sudo certbot renew --dry-run 测试证书更新流程是否能正确执行。

1
0 0 1 * * /usr/bin/certbot renew --quiet

2.5.4 将SSL公私钥配置到Nginx

由于云服务器采用 Nginx 转发请求,因此需要将 SSL 证书的公私钥配置到 Nginx 中。

如果使用 root 用户运行 Nginx,则需要切换至 root 用户再配置。

(1)去除(注释掉)Nginx 默认 Server 配置 /etc/nginx/sites-available/default(注意备份):

1
2
3
4
5
6
server {
# listen 80 default_server;
# listen [::]:80 default_server;
# root /var/www/html;
# server_name _;
}

(2)修改 Nginx 转发配置 /etc/nginx/nginx.conf(注意备份),参考以下部分按实际情况修改:

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
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
user root;
worker_processes auto;
pid /run/nginx.pid;
include /etc/nginx/modules-enabled/*.conf;

events {
worker_connections 768;
# multi_accept on;
}

http {
## ---------- Basic Settings ---------- ##

sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
types_hash_max_size 2048;
# server_tokens off;

# server_names_hash_bucket_size 64;
# server_name_in_redirect off;

include /etc/nginx/mime.types;
default_type application/octet-stream;

proxy_intercept_errors on;

## ---------- Server Settings ---------- ##

# 禁止 IP 直接访问
server {
server_name _;
listen 80 default_server;
listen [::]:80 default_server;
listen 443 ssl default_server;
listen [::]:443 ssl default_server;
return 444;
}

# 所有子域名转发至根域名(需要 DNS 解析),同时匹配多个顶域(TLD)
#
# server_name 支持正则匹配泛域名,其中:
# '~' 表示指定后面的内容为正则表达式,因此后面内容中的特殊符号需要反斜杠转义;
# '^' 表示开始匹配的位置,即判断是否以该符号的下一个部分开头;
# '$' 表示结束匹配的位置,即判断是否以该符号的前一个部分结尾;
# '?<name>{reg}' 表示命名捕获,即获取满足 {reg} 条件的部分,并命名为 name;
#
# 以 ~^(?<name1>.+)\.example\.(?<name2>.+)$ 为例:
# 1. '~' 表示接下来的内容为正则表达式;
# 2. '^' 表示待匹配的内容需要以接下来的部分作为开头;
# 3. '?<name1>.+' 表示查找满足条件 .+ 的部分(且至少匹配一次);
# 4. '$' 表示待匹配的内容需要以前一部分作为结尾;
# 若将规则用于匹配 sub.www.example.com 则:
# 1. name1 = sub.www
# 2. name2 = com
# 若将规则用于匹配 example.com 则匹配失败,因为 name1(对应 '.+')表示必须至少匹配 1 次,也即不能为空。
server {
server_name ~^(?<subDomain>.+)\.example\.(?<domainTLD>.+)$;
listen 80;
listen [::]:80;
listen 443 ssl;
listen [::]:443 ssl;
return $scheme://example.$domainTLD$request_uri;
}

# HTTP 自动转发至 HTTPS
server {
server_name example.com example.cn;
listen 80;
listen [::]:80;
return 301 https://$host$request_uri;
}

# 默认只代理 HTTPS 请求
server {
server_name example.com example.cn;
listen 443 ssl;
listen [::]:443 ssl;

# 如果 LetsEncrypt 选择 Webroot 验证,则需要将 root 配置为同样的 WebRoot 目录:
location /.well-known/acme-challenge/ {
root /xxx/webroot;
}

root /xxx/WebServer;
# autoindex on;
index index.html index.htm index.nginx-debian.html;
error_page 404 500 502 503 504 $scheme://$host/404;
}

## ---------- SSL Settings ---------- ##

ssl_certificate /xxx/fullchain.pem; # 对应前面获取的 SSL 证书的公钥
ssl_certificate_key /xxx/privkey.pem; # 对应前面获取的 SSL 证书的私钥
ssl_session_timeout 5m;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2; # Dropping SSLv3, ref: POODLE
ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:HIGH:!aNULL:!MD5:!RC4:!DHE;
ssl_prefer_server_ciphers on;

## ---------- Logging Settings ---------- ##

access_log /var/log/nginx/access.log;
error_log /var/log/nginx/error.log;

## ---------- Gzip Settings ---------- ##

gzip on;

# gzip_vary on;
# gzip_proxied any;
# gzip_comp_level 6;
# gzip_buffers 16 8k;
# gzip_http_version 1.1;
# gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;

## ---------- Virtual Host Configs ---------- ##

include /etc/nginx/conf.d/*.conf;
include /etc/nginx/sites-enabled/*;
}

注意以上 ssl_certificatessl_certificate_key 需要分别替换为之前获取到的 SSL 证书的公钥和私钥。

(3)验证 Nginx 配置并重启服务:

1
2
3
4
5
6
7
8
ubuntu$ sudo nginx -t

# 如果输出如下,说明配置正确,否则根据 [warn] 或 [error] 的提示修改。
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful

# 测试通过后,重启 Nginx 服务:
ubuntu$ sudo nginx -s reload

2.6 验证网站访问

  • 以 HTTP 协议访问云服务器公网 IP:报错 444.
  • 以 HTTPS 协议访问云服务器公网 IP:报错 444。
  • 以 HTTP 协议访问自定义域名:自动转发至 HTTPS。
  • 以 HTTPS 协议访问自定义域名:正常访问。

如果以上均验证成功,则网站已经正常部署。

2.7 常见异常

如果访问服务器时报错 403,先按上文所述查看 Nginx 的报错日志:

1
ubuntu@~$ vim /var/log/nginx/error.log

(1)directory index of "..." is forbidden

1
yyyy/MM/dd hh:mm:ss [error] 28043#28043: *6 directory index of "/home/ubuntu/Projects/WebServer/" is forbidden, client: XXX.XXX.XXX.XXX, server: XXX.YYY, request: "GET / HTTP/1.1", host: "XXX.YYY"

首先检查 Git Push 是否成功,Hexo 在 Deploy 时,一旦某个文件 Push 失败则 Git 会中断 Push 任务,但只要 Push 任务结束 Hexo 就会提示 INFO Deploy done: git,所以看到这个提示并不一定代表 Push 是成功的,需要检查 Deploy 日志,确保没有提示任何文件写入失败。如果有,大概率是权限问题导致的,可参照上文配置;如果没有,则需要检查 Nginx 配置:

1
2
3
4
5
6
7
8
9
10
11
ubuntu@~$ vim /etc/nginx/nginx.conf

http {
## 默认只代理 HTTPS 请求
server {
// 在默认代理 server 内添加该配置:
index index.html index.htm index.nginx-debian.html;
// 如果不确定网站使用的默认页,则添加如下配置:
autoindex on;
}
}

参考文献