SSD Nodes Learn 🎉 VPS from $4.99/mo
Guides Matt ConnorBy Matt Connor

Self-hosted RSS readers compared on a VPS

Miniflux, FreshRSS, CommaFeed, yarr and Tiny Tiny RSS on a VPS: memory budgets, database needs, Fever and Google Reader API support, and upgrade behaviour.

Which self-hosted RSS reader fits a small VPS

Miniflux is the self-hosted RSS reader to put on a small VPS. It is one Go binary next to PostgreSQL. It speaks the Fever and Google Reader APIs, so third-party phone apps connect, and an upgrade is one docker compose pull. Choose FreshRSS instead when you want extensions and one container with SQLite inside it.

Five readers are worth the disk on a VPS: Miniflux, FreshRSS, CommaFeed, yarr and Tiny Tiny RSS. This page compares what actually differs between them: the memory each stack needs, the database each one forces on you, the sync API your phone app needs, and what happens on upgrade day. Every figure here is either published by the project or plain arithmetic, and the text says which. None of it is a benchmark of your hardware, so measure your own box with docker stats.

The five readers, one paragraph each

Miniflux is written in Go and ships as a single statically compiled binary. Its documentation is blunt about the one hard dependency: it "works only with PostgreSQL". There is no SQLite mode. It offers a REST API, a Fever compatible API and a Google Reader compatible API, plus OPML import and export. Full text search is handed to PostgreSQL, which is part of why the database is not optional.

FreshRSS is PHP and runs as one container holding both the web server and the application. SQLite is the default database and needs no second service, while PostgreSQL and MySQL are supported for bigger installs. It speaks the Google Reader API and the Fever API. Installing it is already covered in our FreshRSS on a VPS walkthrough, so this page compares it rather than repeating the install.

CommaFeed is Java on Quarkus with a layout that copies Google Reader. Its database is chosen at build time, not at run time, so the project publishes one image per database: athou/commafeed:latest-h2 for the embedded H2 database, athou/commafeed:latest-postgresql for PostgreSQL, and further variants for MySQL and MariaDB. It exposes a REST API and a Fever compatible API.

yarr (yet another rss reader) is one Go binary with SQLite embedded, and it needs no container at all. Plain ./yarr listens on 127.0.0.1:7070. The flags are short: -addr 0.0.0.0:7070 -auth alice:secret opens it to the network behind a password, and -db /data/yarr.db puts the database where you want it. It has a Fever compatible API. Its newest tagged release is v2.8, from July 2024, checked August 2026, so treat it as finished software rather than actively developed.

Tiny Tiny RSS is the oldest of the five and the heaviest to run. The official Docker setup is four services: a PostgreSQL container, a PHP-FPM application container, a separate updater container that fetches feeds, and an nginx container in front. The documentation states plainly that "this setup uses PostgreSQL". It has its own JSON API, which its Android client and several third-party apps speak. Fever is not part of it.

How much memory each stack needs

The figures below are budgets, not measurements: the memory ceiling each stack should live under on a small VPS. CommaFeed's number is the project's own published example, which caps the container at 256 MB. The others are ceilings that leave headroom for the feed fetcher, which is the part that spikes when a refresh cycle starts.

ChartMemory ceiling per reader stack, in MB
The data behind this chart
[
  {
    "label": "yarr (SQLite)",
    "containers": 1,
    "mem_limit_mb": 128
  },
  {
    "label": "FreshRSS (SQLite)",
    "containers": 1,
    "mem_limit_mb": 256
  },
  {
    "label": "CommaFeed (H2)",
    "containers": 1,
    "mem_limit_mb": 256
  },
  {
    "label": "Miniflux + Postgres",
    "containers": 2,
    "mem_limit_mb": 320
  },
  {
    "label": "Tiny Tiny RSS",
    "containers": 4,
    "mem_limit_mb": 640
  }
]

yarr sits lowest at 128 MB because it is one binary and one SQLite file, with no database server and no language runtime under it. Miniflux needs 320 MB across 2 containers, and most of that belongs to PostgreSQL rather than to Miniflux. Tiny Tiny RSS is the outlier at 640 MB across 4 containers, because the application, the updater, the database and the web server are four separate processes with four separate heaps.

