这次部署 FreshRSS,真正要解决的问题并不是“把服务跑起来”这么简单。
如果只是临时起一个 RSS 阅读器,很多做法都能工作。但放到已经上线的网站环境里,标准就会变高一些:不能影响主站、不能把临时调试入口长期暴露在外,也不能让域名、证书、回源策略互相打架。
所以这次我更在意的,其实是下面这些事情能不能同时成立:
- FreshRSS 可以稳定安装并正常使用
- 不影响已有主站
- 不能通过服务器 IP 直接访问
- 需要统一走域名、HTTPS 和 Cloudflare
- 后续维护尽量简单,不把部署复杂度做得太高
最终的访问地址是:
https://rss.heyrickishere.com
这次采用的方案
为了尽快稳定上线,这次没有上 Docker,而是直接使用系统原生部署:
- Web 服务器:
Nginx - PHP 运行环境:
php8.1-fpm - 数据库:
SQLite - 应用版本:
FreshRSS 1.28.1
这个选择并不花哨,但很符合当前场景。
原因很直接:
- 服务器本来就在跑
Nginx - 系统里原本没有 Docker,也没有完整 PHP 环境
- FreshRSS 对个人使用来说并不需要一套很重的基础设施
SQLite足够轻,后续如果需要再迁移到MySQL或MariaDB也不晚
对这种偏个人化、长期维护的服务来说,先让结构清晰、排障直接,通常比一开始就堆太多组件更重要。
先确认边界,再开始安装
在真正装 FreshRSS 之前,先确认了服务器现有状态:
- 系统是
Ubuntu 22.04 - 已经有
Nginx在线运行 - 主站已经部署完成
- 默认站点规则已经对直接 IP 访问做了拦截
- 系统里原本没有
docker和php
这一步的意义,是先决定“FreshRSS 应该怎么接进现有结构”,而不是先装完再想办法补救。
如果跳过这一步,后面很容易出现几类问题:
- 把新服务和主站混在同一个目录结构里
- 临时端口忘记回收
- 域名和应用内部地址不一致
- HTTPS 能打开,但跳转、登录或 API 地址还残留旧路径
运行环境怎么补齐
FreshRSS 需要的不是一个特别复杂的后端栈,但 PHP 相关组件要补齐。实际安装的内容包括:
php8.1-fpmphp8.1-curlphp8.1-xmlphp8.1-mbstringphp8.1-sqlite3php8.1-zipphp8.1-intl
这些扩展分别对应抓取订阅源、处理 XML、字符编码、多语言界面、SQLite 支持以及导入导出能力。
从维护视角看,这样的组合有一个明显好处:依赖关系比较清楚,后面排查问题时也更容易定位是 PHP 本身、Nginx 转发,还是应用配置的问题。
安装程序本身时,我更关心“可控”
FreshRSS 最终部署在:
/usr/share/FreshRSS
随后通过它自带的 CLI 工具完成初始化,而不是直接全靠网页安装。初始化时主要确定了几件事:
- 数据库类型使用
SQLite - 创建默认管理员用户
- 启用 API
- 设置站点标题
用 CLI 做初始化的好处,是安装过程更可控,后续如果需要重建或复盘,也更容易还原步骤。
临时验证是必要的,但临时入口不能留着
在正式接域名之前,先用临时端口做了一次内部验证。
这一步主要确认:
- FreshRSS 程序能运行
- PHP-FPM 能正确处理请求
- SQLite 数据库能正常创建
- 管理员账号能初始化成功
但这里有一个很容易被忽视的点:临时验证入口只是过渡,不应该成为最终对外暴露的访问方式。
所以后面在正式域名接通之后,临时 8088 站点配置和防火墙放行都被回收了,没有把测试阶段的路径继续留在线上。
为什么最终一定要拆成子域
这次 FreshRSS 没有接到主站路径下面,而是独立挂到:
rss.heyrickishere.com
这样做的好处很明确:
- 主站和 FreshRSS 的职责边界更清楚
- 不需要把应用挂载在一个更复杂的子路径里
- HTTPS、跳转、缓存和回源策略都更容易单独处理
- 后续如果迁移或更换实现,也不会影响主站主域
与此同时,FreshRSS 应用内部的 base_url 也被改成正式地址:
https://rss.heyrickishere.com
这一步非常关键。因为很多服务即使表面能打开,如果内部 base_url 还停留在旧地址、IP 或临时端口,后面就会在登录跳转、资源地址、API 路径上持续出问题。
HTTPS 不是附加项,而是接入标准的一部分
检查已有证书时发现,原来的证书只覆盖:
heyrickishere.comwww.heyrickishere.com
并不包含:
rss.heyrickishere.com
所以这里为 rss.heyrickishere.com 单独申请并部署了 Let’s Encrypt 证书。这样 FreshRSS 的 HTTPS 是独立完整的,不需要勉强复用主站证书。
从长期维护角度看,这种做法更清晰:主站域名和 RSS 子域各自有明确的证书边界,后续扩展也不会让证书管理越来越乱。
真正关键的一步:不允许绕过 Cloudflare 直接回源
如果只是新增一个子域并把它指到服务器,再打开 Cloudflare 代理,表面上已经能访问了。
但这还不够。
因为理论上,别人仍然可能绕过 Cloudflare,直接访问源站 IP,再伪造 Host 头去请求 FreshRSS。这样一来,“必须通过域名访问”就只剩表面效果,并没有真正落到服务器策略里。
所以这次额外做了 Cloudflare 回源白名单限制:
- 只允许 Cloudflare 官方回源 IP 段访问
rss.heyrickishere.com - 其他来源直接拒绝
这样最终得到的是:
- 正常用户通过
rss.heyrickishere.com访问时,请求先到 Cloudflare,再由 Cloudflare 回源,访问正常 - 如果有人直接打源站 IP,即使知道域名,也不会绕过这层限制
这一步让部署从“能工作”变成了“更符合原本的安全要求”。
收尾时做了哪些清理
正式域名和 HTTPS 都接通之后,又补了几项收尾:
- 删除临时
8088站点配置 - 删除防火墙里对
8088/tcp的放行 - 将
base_url固定到正式域名 - 保持主站和默认拦截规则不变
这类清理很重要,因为很多线上问题不是出在正式配置,而是出在“测试阶段遗留物一直没回收”。
现在的最终状态
部署完成后,FreshRSS 已经满足最初的目标:
- FreshRSS 已安装完成
- 正式入口为
https://rss.heyrickishere.com - 站点已启用 HTTPS
- 访问通过 Cloudflare 代理
- 服务器 IP 不能作为 FreshRSS 的正式入口直接使用
- 临时调试端口已关闭
- 应用内部基础地址已固定为正式域名
这套方案为什么适合当前场景
这次部署方案的重点不是“做得最复杂”,而是“让结构足够清楚,后续维护不别扭”。
它适合当前场景,主要因为三点:
- 主站和 FreshRSS 按域名拆分,职责清晰,不互相污染。
- 访问路径统一走 Cloudflare 和 HTTPS,安全策略一致。
- 使用系统原生
Nginx + PHP-FPM,后续排障和维护都比较直接。
一句话总结
这次部署真正完成的,不只是把 FreshRSS 装起来,而是把它整理成了一个符合现有网站架构、只通过正式域名访问、并且受 Cloudflare 回源策略保护的独立站点。