前言:

我的博客一直架设在NAS上,但访问非常不方便,每次都要手动输入端口。由于还想搭建其他服务,我就在阿里云买了一个九十九元一年的共享型ECS,配置为2核2G3M。这2G内存除去公摊面积扣掉的400MB,再加上Ubuntu系统运行占用的300MB,基本上1G就没了,真是太可恶了。我计划在上面运行的服务还有alist、ts3、wxchat中转docker镜像、探针以及easyn2n组网联机服务等。

谈到博客,有的用户喜欢动态博客,有的则偏爱静态博客,而我是两者都喜欢用。动态博客最好配合CDN加速,当然也可以选择裸奔。我推荐使用国内VPS搭配阿里云ESA或腾讯云EO的免费CDN使用,对于个人博客来讲,这些免费额度基本够用。这里我先不用ESA了,免得以后麻烦,其实博客程序用谁都一样,主要看个人喜好。

再来讲讲图床,写博客难免会用到图片,然而市面上免费的图床总担心跑路或不稳定,自己用VPS搭建又要额外花钱。所以,我们选择使用Cloudflare R2存储桶进行白嫖。虽然需要绑定外币卡或PayPal才能使用标准版,但它有免费额度,个人使用非常充足。记得先把图片压缩,再通过PicGo上传到R2。虽然很少有人会被恶意刷取R2流量,但为了防止被反向薅羊毛,还是需要设置一下。我是用PayPal绑定R2后解绑了银行卡,所以请各位兄弟姐妹莫要刷我。由于Cloudflare服务器在海外,晚高峰加载速度会非常慢,因此还需要套一层EO的CDN进行加速,并利用边缘函数将其转换为WebP格式,从而实现更快的图片加载速度。

准备工作

  • 记得把博客的评论改为审核模式 还有给博客设置定时备份到云盘 研究Fail2ban给网站设置防扫描爆破

  • 国内VPS1台CDN EO国内各1个、国内备案域名1个海外域名1个 CloudFlare R2

  • VPS自己买一个大厂国内的,备案域名就不讲了,没有就不套国内CDN裸奔也可必须俩域名 海外域名绑到CF上 开通CF R2即可

  • EO国内获取地址 这个扫码很好获取激活码,注意别激活到海外去

  • CloudFlare R2 免费开通测 绑定paypal或者卡开通. 解绑pp卡

1Panel安装以及容器安装

1.1Panel安装

bash -c "$(curl -sSL https://resource.fit2cloud.com/1panel/package/v2/quick_start.sh)"
  • 记得放开防火墙一般都自动放开

  • 一共要安装3个容器 halo博客 数据库postgresql OpenResty

  • 由于我们要方便的管理 我们先安装1panel

  • 登录1Panel网页,安装halo。

「应用商店」-「搜索halo」-「安装」

「数据库服务选择postgresql」-「选择去安装」- 「数据库容器不要勾选端口外部访问」

「halo安装界面勾选端口外部访问」-「确认」

  • 应用商店默认安装openresty即可。

2.测试部署Halo

  • 用vps公网IP+8090,看看可以访问halo博客吗,确认无误后进行下一步。

  • 填写一些基础的账号密码站点名称点击确定没问题我们去接入CDN。

3.VPS基础防护

SSH防护

网页1Panel依次点击

「工具箱」-「Fail2ban」-「全部配置」

如果没安装点击快速安装即可。

输入以下代码保存即可。

#DEFAULT-START
[DEFAULT]
bantime = 600
findtime = 300
maxretry = 5
banaction = ufw
action = %(action_mwl)s
#DEFAULT-END

[sshd]
ignoreip = 127.0.0.1/8
enabled = true
filter = sshd
port = 22
maxretry = 1
findtime = 1d
bantime = -1
banaction = iptables-allports
action = %(action_mwl)s
logpath = /var/log/auth.log

开启1panel防火墙

依次点击开启防火墙:

「系统」-「防火墙」

  • 关闭8090端口访问

  • 打开禁止ping

Halo接入EO

1.添加站点

