FreshRSS 自托管订阅服务&快速订阅插件
本文将详细介绍 FreshRSS 的自托管完整流程,包括使用 Docker 部署、配置 Nginx 反向代理、进行验证和故障排除,并提供一个油猴脚本以便快速订阅您喜爱的站点。
一、为什么自托管 RSS
RSS 阅读器选择不少,我最后选了 FreshRSS,理由很实际:
- 部署成本低:Docker Compose 一把起,不依赖重基础组件
- 成熟度够:不是那种只能演示、不能长期养的项目
- 多用户支持:自用够了,给朋友开账号也不需要重构
- 扩展性好:原生 RSS/Atom 之外的来源,HTML+XPath 兜底
主要是名气大,别的也没用过 先看一下效果

二、部署目标
我的环境是这样:
- Linux VPS,Docker Compose 部署
- 应用只监听本机回环,不直接暴露公网
- 独立域名,Nginx 反代入口
- 先用 FreshRSS 自带的 SQLite,不急着上 PostgreSQL
应用只跑回环,公网统一收口到 Nginx。这样做的好处:HTTPS、访问控制、日志全在反代层处理,迁移和排障时层次分明。
三、部署:从 docker-compose.yml 说起
3.1 目录准备
/opt/freshrss
├── docker-compose.yml
├── data/
└── extensions/
mkdir -p /opt/freshrss/{data,extensions}
cd /opt/freshrss
3.2 Compose
services:
freshrss:
image: freshrss/freshrss:latest
container_name: freshrss
restart: unless-stopped
ports:
- "127.0.0.1:3010:80" # 【关键】只绑回环,外部无法直连,必须经 Nginx
volumes:
- ./data:/var/www/FreshRSS/data # 用户数据、配置、SQLite 数据库
- ./extensions:/var/www/FreshRSS/extensions # 第三方扩展单独挂,不和数据混
environment:
TZ: Asia/Shanghai # 时区,影响 cron 执行时间
CRON_MIN: "3,33" # 每小时的第 3、33 分钟抓取一次,自定义
volumes 为什么拆成两个?
如果只挂一个 /var/www/FreshRSS 整目录,扩展和运行数据搅在一起。拆开后:
data/存订阅数据、用户配置、数据库文件,容器重建不会丢extensions/装第三方扩展时直接往宿主机目录放文件,容器内自动可见,不用每次docker cp
CRON_MIN 的设置逻辑
FreshRSS 用内置 cron(不是宿主机 crontab)定时抓取订阅源。CRON_MIN: "3,33" 表示每小时的第 3 和第 33 分钟各跑一次,一天 48 次。个人使用够了,也不算激进。订阅源多或服务器带宽有限的话,改成 "8,38" 甚至一天只跑几次也可以。
为什么不用外部数据库?
FreshRSS 默认用 SQLite,数据就在 data/ 目录里一个文件,零维护成本。PostgreSQL 当然可以用,官方镜像支持 DB_HOST 等环境变量,但个人 RSS 阅读器的并发和数据量远到不了 SQLite 的瓶颈。先把服务跑稳,真遇到性能问题再迁也不迟。
3.3 启动
docker compose up -d
docker logs --tail=100 freshrss # 看启动日志,确认无报错
curl -I http://127.0.0.1:3010 # 本机验证,不通就先别往下走
3.4 Nginx 反代
给 FreshRSS 挂独立域名(如 rss.example.com),写一个反代配置。proxy_pass 指向刚才的回环地址,带上必要的转发头:
server {
listen 80;
server_name rss.example.com;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl http2;
server_name rss.example.com;
ssl_certificate /path/to/fullchain.pem;
ssl_certificate_key /path/to/privkey.pem;
location / {
proxy_pass http://127.0.0.1:3010;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
nginx -t && systemctl reload nginx
四、FreshRSS Helper:让没有 RSS 的网站也能订阅
服务搭好了,但很多网站压根不提供 RSS。要么作者没配,要么 RSS 藏在某个 /feed、/rss.xml、/atom.xml 路径里你找不到。
FreshRSS 自身有 HTML+XPath 抓取模式,能解析任意网页结构。但要在添加订阅页手填七八个 XPath 字段,门槛不低,得会写 XPath,得去源码里翻节点,得反复试错。
手动F12试了下抓取XPath路径,我选择放弃 Codex启动!写了一个油猴脚本,解决了以下痛点
| 痛点 | 脚本方案 |
|---|---|
| 不知道 RSS 地址在哪 | 自动探测 /feed、/rss.xml 等常见路径,按靠谱程度排序 |
| 没有 RSS,要写 XPath | 可视化 XPath 拾取器,鼠标点选填好所有字段 |
| XPath 字段多手填烦 | 跳到 FreshRSS 添加页时自动按语义匹配填充 |
工作流程两步:
- 源页面:点按钮,探测 RSS 或拾取 XPath,结果暂存到浏览器
- FreshRSS 添加页:脚本读取暂存数据,自动填进输入框
两个模式靠 URL 自动切换,不用手动干预。
安装
先装脚本管理器(二选一):
| 管理器 | 特点 |
|---|---|
| Tampermonkey | 主流选择,全浏览器支持 |
| Violentmonkey | 开源轻量 |
然后在管理器仪表盘 → 添加新脚本 → 导入解压缩后的 freshrss-helper-lite.user.js → 保存。
首次配置
- 打开任意网页,右下角会出现土黄色「配置 FreshRSS」按钮
- 点击,输入你的 FreshRSS 根地址(如
https://rss.example.com),只填根地址不带后缀 - 保存后按钮变为「RSS通用标志」,以后点它就进入订阅流程
按住按钮可拖动位置
Shift+左键点击:可重新配置FreshRSS 基址
右键点击:直接排除当前网站及其子域名显示订阅按钮
Ctrl+右键点击:管理排除域名列表
五、实战场景
点按钮后脚本的决策逻辑:先判断是不是 GitHub 仓库,再探测 RSS/Atom,都没有就切 HTML+XPath 模式。下面挑几个有代表性的场景走一遍。
场景 1:GitHub 仓库监控
GitHub 自带 Atom 源,这个场景最省事。
打开任意仓库页,点按钮,脚本弹出四个选项:

| 选项 | 源路径 | 用途 |
|---|---|---|
| 订阅 Releases | /releases.atom | 版本发布(最常用) |
| 订阅 Issues | /issues.atom | 新 Issue / 更新 |
| 订阅 Pull Requests | /pulls.atom | PR 动态 |
| 订阅 Commits | /commits.atom | 主分支提交(高频,慎用) |
按需选择后点击相应订阅按钮,自动跳转到FreshRSS添加订阅页面,手动点填充按钮,提交

场景 2:有 RSS 的个人博客
典型:大部分博客都有RSS,但没有放置RSS图标。
- 打开博客首页(不是单篇文章页)
- 点按钮,脚本探测 RSS 路径
- 按推荐路径点击订阅
- 点「添加到 FreshRSS」,新标签打开添加页,点填充图标,提交

场景 3:无 RSS 的个人博客
典型:某独立开发者的 Hexo / Hugo 博客,作者没配 RSS。
- 打开博客首页(不是单篇文章页)
- 点按钮,脚本探测 RSS 路径大概率找不到,自动切到 XPath 拾取模式
- 按顺序拾取(鼠标移到元素上会出现高亮框,点击选中):
| 字段 | 拾取目标 |
|---|---|
| 文章容器(必填) | 每篇文章的根节点,通常是 <article> 或带 class="post" 的 <div> |
| 文章标题(必填) | 标题文字,通常是 <h2> / <h3> |
| 文章内容(必填) | 摘要或正文片段 |
| 文章链接 | 标题外层 <a>,脚本通常自动找到 |
| 文章日期 | <time> 标签 |
| 文章作者 / 缩略图 | 选填,可点「自动」按钮推断 |
- 填完必填三项后点「测试标题/内容」,脚本跑一次 XPath,返回匹配数和命中率
- 验证通过,点「添加到 FreshRSS」,新标签打开添加页,点「手动填充」,提交




六、XPath 拾取技巧
脚本的设计目标是让你不用学 XPath,所有场景靠鼠标点击完成。但了解几个概念能处理疑难情况。
脚本生成的 XPath 风格
优先使用语义化 class,内部字段用 descendant:: 相对定位:
//article[contains(@class, "post")] ← 文章容器
descendant::h2[contains(@class, "title")] ← 标题
descendant::a/@href ← 链接
descendant::time/@datetime ← 日期
descendant::img/@src ← 图片
测试功能
跳到 FreshRSS 之前,点「测试标题/内容」。它会告诉你容器匹配了多少个、标题/内容命中率多少。这是最直接的反馈,比来回切到 FreshRSS 试快得多。
常见测试失败
| 现象 | 原因 | 解决 |
|---|---|---|
| 容器匹配 0 | XPath 写错 / 页面动态渲染 | 等加载完,重新拾取 |
| 容器匹配过多 | XPath 太宽泛(选了通用 <div>) | 选有具体 class 的容器重新拾取 |
| 标题命中 0/N | 标题节点不在预期位置 | 检查是否被 <header> / <a> 包裹,重新拾取 |
| 标题命中部分 | 部分文章结构不同(如置顶帖) | 通常可接受,看实际抓取效果决定 |
七、调试与排错
字段识别报错:FreshRSS 添加页出现 XPath 语法错误,可能是脚本误把纯文本填进了 XPath 字段。检查脚本是否最新版,看 toast 提示的「已跳过(非 XPath)」字段,或重置 freshrss_pending_payload 重新拾取。
Cloudflare 拦截:源页面如果是安全检查页,脚本会主动提醒。FreshRSS 服务器 IP 可能被屏蔽,需要配代理或找替代源。
字段没自动填:字段标签在不同 FreshRSS 版本/语言下可能不一样。手动填那一个字段即可,或报 issue 给脚本作者。
按钮消失:可能是页面 CSP 严格阻止了样式注入、用了 iframe、或加载时机问题。刷新页面等几秒再看。
八、隐私与存储
- 脚本不向任何第三方发送数据
- 只在两种页面运行:你点按钮时的源页面、你的 FreshRSS 添加页
- 探测 RSS 的请求从你浏览器发出,目标网站看到的是你的 IP,和正常访问没区别
- FreshRSS 地址只存在本地浏览器
- 跨标签页数据通过 GM 存储 + localStorage 双写传递
- 重置配置:在 Tampermonkey 里清空脚本存储,或用浏览器开发者工具删除相关 key