Miniflux: RSS That Survives the Feed Apocalypse

A no-JavaScript feed reader that has outlived every "RSS is dead" think piece

Contents

RSS has been declared dead roughly once a year since Google Reader shut down in 2013. It never actually died; it just stopped being convenient, because every “reader” that survived the culling turned into a social network, an ad platform, or a subscription you pay to read things you already subscribed to elsewhere. I went looking for something that just fetched feeds and showed me the text, and I ended up running Miniflux on a small VM for the last two years, syncing over a hundred feeds, without once thinking about it. That is the highest compliment I can pay a piece of software: I forgot it existed because it never broke.

Miniflux is a self-hosted feed reader written in Go, backed by PostgreSQL, with a server-rendered interface that ships almost no JavaScript. It does not try to be a “content platform.” It fetches feeds, deduplicates entries, strips tracking junk, and gets out of the way. That sounds like faint praise until you have used the alternatives.

Why minimalism wins here

Advertisement

Most modern feed readers accrete features because that is what funded products do: recommendation engines, social sharing, “discover” tabs, newsletter conversion, AI summaries nobody asked for. Every one of those features is also an attack surface, a JavaScript bundle, and a reason the page takes two seconds to render a list of headlines. Miniflux takes the opposite bet. It is one Go binary, one database, and a UI you could describe over the phone: a list of unread entries, a reading pane, keyboard shortcuts to move through them.

The minimalism pays off in three concrete ways I actually notice. First, speed — pages render server-side in single-digit milliseconds because there is no client-side framework rehydrating a virtual DOM for a list of blog titles. Second, longevity — the project has had the same maintainer, Frédéric Guillot, since 2013 (it was called “Miniflux” from the start, rewritten in Go in 2018), and the feature set has barely changed because there is nothing left to add that would make it better at its one job. Third, and this is the one that sold me: it survives feeds behaving badly. Malformed XML, feeds that redirect through three URLs, sites that serve text/html with an .xml extension out of spite — Miniflux’s parser (gofeed underneath, extended with its own sanitiser) shrugs at almost all of it, where I have watched other readers just drop the feed silently.

It also does the un-glamorous things well: it de-duplicates entries by GUID and content hash so a publisher re-pushing an old post doesn’t flood your unread count, it rewrites feed content to strip tracking pixels and known ad-injection patterns, and it can proxy images so remote sites can’t fingerprint you by image request just because you subscribed to their RSS feed.

Deploying it

Miniflux needs exactly one dependency: PostgreSQL. No Redis, no search cluster, no message queue. Here is the compose file I run, trimmed of anything identifying:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
# docker-compose.yml
services:
  miniflux:
    image: miniflux/miniflux:2.2
    container_name: miniflux
    restart: unless-stopped
    ports:
      - "8090:8080"
    depends_on:
      db:
        condition: service_healthy
    environment:
      DATABASE_URL: postgres://miniflux:${MINIFLUX_DB_PASSWORD}@db/miniflux?sslmode=disable
      RUN_MIGRATIONS: 1
      CREATE_ADMIN: 1
      ADMIN_USERNAME: admin
      ADMIN_PASSWORD: ${MINIFLUX_ADMIN_PASSWORD}
      BASE_URL: https://rss.example.com
      POLLING_FREQUENCY: 30
      BATCH_SIZE: 50
      CLEANUP_ARCHIVE_UNREAD: "false"

  db:
    image: postgres:16-alpine
    container_name: miniflux-db
    restart: unless-stopped
    environment:
      POSTGRES_USER: miniflux
      POSTGRES_PASSWORD: ${MINIFLUX_DB_PASSWORD}
      POSTGRES_DB: miniflux
    volumes:
      - miniflux-db-data:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD", "pg_isready", "-U", "miniflux"]
      interval: 10s
      timeout: 5s
      retries: 5

volumes:
  miniflux-db-data:

Put the two secrets in a .env file next to the compose file, bring it up, and it self-migrates the schema on first boot because of RUN_MIGRATIONS: 1. Put it behind a reverse proxy for TLS — I run mine behind Caddy, which I covered in Reverse Proxy Done Right — rather than exposing port 8090 directly.

POLLING_FREQUENCY is worth tuning. The default is 60 minutes; I dropped mine to 30 because most of what I read is low-volume blogs and I would rather Miniflux check twice as often than have me refresh manually. For feeds that publish dozens of times a day, Miniflux supports per-feed polling intervals, so you can leave the global default conservative and override it for the noisy ones.

Reading, keyboard-first

Advertisement

The interface takes maybe ten minutes to learn and then you never look at the mouse again. j/k move down and up the entry list, o or Enter opens an entry, v opens the original page in a new tab, s stars it, m toggles read/unread, A marks the whole feed as read. That last one matters more than it sounds — the single biggest failure mode of every “read later” app I have used is the unread count becoming a source of guilt rather than information. Miniflux makes bulk triage a single keystroke, which keeps the number honest.

It also supports keyboard-driven feed management: g then a category number jumps categories, / searches. None of this requires memorising a cheat sheet; the shortcuts are shown by pressing ? and they map onto the vim-style conventions most terminal-adjacent people already have in muscle memory.

Integrations that actually matter