这里要用国内备案域名

依次操作

输入域名」-「开始接入

如果没有去上面扫码抽奖。

绑定至已购套餐」-「免费版」-「我同意协议」-「下一步

依次点击:

全球可用区」-「CNAME」-「下一步

DNS域名解析证明你的归属权,把主机记录和记录值解析完

点击:

验证」-「完成

2.博客接入EO

我们先将博客接入EO,后续在把OSS接入EO。

依次点击:

「域名管理」-「加速域名(主域名或二级域名)」-「源站配置选择IP或解析IP的域名」-「模板选择网站加速」-「下一步」

按照图片解析CNAME后,点击完成等待部署即可 。

3.EO的SSL证书和HTTPS配置

我们要配置HTTPS协议和SSL证书。

点击配置

点击边缘HTTPS证书下面的配置

依次点击:

「申请免费证书」-「确认」

等待部署完成。

4.Halo一键部署

Halo可以选择一键部署,和反向代理,我更推荐一键部署。

依次点击或勾选:

「创建网站」-「一键部署」-「输入你的域名」-「端口设置:443」-「勾选SSL」-「启用HTTPS」-「申请证书」-「确认」

依次选择:

「站点加速」-「规则引擎」-「编辑」

如果遇到接入EO 404 就是被扫描爆破的弄得,一堆不存在的php请求导致EO缓存404界面,解决方法如下设置完去EO清除缓存即可。

