很多人重新开始用 RSS,不是因为怀旧,而是因为信息流越来越累。社交平台会按算法塞内容,搜索结果里广告和 AI 垃圾越来越多, newsletter 又容易把邮箱变成第二个垃圾场。
RSS 的好处很朴素:你订阅什么,就看什么。没有推荐流,没有热榜绑架,也不需要把阅读习惯交给第三方平台。
FreshRSS 是一个很适合放在 VPS 上的自托管 RSS 阅读器。它轻量、开源、支持多用户、支持网页端和移动端同步,还能和 RSSHub、RSS-Bridge 这类工具配合,把不提供 RSS 的网站也纳入订阅。
这篇文章讲的是用 Docker Compose 在 VPS 上部署 FreshRSS,并用 Caddy 自动申请 HTTPS。我们会采用最简单稳定的 SQLite 默认方案,适合个人和小团队;如果你订阅量很大,后面也会说什么时候该考虑 PostgreSQL。
适合这些场景:
- 你想自建一个 Google Reader / Inoreader / Feedly 替代品。
- 你希望订阅源、已读状态、收藏文章都在自己服务器上。
- 你想在电脑浏览器、手机 App、平板之间同步阅读进度。
- 你需要订阅博客、技术站、GitHub Release、新闻源、播客更新。
- 你想搭配 RSSHub 订阅一些原本没有 RSS 的中文网站。
不太适合这些场景:
- 你只偶尔看几篇文章,用浏览器收藏夹就够了。
- 你完全不想维护 VPS、备份和 HTTPS。
- 你需要非常复杂的团队权限、评论协作或知识库功能。
- 你主要想收藏网页快照,Linkwarden 或 Wallabag 会更接近需求。
FreshRSS 的定位是“阅读器”,不是网页归档系统。它最擅长的是抓取订阅源、同步阅读状态和长期保存文章列表。
第三方 RSS 服务省心,但也有几个问题:
- 免费版订阅数量、刷新频率、搜索功能常有限制。
- 数据和阅读历史在别人平台上。
- 有些服务对中文站点、国内网络、RSSHub 源支持并不好。
- 服务停运或涨价时,迁移成本很烦。
自托管 FreshRSS 的好处是:订阅源 OPML 可以随时导出,数据在自己的 VPS 上,刷新频率自己定,移动端也能用通用 API 同步。
FreshRSS 很轻。官方 Docker 镜像支持 amd64、arm64、arm/v7,个人使用对机器要求很低。
| 场景 | 推荐配置 |
|---|---|
| 个人使用,几十到几百个订阅源 | 1 vCPU / 512MB-1GB RAM / 10GB SSD |
| 多用户或搭配 RSSHub | 1-2 vCPU / 1-2GB RAM / 20GB SSD |
| 大量订阅源、长期保留全文 | 2 vCPU / 2GB RAM / 40GB+ SSD |
如果你的 VPS 已经跑了 Caddy、Uptime Kuma、Vaultwarden、Umami 这类轻服务,FreshRSS 可以一起跑。真正占空间的是文章缓存、图片缓存、SQLite 数据库和扩展目录,不是 FreshRSS 程序本身。
FreshRSS 支持 SQLite、PostgreSQL、MySQL/MariaDB。
个人 VPS 教程我建议默认用 SQLite,理由很简单:
- 少一个数据库容器。
- 备份就是备份
data目录。 - 迁移简单。
- 对 1-5 个用户完全够用。
什么时候考虑 PostgreSQL?
- 多用户很多。
- 订阅源非常多。
- 你已经有统一 PostgreSQL 服务。
- 你希望数据库备份、监控、权限管理更标准化。
这篇文章采用 SQLite 默认方案。它不是“玩具方案”,而是个人 RSS 阅读器最省心的方案。
本文默认:
- Ubuntu 22.04 / 24.04 或 Debian 12。
- 已安装 Docker 和 Docker Compose V2。
- 有一个域名,例如
rss.example.com。 - 域名 A 记录指向 VPS 公网 IP。
- 80、443 端口可开放给 Caddy 自动申请 HTTPS。
创建目录:
sudo mkdir -p /opt/freshrss
sudo chown -R "$USER":"$USER" /opt/freshrss
cd /opt/freshrss
mkdir -p data extensions backups caddy-data caddy-config
目录用途:
| 目录 | 用途 |
|---|---|
data | FreshRSS 配置、用户数据、SQLite 数据库、缓存 |
extensions | 第三方扩展 |
backups | 本地备份 |
caddy-data | Caddy 证书和状态 |
caddy-config | Caddy 配置数据 |
因为本文使用 bind mount,把宿主机目录挂载进容器,所以还要处理写权限。FreshRSS 容器内 Web 用户通常是 www-data,数字 UID/GID 常见为 33:33。在 Linux VPS 上建议执行:
sudo chown -R 33:33 data extensions
sudo chmod -R u+rwX,g+rwX data extensions
如果你的环境里 www-data UID 不同,可以先进入容器确认,或者改用 Docker named volumes。named volumes 更接近官方 Compose 示例,权限问题更少;bind mount 的好处是备份和迁移时路径更直观。
官方文档明确要求备份 data,如果使用第三方扩展,也建议备份 extensions。
创建 .env:
nano .env
写入:
FRESHRSS_DOMAIN=rss.example.com
TZ=Asia/Shanghai
CRON_MIN=7,37
解释一下:
FRESHRSS_DOMAIN:给 Caddy 使用的域名。TZ=Asia/Shanghai:使用中国时区,避免文章时间和刷新时间看起来不对。CRON_MIN=7,37:每小时第 7 分钟和第 37 分钟刷新订阅源,也就是约每 30 分钟一次。
FreshRSS 官方说明:更新脚本不会让某个订阅源过于频繁刷新,因此没必要把计划任务设得太密。个人使用每 20-30 分钟一次就够了。
创建 docker-compose.yml:
nano docker-compose.yml
写入:
services:
freshrss:
image: freshrss/freshrss:latest
container_name: freshrss
restart: unless-stopped
env_file:
- .env
environment:
TZ: ${TZ}
CRON_MIN: ${CRON_MIN}
FRESHRSS_ENV: production
volumes:
- ./data:/var/www/FreshRSS/data
- ./extensions:/var/www/FreshRSS/extensions
ports:
- "127.0.0.1:8080:80"
logging:
options:
max-size: 10m
healthcheck:
test: ["CMD", "cli/health.php"]
timeout: 10s
start_period: 60s
interval: 75s
retries: 3
caddy:
image: caddy:2-alpine
container_name: freshrss-caddy
restart: unless-stopped
env_file:
- .env
ports:
- "80:80"
- "443:443"
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile:ro
- ./caddy-data:/data
- ./caddy-config:/config
depends_on:
- freshrss
这里故意把 FreshRSS 绑定到 127.0.0.1:8080,外网不能直接访问 8080。真正对外的是 Caddy 的 80/443。
如果你追求长期可复现,生产环境可以把 freshrss/freshrss:latest 固定成具体版本标签,例如 freshrss/freshrss:1.29.1。官方说明 latest 是最新稳定版,edge 是滚动分支,不建议新手生产环境直接用 edge。
创建 Caddyfile:
nano Caddyfile
写入:
{$FRESHRSS_DOMAIN} {
encode zstd gzip
reverse_proxy freshrss:80
}
开放防火墙:
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw reload
如果云厂商有安全组,也要放行 80 和 443。Caddy 申请证书失败时,第一优先检查 DNS、系统防火墙和云安全组。
FreshRSS 官方 Caddy 文档也支持子目录部署,但我不建议新手把它挂在 /rss/ 这种路径下。子域名 rss.example.com 更简单,Cookie、API 和移动端同步都少踩坑。
执行:
cd /opt/freshrss
docker compose up -d
查看状态:
docker compose ps
查看日志:
docker compose logs -f --tail=100 freshrss
打开浏览器访问:
https://rss.example.com
第一次进入会看到安装向导。建议选择:
- 语言:简体中文或 English。
- 数据库:SQLite。
- 管理员账号:不要用弱密码。
- 站点地址:填写真实 HTTPS 域名。
如果安装向导里出现权限问题,优先检查 data 和 extensions 是否已经授权给容器内的 www-data 用户。对本文的 bind mount 写法,最常见的修复就是重新执行:
cd /opt/freshrss
sudo chown -R 33:33 data extensions
这套 Compose 已经通过 CRON_MIN=7,37 启用了容器内 cron。
你可以在日志里观察刷新:
docker compose logs --tail=200 freshrss
FreshRSS 官方给了三种刷新方式:
- 容器内 cron:最简单,本文采用。
- 宿主机 cron 调用
actualize_script.php。 - 单独跑一个 cron-only 容器。
不要三种一起开。重复刷新不会让单个源无限加速,反而会增加日志和排查复杂度。
FreshRSS 支持手动添加 RSS/Atom 源,也支持 OPML 导入。
如果你从其他 RSS 服务迁移,通常流程是:
- 在旧服务导出 OPML。
- FreshRSS 后台导入 OPML。
- 等自动刷新跑一轮。
- 检查失败的订阅源。
- 按分类整理。
建议先导入一小批测试,不要一次导入几千个源。很多旧 OPML 里会混着已经失效的网站,第一次刷新可能很慢。
FreshRSS 支持 Google Reader API 和 Fever API。官方文档也说明,移动端推荐使用 Google Reader API,功能更完整。
后台操作:
- 进入设置。
- 在认证相关设置里启用 API 访问。
- 到用户资料里设置 API 密码。
- 打开 API 检查页面,确认配置通过。
移动端可以选择支持 Google Reader API 的客户端,例如 Reeder、NetNewsWire、FeedMe、Read You 等。API 密码和登录密码分开是好事:移动 App 使用 API 密码,主登录密码不要到处填。
常见 API 地址一般类似:
https://rss.example.com/api/greader.php
不同客户端填写方式略有不同,有的只需要填根域名,有的要填 API 路径。遇到登录失败,先打开 FreshRSS 后台的 API 检查页面,它会直接告诉你服务器配置哪里不对。
中文互联网很多站点没有 RSS,或者 RSS 做得很差。这时 FreshRSS 可以搭配 RSSHub。
但有一个安全点必须注意:FreshRSS 默认会阻止访问一些内部地址,避免 SSRF 类风险。如果你把 RSSHub 跑在同一台 Docker 网络里,需要按官方 Docker 文档设置 INTERNAL_HOST_ALLOWLIST,只允许明确的内部服务,例如:
environment:
INTERNAL_HOST_ALLOWLIST: rsshub:1200
不要为了省事写 * 或 0.0.0.0/0。这等于放开所有内部地址访问,风险很高。
如果你不部署 RSSHub,而是直接订阅公开 RSS 源,就不需要这个配置。
SQLite 方案下,核心备份就是 data 和 extensions。但 SQLite 数据库不适合在服务持续写入时直接在线打包当作唯一备份。更稳妥的做法是:备份前临时停止 FreshRSS 容器,完成文件归档后再启动。
创建备份脚本:
nano backup-freshrss.sh
写入:
#!/usr/bin/env bash
set -euo pipefail
APP_DIR="/opt/freshrss"
BACKUP_DIR="$APP_DIR/backups"
DATE="$(date +%F-%H%M)"
mkdir -p "$BACKUP_DIR/$DATE"
cd "$APP_DIR"
# 先尝试生成 FreshRSS 自带数据库快照,便于迁移和细粒度恢复。
# 如果这一步失败,脚本会停止,避免产生“看似成功但实际不完整”的备份。
docker exec --user www-data freshrss cli/db-backup.php
# 停止 FreshRSS,避免在线打包 SQLite 数据库时出现不一致。
docker compose stop freshrss
# 备份配置、SQLite 数据、扩展、Compose 和 Caddy 配置。
tar czf "$BACKUP_DIR/$DATE/freshrss-data.tar.gz" data extensions docker-compose.yml Caddyfile .env
# 重新启动服务。
docker compose start freshrss
# 保留最近 14 天备份。
find "$BACKUP_DIR" -mindepth 1 -maxdepth 1 -type d -mtime +14 -exec rm -rf {} \;
echo "Backup saved to $BACKUP_DIR/$DATE"
加执行权限并测试:
chmod +x backup-freshrss.sh
./backup-freshrss.sh
ls -lh backups
加定时任务:
crontab -e
每天凌晨 3 点备份:
0 3 * * * /opt/freshrss/backup-freshrss.sh >> /opt/freshrss/backups/backup.log 2>&1
这个脚本会短暂停止 FreshRSS,个人阅读器通常可以接受。如果你不能接受停机,就应该把 FreshRSS 自带的 db-backup.php 导出作为数据库主备份路径,并单独备份配置和扩展,不要把运行中的 SQLite 文件在线 tar 当作唯一恢复依据。
本地备份只是第一层。更稳妥的做法是用 rclone 同步到对象存储、另一台 VPS 或 NAS。
恢复前先停止服务:
cd /opt/freshrss
docker compose down
找到要恢复的备份,例如:
ls -lh /opt/freshrss/backups
假设备份目录是 /opt/freshrss/backups/2026-07-09-0300/,解压:
tar xzf /opt/freshrss/backups/2026-07-09-0300/freshrss-data.tar.gz -C /opt/freshrss
如果你要恢复的是 cli/db-backup.php 生成的数据库快照,也可以按 FreshRSS 官方恢复流程使用 cli/db-restore.php。文件级恢复和数据库级恢复不要混着乱用,先确认自己恢复的是整站目录还是数据库导出。
修正 bind mount 权限:
cd /opt/freshrss
sudo chown -R 33:33 data extensions
重新启动:
docker compose up -d
恢复后检查:
- 能否登录。
- 订阅源是否还在。
- 已读状态是否正常。
- 移动端 API 是否还能连接。
- 自动刷新是否执行。
不要只备份不演练。RSS 数据不一定是核心业务,但几年订阅和收藏丢了也很难受。
升级前先备份:
cd /opt/freshrss
./backup-freshrss.sh
如果使用 Docker Compose:
docker compose pull
docker compose up -d
升级后检查:
docker compose ps
docker compose logs --tail=100 freshrss
官方更新文档提醒:跨版本升级最好逐版本走,不要从很老的版本直接跳到很新的版本。长期不维护的实例,建议先看 Release Notes,再决定升级路径。
至少做到这些:
- 只开放 80/443,不要开放 8080。
- 管理员密码使用强密码。
- API 密码和登录密码分开设置。
- 不要把
data、extensions、backups放到 Web 可访问目录。 - 不要随便设置
INTERNAL_HOST_ALLOWLIST=*。 - 不需要公开注册就不要开放多用户注册。
- 定期更新镜像和 VPS 系统补丁。
- 备份同步到异地。
- 如果只自己使用,可以再套 Cloudflare Access、Tailscale 或 WireGuard。
FreshRSS 本身不重,但它会去抓取外部 URL。所有能抓外部网页的服务,都要注意 SSRF 和内网访问边界。
先看容器:
docker compose ps
再看 Caddy:
docker compose logs --tail=100 caddy
重点查 DNS、80/443、防火墙、安全组。Cloudflare 代理模式下如果证书或回源配置不对,也会导致访问异常。
检查 .env 里的 CRON_MIN 是否存在:
grep CRON_MIN .env
再看日志:
docker compose logs --tail=200 freshrss
如果你选择宿主机 cron,就不要再设置 CRON_MIN,避免两套刷新机制同时跑。
按顺序检查:
- 后台是否启用 API 访问。
- 用户资料里是否设置 API 密码。
- 客户端是否选择 Google Reader API。
- API 地址是否填对。
- 反向代理是否正确转发路径。
FreshRSS 后台的 API 检查页面很有用,先看它的 PASS/FAIL,不要盲目改客户端。
这是 FreshRSS 解决不了的问题。FreshRSS 是阅读器,不是万能爬虫。
解决方案:
- 找网站是否有隐藏 RSS。
- 使用 RSSHub。
- 使用 RSS-Bridge。
- 用 FreshRSS 的网页抓取功能处理简单页面。
但越像爬虫,越要注意频率、版权和目标站规则。
常见原因是缓存、图片、扩展或大量历史文章。
可以先确认目录大小:
du -h --max-depth=2 /opt/freshrss | sort -h
官方备份说明里提到,cache 可以跳过,因为 FreshRSS 可以重建缓存。你也可以调整历史文章保留策略,避免无限增长。
这几个工具经常被混在一起,但定位不同。
| 工具 | 主要用途 |
|---|---|
| FreshRSS | RSS 阅读、订阅、已读同步 |
| RSSHub | 把没有 RSS 的网站转换成 RSS 源 |
| Linkwarden | 书签管理和网页归档 |
| Wallabag | 稍后阅读和文章保存 |
如果你的目标是每天看订阅源,选 FreshRSS。如果你的目标是收藏网页并保存快照,选 Linkwarden。如果你想订阅知乎、微博、B 站等复杂来源,FreshRSS 可以搭配 RSSHub。
正式使用前检查:
- 域名已经解析到 VPS。
- 80/443 已在防火墙和安全组放行。
- FreshRSS 只绑定
127.0.0.1:8080。 - Caddy 已成功申请 HTTPS 证书。
-
TZ=Asia/Shanghai已设置。 -
CRON_MIN已设置,且只启用一种刷新方式。 -
data和extensions已授权给容器内www-data用户,或已改用 named volumes。 - 管理员密码已设置为强密码。
- 移动端 API 已启用并测试通过。
- 备份脚本跑通过。
- 至少做过一次恢复演练。
- 如使用 RSSHub,
INTERNAL_HOST_ALLOWLIST只允许明确服务。
FreshRSS 的价值不在于炫技,而在于把信息流重新变成你能控制的东西。
你可以把技术博客、开源项目 Release、新闻站、播客更新、论坛帖子都放进 FreshRSS。每天打开阅读器,只看自己订阅的内容,不被推荐流牵着走。
如果你已经有一台 VPS,FreshRSS 是非常值得自托管的轻量应用。它不吃资源,备份简单,移动端同步成熟,和 RSSHub 搭配后也很适合中文互联网的信息订阅场景。