Miniflux exposes a documented REST API and, more usefully, a Fever-API-compatible and Google-Reader-API-compatible endpoint, which means the entire ecosystem of mobile RSS apps — Reeder, NetNewsWire, FocusReader on Android — talks to it without anyone having to write a bespoke Miniflux client. I read on my phone through an app that has no idea Miniflux exists; it just thinks it’s talking to a Fever server.

The other integration I lean on is the outbound webhook and the “save to” third-party services: Miniflux can push starred entries to Pocket-alikes, to a Linkding instance, or to a custom webhook. I wired mine to a small webhook that drops the entry title and URL into a personal notes app, which turned “starred for later” from a graveyard into an actual pipeline.

Import is OPML, export is OPML, and that portability is not a minor point — it’s the whole reason to trust a self-hosted reader with your subscriptions in the first place. I have moved feed lists between three different self-hosted readers over the years and it has never been more than one file.

Themes, PWA mode, and the mobile gap

The default theme is light-grey and function-first; there’s a built-in dark theme toggled per-user in Settings, and a small selection of community CSS overrides if you want denser line spacing or a different type scale. None of it matters much day to day because you spend most of your time in the reading pane, where the chrome fades into the background entirely.

The one gap is a dedicated first-party mobile app — there isn’t one, and the project has no plans to build one, because the Fever/Google-Reader API compatibility layer already covers that need without Miniflux’s maintainer having to build and maintain two more codebases. In practice this is a non-issue: install it as a PWA from a mobile browser (Settings → Add to Home Screen works fine on both iOS Safari and Android Chrome against Miniflux’s own web UI, which is responsive) or point a dedicated app like Fluent Reader, FocusReader, or NetNewsWire at your instance’s URL with the Fever API credentials generated under Settings → API Keys. I use the PWA on my phone and a proper native app on my tablet, both against the same Miniflux instance, and read position syncs between them because it’s the same server-side unread state either way — no separate sync service, no separate account.

Migrating from something else

If you’re coming from a hosted reader, the OPML export is the whole migration. Feedly, Inoreader, and The Old Reader all export OPML from their settings pages; Miniflux imports it under Settings → Import. The one thing that doesn’t carry over is read/unread history and any starred-item archive — Miniflux starts you at zero unread across every imported feed, so budget five minutes after import to bulk-mark everything as read before your unread count becomes an unmanageable four-digit number on day one.

Troubleshooting

Feeds show “parsing error” but work fine in a browser. Almost always a Content-Type mismatch — the server is serving text/html for an endpoint that’s genuinely RSS or Atom. Miniflux tries to sniff the real format regardless, but some feeds are malformed enough that it gives up. Check Settings → Feeds → [feed] → Edit and look at the fetched status code and content type; if the server needs a specific User-Agent to serve the feed at all (some do, to block scrapers), Miniflux lets you override the User-Agent per feed.

“Certificate error” on feeds that clearly have valid certs in a browser. Miniflux runs on its own trust store inside the container. If you’re behind a corporate proxy or your own internal CA for outbound traffic, you need to mount your CA bundle into the container and set SSL_CERT_DIR, or the Go HTTP client will refuse the handshake even though your browser (using the OS trust store) is happy.

Database connection refused on first boot. The depends_on: condition: service_healthy in the compose file above exists for exactly this reason — Postgres takes a few seconds to become ready for connections after the container starts, and without the healthcheck gate, Miniflux’s own container starts faster and fails its first migration attempt. If you strip the healthcheck out for some reason, expect an intermittent failure on docker compose up that “fixes itself” on a restart.

Duplicate entries flooding a feed after a site redesign. This happens when a publisher changes their GUID scheme (migrating CMS platforms is a classic trigger). Miniflux’s deduplication keys off the entry GUID first, hash of content second; if a site starts emitting new GUIDs for old content, you’ll see a one-time flood of “new” entries for things you’ve already read. There’s no clean fix beyond bulk-marking the feed as read and moving on — the fault sits with the publisher’s GUID scheme, well outside Miniflux’s control.

Unread count balloons after leaving it a week. Check POLLING_FREQUENCY and BATCH_SIZE together — if you have many feeds and a low batch size, the polling cycle can take longer than the polling interval, and Miniflux will silently skip feeds it doesn’t get to in a given pass rather than overlap runs. Raising BATCH_SIZE (I run 50, up from the default 100 workers being throttled at low-resource VMs) or reducing feed count are the two honest fixes.

Is it worth it

If your bar for an RSS reader is “syncs across three devices and doesn’t nag me,” Miniflux clears it in about the time it takes to write the compose file above. It costs nothing beyond a small VM’s worth of resources — mine sits comfortably in 512 MB of RAM including Postgres — and it has needed zero maintenance beyond routine image updates since I stood it up. The trade-off is aesthetic: if you want a reader with a slick card-based UI, inline images by default, and reading-time estimates, Miniflux’s spartan list view will feel like a step backward. I’d argue that spartan-ness is the actual feature. It reads like Google Reader did in 2011, because that turned out to be the correct design and everyone else has spent a decade rediscovering it.

For anyone already running something like Paperless-ngx or a Pi-hole setup, Miniflux slots into the same “boring, self-hosted, does one thing” category, and it’s one of the lowest-maintenance services in my entire stack.

Advertisement
Advertisement
Smarc
Written by Smarc

Founder and editor of vo.rs. A lifelong tinkerer who self-hosts far more than is sensible, hardens Linux boxes for fun, and prods the latest AI tools to see what they can really do. The how-to guides here are the notes Smarc wishes had existed the first time round.