新增一个IF2 设置URL path 等于 /console/*和/uc/*和/,节点缓存TTL 不缓存,并点击操作添加状态码缓存TTL 选择404 缓存时间0秒 。

如果遇到更新文章cdn还在缓存旧的资源 请去插件中心下载页面静态缓存插件,以及观察图下的cache

eo缓存是否是miss

5.EO网站加速配置

站点加速设置开关

节点缓存 TTL 浏览器缓存 TTL 都是遵循源站 Cache-Control

HTTP/2 回源 打开

OCSP 装订开

网络优化内的HTTP/2 打开

WebSocket 设置300秒打开

CloudFlare R2 存储设置

下面我们就一步一步教大家如何开启 CloudFlare R2 服务:

这里要把你的海外域名托管到Cloudflare上

小建议提示我自己的[可选]CF R2设置屏蔽海外用户和关闭IPV6

1. 创建 R2 存储桶

官方网址:https://www.cloudflare.com/zh-cn/

打开并注册 CF 账户(不是,你不会还没有CF账户吧?😁)进入「R2 对象存储」:

添加支付信息,这里需要一张外币卡 或者 Paypal

完成之后,就可以「创建存储桶」

点击「创建存储桶」:

存储桶名称:自己填写

  • 位置:亚太地区 或 北美洲西部 (实际速度差不多)

  • 默认存储类:标准(不能选不频繁访问,没有免费额度)

这样就创建完成了!这时候就可以直接在页面上上传和删除等操作。

加你的图床域名,当然主域名要先托管到CF

按需设置CORS 策略等可选:

如果浏览器访问不了在设置这个 在R2设置里面有CORS策略

[
  {
    "AllowedOrigins": [
      "https://你的域名"
    ],
    "AllowedMethods": [
      "GET"
    ]
  }
]

2. 创建 R2 API

按照下面的路径进行操作,账户API、用户API均可:

「R2对象存储」-「API」-「管理API令牌」-「创建API令牌」

其中权限选择「管理员读和写」,对象读和写也可以,不过要指定桶。

创建好后会出现API密钥等信息,请保存好,以后一些插件、软件都会用到

3.关键防护配置

虽然 R2 基本不怕被刷,但是如果你还有这方面的担忧,可以通过下面三步操作基本杜绝这方面的问题:

1. 设置图片缓存规则

设置这个主要是为了进一步防止被刷下载次数(虽然也基本没人去刷CF的R2),先点进去域名:

然后选择「规则」-「页面规则」-「创建页面规则」:

其中:

  • URL:https://img.ssqq.de/*​ 要带https,后面 /*

  • 浏览器缓存 TTL:1天

  • 边缘缓存TTL:1个月(也可以适当降低,如果你经常更换图片的话)

  • 缓存级别:缓存所有内容

  • 源服务器缓存控制:添加但不开启!

这样缓存规则就设置完毕了!

2. 设置速率限制

还通过设置速率限制防止恶意请求:

选择「安全性」-「WAF」-「速率限制规则」-「创建规则」:

其中:

  • 规则名称:随意

  • 字段:URL路径、包含、/​

  • 当速率超过...:100​,10秒钟

  • 然后采取措施…:阻止

这里重点是【当速率超过...】这个选项,推荐100甚至更多一点,不建议填写太低,很容易误伤;意思是同一个 IP 10 秒内请求超过多少张图片,就触发操作(按照你站点图片情况设置)

3. 设置图片防盗链

这个可以按需添加,主要是防止别的网站盗用你的图床的图片,在别的网址引用图床链接就会提示错误,但是直接请求的方式就还是能打开!

选择「安全性」-「WAF」-「自定义规则」:

  • 主机名:等于,img.ssqq.de (图床域名)

  • And:右边添加一个 And

  • 引用方:不包含,www.xiaoge.org(你的博客域名)

  • 然后采取措施...:阻止

4.工具链推荐

CloudFlare 的 R2 是兼容 Amazon S3 对象存储的,所以有很多配合的软件可以使用,例如:

picgo设置
在插件设置中,添加常用插件。
S3插件:用来登录S3的图床
compress-next:用来压缩图片至webp。
watermark:给图片打水印
autoback:用来备份图床

这里有几项配置需要尤其注意。

应用密钥 ID,填写 R2 API 中的 Access Key ID(访问密钥 ID)
应用密钥,填写 R2 API 中的Secret Access Key(机密访问密钥)
桶名,填写 R2 中创建的 Bucket 名称,如创建R2的桶的名字 img
文件路径,上传到 R2 中的文件路径,这里选择使用 {fileName}.{extName} (或者{fullName})来保留原文件的文件名和扩展名。
自定义节点,填写 R2 API 中的「为 S3 客户端使用管辖权地特定的终结点」,即 xxx.r2.cloudflarestorage.com格式的 S3 Endpoint
自定义域名,填写上文生成的https://xxx.r2.dev格式的域名或自定义域名,如我配置的https://img.a.com
ForcePathStyle:no关闭,否则会在最终路径里面显示有桶名。
拒绝无效TLS证书连接 :yes开启,如果出现证书错误可以关闭
ACL访问控制列表:public-read
Bucket前缀:false

URL重写顾名思义 上传后批量替换你的URL为cdn域名
  • 推荐piclist更强大支持压缩:https://piclist.cn/

#URL重写顾名思义 上传后批量替换你的URL为cdn域名
#piclist的url重写在 图床-awss3-Default-点击编辑下拉到最下面的自定义域名 就是url重写
piclist 配置
应用密钥 ID,填写 R2 API 中的 Access Key ID(访问密钥 ID)
应用密钥,填写 R2 API 中的Secret Access Key(机密访问密钥)
桶名,填写 R2 中创建的 Bucket 名称,如创建R2的桶的名字 img
文件路径,上传到 R2 中的文件路径,这里选择使用 {fileName}.{extName} (或者{fullName})来保留原文件的文件名和扩展名。我的全在upload/{fullName}
自定义节点,填写 R2 API 中的「为 S3 客户端使用管辖权地特定的终结点」,即 xxx.r2.cloudflarestorage.com格式的 S3 Endpoint
自定义域名,填写上文生成的https://xxx.r2.dev格式的域名或自定义域名,如我配置的https://img.a.com
ForcePathStyle:no关闭,否则会在最终路径里面显示有桶名。
拒绝无效TLS证书连接 :yes开启,如果出现证书错误可以关闭
ACL访问控制列表:public-read
Bucket前缀:false

#图片压缩建议改为75-80 不要改成100那是完全不压缩

  • ScreenToGif这个自带压缩-官网:https://www.screentogif.com

  • ScreenToGif-github:https://github.com/NickeManarin/ScreenToGif

  • 图片压缩:https://tinypng.com

CloudFlare-R2-OSS图床接入EO

前提你应该已经把海外域名绑定到了CF上

1.EdgeOne 添加 cdn 域名

1.新增站点」-输入域名-「开始接入」-去CF-DNS绑定CNAME-「域名管理

  • CF解析,证明是你的域名。

点击域名管理

2.配置域名设置回源站点为 R2 的自定义域名站点

记住先择网站加速 自动帮你缓存1个月 免得设置

3.域名服务商配置域名的 cname 指向 EO 的 cname 地址

设置站点加速规则引擎

点击编辑

删除Host里自带的节点缓存TTL

在图片缓存这加一个节点缓存TTL 30天 开启强制缓存

浏览器缓存TTL 设置1小时或1天都可 最好跟你CF 规则页面内的浏览器缓存TTL一致

4.配置 https 证书

此时已经完成了 cdn 的操作,接下来我们配置边缘函数来将 png 转换为 webp 图片减小图片体积

5.图片转换

继续

继续

边缘函数带轻型防盗链

边缘函数-函数管理-编辑代码

覆盖以下代码即可拥有轻度防盗链

把你的域名四个字改为你的博客域名不带http和https等 你的域名写博客地址

async function handleEvent(event) {
  const { request } = event;
  const url = new URL(request.url);
  
  // --- 1. 防盗链逻辑 (Referer 校验) ---
  const referer = request.headers.get('Referer');
  
  // 允许的域名白名单
  const allowDomains = ['你的域名']; 
  
  // 逻辑:允许空 Referer (直接打开图片);若有 Referer 则必须匹配白名单
  let isAuthorized = true;
  if (referer) {
    try {
      const refUrl = new URL(referer);
      // 检查 Referer 是否以白名单域名结尾
      isAuthorized = allowDomains.some(domain => refUrl.hostname.endsWith(domain));
    } catch (e) {
      isAuthorized = false;
    }
  }

  // 如果校验不通过,直接响应 403,不再回源
  if (!isAuthorized) {
    return new Response('Forbidden: Unauthorized Referer', { status: 403 });
  }

  // --- 2. 图片优化逻辑 ---
  const accept = request.headers.get('Accept');
  const option = { eo: { image: {} } };

  // 检查客户端是否支持 WebP 格式
  if (accept && accept.includes('image/webp')) {
    option.eo.image.format = 'webp';
  }

  // 向源站(CF CDN / R2)请求资源
  const response = await fetch(request, option);
  return response;
}

addEventListener('fetch', event => {
  event.passThroughOnException();
  event.respondWith(handleEvent(event));
});

6.添加触发规则

新增触发规则

注意触发规则写的是eo图床地址,不是博客地址.

EO基础防护

禁止海外访问EO

待定看看会不会影响CF 不影响再发。 CF也可以设置全球禁止只允许China

开启防止盗刷

部署完成。

常见问题

halo导入备份恢复

有的导入超过60MB就会出现上传失败,记得在openresty-性能调整-client_max_body_size
默认60MB根据你的压缩包大小调整最高800MB就是eo的上线 设置完改回60MB

halo批量替换

域名替换

都有过迁移博客要批量替换域名或者图片前缀的问题 halo2版本已经不支持数据库替换了。

如果替换域名请看这篇文章:点我

本地图片替换到对象存储

如果是替换本地的图片到对象存储请看下面的代码 本地图片默认upload文件夹,你需要先把在对象存储内创建upload文件夹,再把图片上传到对象存储的upload文件夹内。

先安装node.js 官网:点我

安装后,先去halo备份下载下来有个extensions.data文件,解压放到桌面新建1文件夹内。然后在1文件夹创建一个xxx.js文件,把下面代码粘贴进去,修改你要替换的对象存储域名域名,在cmd内cd到1文件夹 执行即可 node xxxx.js 他会告诉你替换了多少,把extensions.data文件放入linux系统的vps上 再把workdir文件夹压缩上去再解压出来 extensions.data和workdir文件夹放到一起 cd到目录 用zip -r xxx.zip ./* 来压缩 用halo备份工具恢复即可完成替换。

注意脚本生成的叫extensions-out.data 记得改为extensions.data。

const fs = require('fs');

/* ======================================================
   ⭐ ① Halo 备份文件路径(必须改对)
   ------------------------------------------------------
   input  = 原始 extensions.data
   output = 修改后的新文件(恢复用)
   Windows 路径注意用 \\ 
   ====================================================== */