Set these as real limits rather than hopes. Memory limits in Docker Compose covers the syntax and what a container does when it reaches the ceiling. A container with no limit does not fail politely on a full box: the kernel picks a victim process and kills it, and the victim is often not the container that caused the pressure.

Which database each reader forces on you

The database is the biggest operational difference between these five. It is a larger decision than any difference in the user interface, because it decides your backup procedure and your upgrade risk.

PostgreSQL is required by Miniflux and by the official Tiny Tiny RSS setup. It buys real full text search and safe concurrent writes. It costs a second container, a volume, and one recurring problem: the official PostgreSQL images cannot migrate data between major versions in place. The Tiny Tiny RSS documentation says so directly, and warns that "official PostgreSQL containers have no support for migrating data between major versions". Your realistic options are to pin the old major version, or to dump and restore with pg_dump and pg_restore. Plan for that once every year or two.

SQLite is FreshRSS's default and yarr's default. One file, no server, no port, no password. It holds up well for one person with a few hundred feeds, and it slows down when several users write at once, which is when FreshRSS's PostgreSQL option starts to earn its keep. yarr added optional PostgreSQL support in v2.7, but the embedded file is the normal way to run it.

H2 is CommaFeed's embedded default, and it deserves a moment of thought before you start, because CommaFeed picks its database when the image is built. Moving from H2 to PostgreSQL later is not a configuration change. It is a different image plus a data migration you have to perform yourself, so decide before you have a year of read history in the box.

Will your phone app work

This question decides more than people expect, because the web interface is only half of how a feed reader gets used.

Miniflux speaks a Fever compatible API and a Google Reader compatible API, so most iOS and Android clients connect to it. FreshRSS speaks the same two, and its own documentation ranks them: the Google Reader API is "best" with full feature support, while the Fever API has "limited features and less efficient" behaviour. FreshRSS also needs two steps before any app can log in. Enable "Allow API access (required for mobile apps)" under Authentication, then create an API password in the user profile. Skipping the API password gives an authentication failure in the app while the web login keeps working, which is confusing until you know where to look.

CommaFeed and yarr both expose a Fever compatible API and nothing else, so they work with Fever capable clients and not with apps that only speak Google Reader. Tiny Tiny RSS has its own API instead, which means you need a client written for it. Check that your preferred app supports the reader before you import 300 feeds into it.

A working compose file for a 1 GB box

This is the Miniflux stack, adapted from the project's own Docker example as of August 2026. The published port is bound to loopback, the listen address is set explicitly, and both containers carry a memory limit.

services:
  miniflux:
    image: miniflux/miniflux:latest
    restart: unless-stopped
    ports:
      - "127.0.0.1:8080:8080"
    depends_on:
      db:
        condition: service_healthy
    environment:
      - DATABASE_URL=postgres://miniflux:CHANGE_ME@db/miniflux?sslmode=disable
      - LISTEN_ADDR=0.0.0.0:8080
      - BASE_URL=https://rss.example.com/
      - RUN_MIGRATIONS=1
      - CREATE_ADMIN=1
      - ADMIN_USERNAME=admin
      - ADMIN_PASSWORD=CHANGE_ME_TOO
      - POLLING_FREQUENCY=60
    healthcheck:
      test: ["CMD", "/usr/bin/miniflux", "-healthcheck", "auto"]
    mem_limit: 128m
  db:
    image: postgres:18
    restart: unless-stopped
    environment:
      - POSTGRES_USER=miniflux
      - POSTGRES_PASSWORD=CHANGE_ME
      - POSTGRES_DB=miniflux
    volumes:
      - miniflux-db:/var/lib/postgresql
    healthcheck:
      test: ["CMD", "pg_isready", "-U", "miniflux"]
      interval: 10s
      start_period: 30s
    mem_limit: 192m
volumes:
  miniflux-db:

Three lines in that file are the ones people get wrong. LISTEN_ADDR=0.0.0.0:8080 is set because the binary's documented default is 127.0.0.1:8080, and a process bound to loopback inside a container cannot be reached through the published port, so you get a connection reset with a container that looks healthy. The volume path /var/lib/postgresql matches PostgreSQL 18; version 17 and earlier store data in /var/lib/postgresql/data, and mounting the wrong path means the data directory is not on the volume at all, so everything vanishes the next time the container is recreated. 127.0.0.1:8080:8080 keeps the port off the public internet, because publishing a port without an address writes a rule into a chain that ufw does not manage. Docker ports bypass ufw explains that mechanism, and a Traefik reverse proxy is how you put TLS in front of it.

docker compose up -d
docker compose ps
docker compose logs -f miniflux
docker stats --no-stream

docker compose ps should list both services as running, with the database marked healthy. The first start of Miniflux logs its schema migrations, which is what RUN_MIGRATIONS=1 triggers. docker stats --no-stream prints the live memory column, and that is the number to compare with the ceilings in the chart above. If the Miniflux container restarts in a loop, read its log: connect: connection refused means it started before PostgreSQL was ready to accept connections, which is exactly what the service_healthy condition prevents, so check that the condition survived your edits. If Compose itself is new to you, Docker Compose basics on a VPS covers the file layout first.

What will not fit on a 1 GB box

Tiny Tiny RSS is the one to skip. Its official four service stack runs on a 1 GB VPS when that VPS does nothing else, and it does not run there next to another database-backed application and a reverse proxy. Four services means four sets of overhead, and one of them is PostgreSQL.

CommaFeed fits, but only with the H2 image and the 256 MB cap that the project's own example sets. The pairing that breaks a small box is a JVM next to a separate database server, because a JVM takes whatever headroom you leave it. CommaFeed's documentation points at -Xmx256m as a hard limit and at OpenJ9 as "a more memory-efficient alternative to the HotSpot JVM", which tells you where its memory goes.

When a box does run out, the kernel out of memory killer chooses a process and terminates it. dmesg -T shows a line like Out of memory: Killed process 1234 (java), and the container simply disappears from docker compose ps with no message in the application log, because the application never got to write one.

How upgrades behave on each one

  • Miniflux: docker compose pull && docker compose up -d, with schema migrations applied at start while RUN_MIGRATIONS=1 is set. The upgrade risk is not Miniflux. It is the PostgreSQL major version underneath it.
  • FreshRSS: pull the new image. With SQLite there is no database engine to upgrade, so the usual breakage comes from third-party extensions that have not kept up.
  • CommaFeed: pull the image variant that matches your database. Switching from latest-h2 to latest-postgresql does not carry your data across.
  • yarr: replace the binary and keep the database file. With no release since v2.8 in July 2024, checked August 2026, there is usually nothing to upgrade.
  • Tiny Tiny RSS: docker compose pull && docker compose up -d. Schema migrations run automatically, and the interface redirects you to a migration screen when one needs confirmation.

Take a database dump before any of these, not after.

docker compose exec -T db pg_dump -U miniflux miniflux | gzip > miniflux-$(date +%F).sql.gz

What a refresh interval costs in bandwidth

The numbers below are arithmetic, not a measurement. They assume 100 feeds, one request per feed per interval, and 40 KB per response. Real traffic runs lower whenever a server honours conditional requests, and higher when feeds carry full article text.

ChartMonthly fetches and bandwidth for 100 feeds at 40 KB per response
The data behind this chart
[
  {
    "label": "Every 5 minutes",
    "fetches_per_month": "864,000",
    "gb_per_month": 34.6
  },
  {
    "label": "Every 15 minutes",
    "fetches_per_month": "288,000",
    "gb_per_month": 11.5
  },
  {
    "label": "Every 30 minutes",
    "fetches_per_month": "144,000",
    "gb_per_month": 5.8
  },
  {
    "label": "Every 60 minutes",
    "fetches_per_month": "72,000",
    "gb_per_month": 2.9
  }
]

