A self-hosted RSS and Atom aggregator with per-user accounts, filter rules, HTML scraping for feeds that only publish excerpts, and an extension system. It replaces Feedly, Inoreader and whatever you moved to when Google Reader closed.
Our verdict on FreshRSS#
FreshRSS is the configurable one, and you should pick it when configurability is the point. It runs on SQLite in a single container, or on PostgreSQL 10+, MariaDB 10.6+ or MySQL 8.0+ when you outgrow that; it serves the Google Reader API and the Fever API so essentially every mobile client works; and its extension system covers the things Miniflux will never add, including full-content scraping via CSS selectors and, since 1.29.0 (May 2026), a bundled LLM classification extension for auto-tagging. That flexibility is the cost too: extensions are third-party code running in your PHP process, and they are the usual thing that breaks on upgrade. Releases come every few months rather than monthly, PHP 8.1+ is the floor, and the README's own benchmark is a Raspberry Pi 1 handling 150 feeds and 22,000 articles with sub-second responses. Choose FreshRSS over Miniflux when you want filter rules, per-feed scraping and several users with different reading habits.
Who FreshRSS is for#
Choose it if
- A household or club where three or four people each want their own account, their own feeds and their own unread counts
- Anyone following feeds that publish excerpts only, since the scraping and full-content extensions pull the article body in
- People with shared PHP hosting or an existing LAMP box who do not want to introduce PostgreSQL for a feed reader
- Readers with heavy filtering needs: mark-as-read rules, per-category sort orders, saved user queries
Look elsewhere if
- You want a fixed, opinionated reader you will never tune; the settings surface here is large and it invites fiddling
- You dislike PHP deployments enough that the extension directory, the cron container and the config file feel like liabilities
- You need a single static binary you can drop on a host with no runtime; that is Miniflux's job
What running it actually looks like#
One container, freshrss/freshrss or ghcr.io/freshrss/freshrss, listening on port 80 internally and usually published on 8080. Mount /var/www/FreshRSS/data for config, the SQLite database and user data, and /var/www/FreshRSS/extensions if you install any. Set CRON_MIN (for example 1,31) or the built-in refresh cron never runs and feeds only update when a page load happens to trigger it, which is the single most common 'FreshRSS is broken' report. Set TZ too, since article timestamps follow it. First run is a web installer that asks for the database choice and creates the admin user. Multi-arch images cover amd64, arm64 and armv7. Upgrades are a pull and a restart; the schema migration runs on first request, and third-party extensions are what occasionally need a version bump afterwards.
Resource profile#
- Memory
- 90 MB idle, around 200 MB in ordinary use. Apache plus PHP-FPM workers; memory is a function of concurrent requests, not feed count. The README notes it runs usably on a Raspberry Pi 1 with 150 feeds and 22k articles, so the floor is genuinely low.
- CPU and acceleration
- Flat except during the refresh cron, which fetches and parses every due feed. Scraping extensions and full-text retrieval multiply that cost; 500 feeds on a Pi means a visibly busy few minutes each cycle.
- Storage growth
- Article bodies dominate. Set a retention policy per feed or the database grows without limit; roughly a few hundred megabytes per 100k retained articles with full content, plus a small favicon cache.
- Operational load
- 2 of 5, Light. One container with SQLite is a file copy to back up and a pull to upgrade. It moves to a 3 if you put it on MariaDB or PostgreSQL, or if you install third-party extensions, which are the usual source of breakage after a version bump.
Figures describe a small single-household install and are the working assumptions behind our stack planner. Your numbers will differ with library size, user count and hardware. See methodology.
Data and backup surface#
Back up the data/ directory and, if you use them, extensions/. With the default SQLite backend data/ contains the databases, config.php, user configs and the favicon cache; stop the container or use sqlite3 .backup rather than copying a live file, because FreshRSS writes on every refresh cycle. On MariaDB, MySQL or PostgreSQL take a dump of the database and keep data/config.php alongside it, since the connection settings and salt live there and a restore without them will not authenticate. Favicons and the thumbnail cache regenerate and can be excluded. Your feed list is also exportable as OPML, which is worth doing on a schedule as a format-independent fallback.
Derived backup shape
SQLite backup. sqlite3 <db> ".backup out.db" while running, or stop the container and copy. A live cp of a WAL-mode SQLite file can restore corrupt.
Traps and surprises#
No CRON_MIN means feeds never refresh
The Docker image only starts its internal cron if you pass CRON_MIN. Leave it unset and FreshRSS looks perfectly healthy, logs nothing alarming, and simply stops showing new articles until someone triggers a refresh in the UI. Set CRON_MIN=1,31 or similar, then confirm on the admin page that the last refresh time actually moves.
trusted_sources makes header auth a trust decision
Proxy authentication works by accepting a Remote-User or X-WebAuth-User header from any IP inside the trusted_sources CIDR list. The docs warn directly that FreshRSS will trust any IP configured there, so if your proxy is not locked down an attacker can simply set the header and become any user. Narrow the CIDR to the proxy's exact address and make the proxy strip inbound copies of that header.
HTTP auth auto-creates users
New users authenticating over HTTP or proxy headers are created automatically on first login. On a public-facing instance behind a permissive proxy that quietly becomes open registration. Disable it with the http_auth_auto_register setting unless you are deliberately using your IdP as the source of accounts.
Third-party extensions break across versions
Extensions run as PHP inside FreshRSS and use internal hooks that change. 1.29.0 alone sanitized extension name and version parsing and added a new CSP-amending function and a new entry-state property for extension authors. After every minor upgrade, check that your extensions still load; a broken one can take the whole page down rather than degrading quietly.
Unbounded retention fills the disk
FreshRSS keeps articles until you tell it not to. With full-content scraping enabled on a few dozen busy feeds, the database grows steadily and SQLite in particular gets slow before it gets alarming. Set a global and per-feed article retention policy early, and run the purge from the admin page once to see how much it reclaims.
Specifications#
- Category
- Feeds, bookmarks and read-it-later
- Licence
- AGPL-3.0 (Copyleft)
- Written in
- PHP
- First release
- 2013
- Maturity
- Mature
- Datastore
- SQLite by default; PostgreSQL 10+, MariaDB 10.6+ or MySQL 8.0+ supported
- Default ports
80- Ships as
- docker, php on any web server, yunohost / cPanel package
- arm64 builds
- Yes
- Identity
- Native OIDC. Form login, HTTP Basic, reverse-proxy headers (`Remote-User` or `X-WebAuth-User` from a CIDR in `trusted_sources`) and OpenID Connect are all supported in core, with auto-registration of new users on first proxy login.
- Replaces
- Feedly, Inoreader, NewsBlur, Google Reader
- Project site
- freshrss.org
- Source
- github.com/FreshRSS/FreshRSS
- Documentation
- freshrss.github.io/FreshRSS
Alternatives to FreshRSS#
Everything else in feeds, bookmarks and read-it-later, closest in operational weight first.
| Tool | Ops load | Idle RAM | Licence |
|---|---|---|---|
| Karakeep | 3, Moderate | 700 MB | AGPL-3.0 |
| Miniflux | 3, Moderate | 40 MB | Apache-2.0 |
| linkding | 1, Set and forget | 90 MB | MIT |
| wallabag | 3, Moderate | 180 MB | MIT |
Where FreshRSS comes up elsewhere#
Head to head
Guides
Sources#
Facts on this page were checked against the project's own material. Where a number is our own estimate rather than a published figure, the text says so.
Published . Last reviewed . Found something out of date? Tell us and we will fix it and log the change.