const inputFilePath  = 'C:\\Users\\22265\\Desktop\\1\\extensions.data';
const outputFilePath = 'C:\\Users\\22265\\Desktop\\1\\extensions-out.data';


/* ======================================================
   ⭐ ② 图片访问域名前缀(最常改的地方)
   ------------------------------------------------------
   例:
   https://img.99yu.top
   https://cdn.example.com
   https://r2.xxx.workers.dev
   ====================================================== */
const PREFIX = 'https://img.99yu.top';


/* ======================================================
   ⭐ ③ 图片存放路径规则
   ------------------------------------------------------
   默认是 Halo 的 /upload/ 目录
   如果你以后换成 /images/ 或 /assets/
   只需要改这里
   ====================================================== */
const UPLOAD_PATH = '/upload/';


/* ======================================================
   ⭐ ④ 支持的图片后缀
   ------------------------------------------------------
   想多支持一个就加一个 | 后缀
   不要写点 .
   ====================================================== */
const IMAGE_EXTENSIONS = 'png|jpe?g|webp|gif|svg|avif';


/* ====================== 以下不用改 ===================== */

/**
 * base64 解码
 */
function decodeBase64(base64) {
  return Buffer.from(base64, 'base64').toString('utf8');
}

/**
 * base64 编码
 */