A five minute interval on 100 feeds is 864,000 requests and roughly 34.6 GB a month. Hourly polling is 72,000 requests and about 2.9 GB. Miniflux ships with POLLING_FREQUENCY set to 60 minutes, which is the last row of that chart, and that default is right for almost everyone. An article does not arrive sooner because you asked for it more often.

Conditional requests are what keep the real number below the arithmetic. A reader that stores the ETag and Last-Modified headers a feed returned sends them back as If-None-Match and If-Modified-Since, and a server with nothing new answers 304 Not Modified with no body. The connection still costs a handshake, but not the payload. Feeds that ignore conditional requests hand you the whole document every single time, so a few large feeds can dominate your transfer bill on their own.

Polling hard also gets you blocked. A server that decides you are hammering it replies 429 Too Many Requests, and some sites answer 403 instead. Miniflux records the last error against the feed itself, so the feed list is the first place to look when one feed stops updating while the rest keep working.

Feeds die, and an OPML file is not a backup

Feeds rot faster than you expect. Domains lapse, sites move to a platform with no feed, and a URL that used to serve XML starts serving an HTML error page with a 200 OK status. That last case is the awkward one: the fetch succeeds, the parse fails, and your reader records a parsing error rather than a network error. Once a year, sort the feed list by last updated and delete what has gone silent.

An OPML export is your subscription list. It holds feed URLs and folder names. It does not hold read state, starred articles, per-feed settings, filter rules, or the article text you saved. Import that OPML into a fresh install and you get your feeds back with every article you ever read marked unread again.

The backup that matters is the database. For PostgreSQL, the pg_dump command above is the whole job. For a SQLite reader such as FreshRSS or yarr, stop the writer and copy the file, or take a consistent copy while it runs with sqlite3 yarr.db ".backup '/tmp/yarr-backup.db'". A plain cp of a database that is being written can produce a file that will not open later, because the copy captures a half-finished write. Then push those files off the box on a schedule, which is what restic backups on a VPS are for, and restore one into a scratch container at least once so you know the procedure works.

A feed reader is one of the cheapest services to run yourself, which is why it appears on every list of things worth self-hosting in 2026. Put a self-hosted SearXNG instance next to it and both your reading and your searching stay on hardware you control.

FAQ

Which self-hosted RSS reader uses the least memory?

yarr. It is a single Go binary with SQLite compiled in, so there is no database server and no language runtime beside it, and a 128 MB ceiling is comfortable. The trade is maintenance and features: its newest release is v2.8 from July 2024, and it speaks only the Fever API. If you want an actively developed project at a similar footprint, Miniflux plus PostgreSQL at 320 MB is the better answer.

Can I run a self-hosted RSS reader on a 1 GB VPS?

Yes. Miniflux with PostgreSQL fits inside about 320 MB when you set mem_limit on both containers, and FreshRSS with SQLite fits in one container. The one to avoid on 1 GB is the official Tiny Tiny RSS stack, which is 4 services including its own PostgreSQL. Always set memory limits, because an unlimited container on a full box gets a process killed by the kernel, and the process chosen is often the database rather than the application at fault.

Which of these work with iOS and Android RSS apps?

Miniflux and FreshRSS speak both a Fever compatible API and a Google Reader compatible API, so nearly any mobile client connects. CommaFeed and yarr offer the Fever API only. Tiny Tiny RSS uses its own API, so you need a client built for it. On FreshRSS you must also enable API access under Authentication and set a separate API password in the profile, or the app fails to log in while the website still works.

Is an OPML export a backup of my RSS reader?

No. OPML holds feed URLs and folders, so it rebuilds your subscription list and nothing else. Read state, starred items, filter rules and article text all live in the database. Back up the database itself with pg_dump for PostgreSQL, or a .backup command for SQLite, and copy the result off the server.

Does Miniflux support SQLite?

No. The project documentation says it "works only with PostgreSQL", and full text search is implemented with PostgreSQL features, so there is no lighter mode to switch to. If you want a feed reader with no database container at all, run FreshRSS with its default SQLite backend or yarr with its embedded file.