注意
本文最后更新于 2023-12-12,文中内容可能已过时。
前几天黑五在大佬推荐下入了台服务器,年付 40 刀,4 core 4G 内存,每月 2T 的流量,性价比要高过一大批国内主机商。闲来无事打算给自己的博客换个新家。
之前博客部署一直用的 vercel 平台托管,虽然免费但是会有容量限制不是长久之计。
DNS 负载均衡方案探讨
但是 vercel 的服务器也不想直接扔了,一开始的思路采用循环 DNS 的方式部署站点,即在 DNS 服务器中配置多个 A
记录,一个到 vercel 的 ip,另一个到 crowncloud 的 ip。
后来发现 vercel 配置 www 域的方式用的 CNAME
记录,而 CNAME
记录不能和 A
记录同时启用,而我希望我博客的最终域名是带 www 三级域名的。又了解到循环 DNS 的缺陷,故放弃了循环 DNS 方案,索性直接将 vercel 作为镜像站保留。
最终站点如下:
主站点:nikunokoya.com
镜像站:vercel.nikunokoya.com
循环 DNS
Round-robin DNS 是基于 DNS 的负载均衡方案,它将多个 IP 地址映射到一个域名,每次请求时,DNS 服务器会将域名对应的 IP 地址返回给客户端,客户端会选择其中一个 IP 地址进行连接。
优势
- 配置简单
- 交给 DNS 提供商处理,不需要自己搭建负载均衡服务器
缺陷
- 无法做到热备,如果有两台服务,如果其中一台服务挂了可能就有一半分用户无法访问
- 不能提供均匀的负载均衡服务
服务器配置
debian 12
服务器系统选择了 Debian 12,还是熟悉的 apt 包管理器。需要注意的一点是 vps 的 locale 配置,crowncloud
提供的系统会配置中文 locale,建议修改为英文 en_US.UTF-8
。
debian locale wiki
arch locale wiki
创建用户
创建自己常用的用户,可以使用 adduser
比较方便,也可以使用 useradd
命令自主性更强。
区别
adduser
命令是 useradd
的前端会使用 perl
脚本中使用 useradd
命令创建用户,提供了较为丰富的交互功能,删除用户对应 deluser
命令,该命令并非所有发行版都可用。
useradd
如果不加参数仅仅是创建用户,不会创建用户的家目录,删除用户对应 userdel
命令。
Debian 系统默认没有 sudo 命令,需要手动安装 apt install sudo
,通过 visudo
修改配置,visudo
默认使用 nano 编辑器,如果不喜欢 nano 可运行 apt purge nano
将 nano 包和配置删除。
sudoers 配置中 all 的含义
1
2
3
4
5
|
# User privilege specification
root ALL=(ALL:ALL) ALL
niku ALL=(ALL) NOPASSWD: ALL
# Allow members of group sudo to execute any command
%sudo ALL=(ALL:ALL) ALL
|
- %sudo 表示 sudo 组
- 第一个 ALL 表示所有主机,如果有多个主机的同样一份 sudo 配置上为给用户授权
- 括号中 (ALL:ALL) 第一个表示作为任何目标用户,第二个表示允许切换的组列表
- 最后 ALL 表示无密码运行任何命令
更详细说明参见 man sudoers
ssh 配置
- 修改 ssh 端口
sshd 的配置文件位于 etc/ssh/sshd_config
,将 Port 22
修改为其他端口。
- 禁止 root 登录
禁止 root 密码登入 PermitRootLogin no
(以上操作在创建好自己的个人用户,并配置好 ssh 秘钥登录后再修改)。
- 单独禁止博客部署用户密码登入
为了安全考虑我为博客服务单独创建了一个低权限的普通用户,通过该用户部署我的 hugo 编译好的 public 文件。
1
2
|
Match User miku
PasswordAuthentication no
|
修改完 sshd 配置后记得重启 ssh 服务 sudo systemctl restart ssh
。
- 在本机生成 ssh 密钥对,将公钥上传到服务器
1
2
3
|
ssh-keygen -t ed25519 -f niku_cc_vps -C "ubuntu PC to niku@vps.nikunokoya.com"
# 公钥会添加到 `~/.ssh/authorized_keys` 文件中,如果没有该文件则创建。
ssh-copy-id -i {{path/to/certificate}} -p {{port}} {{username}}@{{remote_host}}
|
- 修改
.ssh/authorized_keys
文件权限,仅所有者可读写 chmod 600 ~/.ssh/authorized_keys
安装 ufw 防火墙
为了防止自己疏忽启动了一些服务,导致服务器被攻击,打算还是安装一下 ufw 防火墙。
1
2
3
4
5
6
|
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow your_ssh_port/tcp comment 'SSH'
sudo ufw allow http
sudo ufw allow https
sudo ufw enable
|
fail2ban 配置
fail2ban 是一个防止暴力破解的工具,可以监控 ssh 登录失败的次数,超过一定次数后会将该 ip 加入到防火墙黑名单中。
配置 sshd 的 jail 规则,/etc/fail2ban/jail.d/sshd.conf
。ssh 链接失败 5 次会 ban ip 5 分钟。
1
2
3
4
5
6
7
|
[sshd]
backend = systemd
enabled = true
port = your_port
filter = sshd
maxretry = 5
bantime = 300
|
注意这里的 backend 需要选择 systemd,否则日志配置会有冲突,详情见issue#3292
1
2
|
sudo fail2ban-client status #查看状态
sudo fail2ban-client status sshd #查看sshd的详细状态
|
nginx 配置
nginx 安装见文档 nginx: Linux packages
nignx 本身使用 root 启动 master 进程,默认使用 nginx 用户启动 worker 进程,并且 nginx 用户权限很低,没必要使用非 root 用户启动或者 chroot 隔离。
http 与链接配置
参考 archwiki 的建议。
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
|
user nginx;
worker_processes auto;
worker_cpu_affinity auto;
error_log /var/log/nginx/error.log notice;
pid /var/run/nginx.pid;
events {
multi_accept on;
worker_connections 1024;
}
http {
charset utf-8;
sendfile on;
tcp_nopush on;
tcp_nodelay on;
server_tokens off;
log_not_found off;
types_hash_max_size 4096;
client_max_body_size 16M;
include /etc/nginx/mime.types;
default_type application/octet-stream;
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /var/log/nginx/access.log main;
keepalive_timeout 65;
include /etc/nginx/conf.d/*.conf;
include /etc/nginx/sites-enabled/*;
}
|
server 配置
创建两个目录方便管理 sites-available
中存放不同模块的配置文件,sites-enabled
中存放链接到 sites-available
的配置文件。
1
2
3
4
5
6
7
|
mkdir /etc/nginx/sites-available
mkdir /etc/nginx/sites-enabled
# 启动一个站点的配置文件
ln -s /etc/nginx/sites-available/example.conf /etc/nginx/sites-enabled/example.conf
# 停止一个站点的配置文件
unlink /etc/nginx/sites-enabled/example.conf
|
我的博客配置文件如下:
我打算不管 http 还是 https 协议,访问域名 nikunokoya.com
还是 www.nikunokoya.com
都能访问到我的博客,并且 https://www.nikunokoya.com
是最终的目的地址(因为我的不蒜子一直统计的带 www 的站点)。
因此将 80 端口与 443 端口做了 308 重定向到 https://www.nikunokoya.com
,为何使用 308 而不是 301 见下面注释。
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
|
server {
listen 80;
server_name nikunokoya.com www.nikunokoya.com;
return 308 https://www.nikunokoya.com$request_uri;
}
server {
server_name nikunokoya.com;
return 308 https://www.nikunokoya.com$request_uri;
listen 443 ssl; # managed by Certbot
ssl_certificate /etc/letsencrypt/live/nikunokoya.com/fullchain.pem; # managed by Certbot
ssl_certificate_key /etc/letsencrypt/live/nikunokoya.com/privkey.pem; # managed by Certbot
include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot
}
server {
server_name www.nikunokoya.com;
set $base ***;
root $base/public;
index index.html; # Hugo generates HTML
location / {
try_files $uri $uri/ =404;
}
error_page 404 /404.html;
listen 443 ssl; # managed by Certbot
ssl_certificate /etc/letsencrypt/live/nikunokoya.com/fullchain.pem; # managed by Certbot
ssl_certificate_key /etc/letsencrypt/live/nikunokoya.com/privkey.pem; # managed by Certbot
include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot
}
|
308 重定向
浏览器会进行重定向,同时搜索引擎也会更新其链接(用 SEO 的行话来说,意思是“链接汁”(link juice)被传递到了新的 URL)有利于 SEO。
在重定向过程中,请求方法和消息主体不会发生改变,然而在返回 301 状态码的情况下,请求方法有时候会被客户端错误地修改为 GET 方法。
gzip 配置
1
2
3
4
5
6
7
8
9
10
|
gzip on;
gzip_types text/plain text/css application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript;
gzip_static on;
gzip_proxied any;
gzip_vary on;
gzip_comp_level 4;
gzip_buffers 16 8k;
gzip_min_length 1k;
gzip_http_version 1.1;
|
- gzip_types:要采用 gzip 压缩的 MIME 文件类型,其中 text/html 被系统强制启用;
- gzip_static:默认 off,该模块启用后,Nginx 首先检查是否存在请求静态文件的 gz 结尾的文件,如果有则直接返回该 .gz 文件内容;
- gzip_proxied:默认 off,nginx做为反向代理时启用,用于设置启用或禁用从代理服务器上收到相应内容 gzip 压缩;
- gzip_vary:用于在响应消息头中添加 Vary:Accept-Encoding,使代理服务器根据请求头中的 Accept-Encoding 识别是否启用 gzip 压缩;
- gzip_comp_level:gzip 压缩比,压缩级别是 1-9,1 压缩级别最低,9 最高,级别越高压缩率越大,压缩时间越长,建议 4-6;
- gzip_buffers:获取多少内存用于缓存压缩结果,16 8k 表示以 8k*16 为单位获得;
- gzip_min_length:允许压缩的页面最小字节数,页面字节数从header头中的 Content-Length 中进行获取。默认值是 0,不管页面多大都压缩。建议设置成大于 1k 的字节数,小于 1k 可能会越压越大;
- gzip_http_version:默认 1.1,启用 gzip 所需的 HTTP 最低版本;
禁止直接 ip 访问站点
自签一个 ssl 证书
1
2
|
mkdir /etc/nginx/ssl/
openssl req -x509 -nodes -days 3650 -newkey rsa:2048 -keyout /etc/nginx/self-signed-certs/default.key -out /etc/nginx/self-signed-certs/default.crt
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
server {
listen 80 default_server;
listen [::]:80 default_server;
listen 443 ssl default_server;
listen [::]:443 ssl default_server;
server_name _;
# 为 443 端口配置自签名证书(或任意证书),不要使用实际域名证书
ssl_certificate /etc/nginx/self-signed-certs/default.crt;
ssl_certificate_key /etc/nginx/self-signed-certs/default.key;
# 返回 444 关闭连接,或者 403 禁止访问
return 444;
}
|
遇到的问题,历史的 HSTS 缓存干扰
由于之前使用 vercel 部署的博客,而 vercel 中配置 HSTS,因此浏览器中缓存了 307 重定向,导致我在 nginx 中配置 308 重定向后,浏览器中依然是 307 重定向。
如何删除 chrome HSTS 缓存:
chrome://net-internals/#hsts
HSTS
HSTS的作用是强制客户端(如浏览器)使用HTTPS与服务器建立连接。服务器开启HSTS的方法是,当客户端通过HTTPS发出请求时,在服务器返回的超文本传输协议(HTTP)响应头中包含Strict-Transport-Security字段。非加密传输时设置的HSTS字段无效。
比如,https://example.com/ 的响应头含有Strict-Transport-Security: max-age=31536000; includeSubDomains。
certbot 申请 let’s encrypt 证书
certbot.eff.org
certbot 文档
1
|
sudo certbot --nginx -d your_domain -d www.your_domain
|
certbot 会自动配置 nginx 安装证书,并同时 your_domain 的证书包括了 www 域。
1
2
3
4
5
6
7
8
9
10
|
sudo certbot certificates
Found the following certs:
Certificate Name: nikunokoya.com
Serial Number: ***
Key Type: ECDSA
Domains: nikunokoya.com www.nikunokoya.com
Expiry Date: 2024-03-09 16:43:23+00:00 (VALID: 88 days)
Certificate Path: ***
Private Key Path: ***
|
certbot 会自动续签证书,sudo certbot renew --dry-run
测试无错误即可。
github action
github action 可以很方便的为仓库自定义一些 CI/CD 工作流。
当仓库接收到 push 动作时,会触发 github action 工作流。
github action 会启动一个 ubuntu 虚拟机,并检出仓库的全部代码,使用 peaceiris/actions-hugo@v2
action 配置完 hugo 环境后运行 hugo --minify
编译仓库代码,编译成功后使用 burnett01/rsync-deployments@6.0.0
执行 rsync
将编译好的 public 文件传到服务器。
我的博客仓库工作流如下:
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
|
name: Deploy Hugo Site
on: push
jobs:
build:
runs-on: ubuntu-22.04
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
steps:
- uses: actions/checkout@v4
with:
submodules: true
fetch-depth: 0
- name: Setup Hugo
uses: peaceiris/actions-hugo@v2
with:
hugo-version: '0.118.2'
extended: true
- name: Build
run: hugo --minify
- name: Deploy
uses: burnett01/rsync-deployments@6.0.0
with:
switches: -avzr --delete
path: public/
remote_path: ${{ secrets.DEPLOY_PATH }}
remote_host: ${{ secrets.DEPLOY_HOST }}
remote_port: ${{ secrets.DEPLOY_PORT }}
remote_user: ${{ secrets.DEPLOY_USER }}
remote_key: ${{ secrets.DEPLOY_KEY }}
|
注意
concurrency
用于防止多次 push 时触发多次工作流,如果不设置则每次 push 都会触发一次工作流。
burnett01/rsync-deployments
需要配置 DEPLOY_KEY
,该 key 为服务器的私钥,需要在服务器上配置对应的公钥,rsync 默认使用 sftp 的方式传输文件。
引用
GitHub Actions 文档
小鸡配置
How I automated the deployment of my blog using GitHub Actions
使用 Certbot 为 Nginx 自动配置 SSL 证书
Nginx 禁止 IP 访问并防止泄漏 SSL 证书
什么是循环 DNS?