function encodeBase64(str) {
  return Buffer.from(str, 'utf8').toString('base64');
}

/**
 * 根据上面的可配置项动态生成正则
 */
const IMAGE_REGEX = new RegExp(
  `(?<!https?:\\/\\/)(${UPLOAD_PATH.replace(/\//g, '\\/')}[^\\s"'()]+?\\.(${IMAGE_EXTENSIONS}))`,
  'gi'
);

// 读取文件
const raw = fs.readFileSync(inputFilePath, 'utf8');
const json = JSON.parse(raw);

let hitCount = 0;
let replaceCount = 0;

// 主处理逻辑
for (const item of json) {
  if (!item.data) continue;

  const decoded = decodeBase64(item.data);

  if (decoded.includes(UPLOAD_PATH)) {
    hitCount++;

    const replaced = decoded.replace(IMAGE_REGEX, (match) => {
      replaceCount++;
      return PREFIX + match;
    });

    if (replaced !== decoded) {
      item.data = encodeBase64(replaced);
    }
  }
}

// 写入新文件
fs.writeFileSync(
  outputFilePath,
  JSON.stringify(json, null, 2),
  'utf8'
);

// 输出结果
console.log(`🔍 命中含图片路径的记录数: ${hitCount}`);
console.log(`🖼️ 实际替换图片引用次数: ${replaceCount}`);
console.log(`🎉 完成: ${outputFilePath}`);

虚拟内存设置

关闭内核节省256m内存

阿里云会扣256m内存

halo博客也不开启端口对外开放

  • 测试完记得进1panel应用商店-已安装内把halo的勾选端口外部访问关闭然后点击重建,目的在于更安全的隐藏源站ip

halo插件

halo邮件设置

halo官方说明:https://docs.halo.run/user-guide/settings/#%E9%82%AE%E4%BB%B6%E9%80%9A%E7%9F%A5
SMTP 主机:smtp.qq.com
SMTP 端口:465
加密方式:SSL
用户名:你的QQ@qq.com
密码:QQ邮箱授权码
发件人地址:你的QQ@qq.com
发件人名称:你的博客名(随便)

Fail2ban网站设置防扫描爆破

还有给博客设置定时备份到云盘