Fully verified9,646

RSS and news

Miniflux

Miniflux is one Go binary and one PostgreSQL database: no plugins, no recommendation feed, no account anywhere else. It reads RSS, Atom, JSON Feed, and RDF, pulls the full article when a feed ships only an excerpt, and serves the Google Reader and Fever APIs so existing mobile readers sync against it.

Overview

Miniflux is a deliberately small feed reader: one Go binary, one PostgreSQL database, no plugins, no recommendation engine, and no account on somebody else’s server. It reads RSS, Atom, JSON Feed, and RDF, fetches the full article text when a feed only ships an excerpt, keeps entries readable offline in the browser, and exposes the Google Reader and Fever APIs so existing mobile readers can sync against it.

This recipe runs pinned Miniflux 2.3.3 with PostgreSQL 18. The application itself stores nothing on disk — feeds, entries, icons, sessions, and API keys all live in the database, so a single dump is the whole backup. The web port binds to localhost and PostgreSQL is not published at all.

Security and recipe boundaries

The full application smoke test has passed on amd64 and arm64, and practical backup and restore have passed on amd64. Miniflux fetches arbitrary URLs on a schedule, so treat the poller as an outbound HTTP client with the reach of its network — put an egress policy in front of it if the instance is shared with people you do not fully trust. Passwords are the only credential the recipe sets up; both the administrator password and the PostgreSQL password sit in .env, which must be mode 600 and must never enter Git. TRUSTED_REVERSE_PROXY_NETWORKS is limited to loopback, so forwarded client-IP and proxy-authentication headers are ignored until an operator deliberately trusts the Docker bridge network. The recipe does not set up OAuth2/OIDC or passkeys; both are supported upstream and are configured after the first sign-in.

Update tracking

These are stored results of separate checks, not real-time data. An unknown status means the check has not been performed yet.

New releases

Recipe version compared with the latest known upstream release. A version number, not a reading of what changed.

Current version 2.3.3Sep 2, 2026, 7:40 PM UTC

Critical vulnerabilities

Result of a dedicated Docker image scan, not a Compose configuration check.

Not checkedNot checked

Breaking updates

A new major version is found by comparing version numbers. A compatibility verdict is entered by hand after reading the release notes and the migration steps.

Not checkedNot checked

compose.yaml

Upstream Compose changes relative to the verified recipe.

Upstream Compose is not trackedNot checked

Outdated Docker images

Pinned image tags checked for newer versions.

Not checkedNot checked

From observation to action

Update plan

The recipe already pins the latest known release. The steps below show the evidence retained for that version.

2.3.32.3.3
  1. Open the release evidence

    Open this release on GitHub ↗ The link is evidence to review, not proof of compatibility.

  2. Review the version boundary

    The version numbers do not cross a major compatibility boundary. No compatibility verdict has been recorded.

  3. Prepare image-tag changes

    No image-tag change is currently planned.

  4. Follow the documented migration shape

    Change the tag and restart. Release-specific migration steps still come from the release notes and installation guide.

  5. Protect the rollback point

    No version change is currently planned. The recipe includes a backup procedure. Restore has been tested.

  6. Verify the updated recipe

    An exact Fully verified check for this target was recorded on 2026-09-03.

Read the update procedure in the installation guide ↓

Specifications

Security and exposure

Installation levels

The status shows how fully the published recipe meets each level's requirements.

Partial

For home

A minimal setup for a local network or access through a VPN.

  • Single server
  • Local access
  • Minimum components

You still need to add: single server, local access, minimum components.

Partial

For a VPS

A public deployment with protected access and backups.

  • Domain
  • HTTPS via reverse proxy
  • Off-server backup

You still need to add: domain, https via reverse proxy, off-server backup.

Partial

Reliable

A setup designed for predictable operations and observability.

  • Separate database
  • Healthcheck
  • External monitoring
  • CPU and memory limits

You still need to add: external monitoring, cpu and memory limits.

Tailored to your server

Configuration generator

Answer five questions and download a ready-to-run Miniflux bundle.

Runs locally
01 Do you have a domain?
02 Which reverse proxy?
03 Server architecture
04 Where should data be stored?

Use an absolute path for a separate disk, such as /srv/miniflux.

05 Is VPN access required?

Your bundle is ready

The archive is created in your browser. Your answers are never sent anywhere.

Screenshots

Screenshots have not been added yet.

Quick start

Read the full guide first: it explains external access, backups, and data locations.

Prepare settingscp .env.example .env
Launchdocker compose up -d
Check statusdocker compose ps

Step-by-step guide

1. Check the Ubuntu or Debian server

Use Ubuntu 22.04+ or Debian 12+ with Docker Engine and Compose v2.24+. One CPU core, 512 MB RAM, and 2 GB disk are enough for a few hundred feeds; the database grows with retained entries and downloaded icons. The official image declares amd64 and arm64.

docker --version
docker compose version

2. Prepare the recipe and two independent secrets

mkdir -p ~/services/miniflux
cd ~/services/miniflux
cp .env.example .env
chmod 600 .env
admin_password="$(openssl rand -hex 24)"
db_password="$(openssl rand -hex 32)"
sed -i "s|^MINIFLUX_ADMIN_PASSWORD=.*|MINIFLUX_ADMIN_PASSWORD=$admin_password|" .env
sed -i "s|^MINIFLUX_DB_PASSWORD=.*|MINIFLUX_DB_PASSWORD=$db_password|" .env
echo "administrator password: $admin_password"
unset admin_password db_password

The database password ends up inside DATABASE_URL, so keep it alphanumeric: a @, /, or # would break the connection string. The administrator password must be at least 12 characters. Store both in a password manager; the administrator password is shown once here and is not recoverable from the database.

Every .env variable:

  • MINIFLUX_PORT is the local web port, default 8080;
  • MINIFLUX_BASE_URL is the exact public URL without a trailing /; links, OAuth2 redirects, and the Google Reader endpoint are derived from it;
  • MINIFLUX_ADMIN_USER and MINIFLUX_ADMIN_PASSWORD create the first account on the first start;
  • MINIFLUX_DB_PASSWORD is an alphanumeric PostgreSQL password;
  • MINIFLUX_DB_NAME and MINIFLUX_DB_USER are the database and role names, changed only before the first start;
  • MINIFLUX_POLLING_FREQUENCY is the poller interval in minutes;
  • MINIFLUX_POLLING_PARSING_ERROR_LIMIT is how many consecutive parse errors disable a feed;
  • MINIFLUX_CLEANUP_ARCHIVE_READ_DAYS is how long read entries are kept;
  • MINIFLUX_TRUSTED_PROXY_NETWORKS lists CIDR networks whose forwarded headers are trusted, loopback only by default, and must never be empty;
  • MINIFLUX_LOG_LEVEL is error, warning, info, or debug;
  • MINIFLUX_TIME_ZONE is an IANA time zone;
  • MINIFLUX_DB_VOLUME names the PostgreSQL volume;
  • MINIFLUX_BACKUP_DIR selects the host backup directory.

RUN_MIGRATIONS and CREATE_ADMIN are pinned to 1 in the recipe. Both are idempotent: migrations run at every start and the administrator is created only while the user table is empty.

3. Start and sign in

docker compose config
docker compose pull
docker compose up -d --wait --wait-timeout 600
docker compose ps
curl --fail http://127.0.0.1:8080/healthcheck

/healthcheck answers OK only after the database connection works, so a successful call means the migrations finished. Sign in at MINIFLUX_BASE_URL with MINIFLUX_ADMIN_USER, then add feeds or import an OPML file from Settings → Import.

VPS deployment

Keep 127.0.0.1:${MINIFLUX_PORT}:8080; only an HTTPS proxy on the host can reach the application. PostgreSQL has no published port. Allow SSH, HTTP, and HTTPS through the firewall and nothing else. Miniflux polls feeds from the server, so outbound HTTPS must stay open; if the box also reaches private services, restrict container egress to public networks.

Trusted LAN access

Prefer ssh -L 8080:127.0.0.1:8080 user@server with MINIFLUX_BASE_URL=http://localhost:8080 for that route. For permanent LAN access, replace the localhost bind with one specific private IP, set a matching MINIFLUX_BASE_URL, and restrict the port with a firewall. Do not bind 0.0.0.0 without network controls: the login form and the Fever API would then answer on every interface over plain HTTP.

Domain and HTTPS

Set MINIFLUX_BASE_URL=https://reader.example.com, replace the host in the Caddy, Nginx, or Traefik example, and recreate the container. The proxy must forward the Authorization header — Caddy and Traefik do it by default, and the Nginx sample does it explicitly — otherwise the Google Reader and Fever APIs reject every client. Miniflux needs no WebSocket support. Serving from a subpath is possible with BASE_URL, but a dedicated subdomain avoids rewriting the static asset paths.

Backup

chmod +x backup.sh restore.sh
./backup.sh

The script stops the application so no poller writes mid-dump, keeps PostgreSQL running for a native pg_dump, and archives the compressed dump together with .env and compose.yaml. Everything Miniflux owns is in that dump: feeds, entries, enclosures metadata, icons, sessions, and API keys. An OPML export is not a backup — it lists subscriptions and nothing else. The archive contains secrets; encrypt it and copy it off the server.

Restore

Restore irreversibly replaces the database. Use the same Miniflux and PostgreSQL versions with the active .env:

./restore.sh ./backups/miniflux-YYYYMMDDTHHMMSSZ.tar
curl --fail http://127.0.0.1:8080/healthcheck

The script first backs up the state being replaced, recreates the volume, restores the dump, and starts the stack. The archived configuration.env is kept for comparison only and is never activated. This procedure has not passed a practical restore test; rehearse it on a separate server before you rely on it.

Update Miniflux

Create a backup and read the release notes. Replace the exact miniflux/miniflux:2.3.3 tag with a reviewed version, never latest, and do not change PostgreSQL in the same step:

./backup.sh
docker compose pull
docker compose up -d --wait --wait-timeout 600
curl --fail http://127.0.0.1:8080/healthcheck
docker compose logs --tail=200 miniflux

Schema migrations run automatically at startup because RUN_MIGRATIONS=1.

PostgreSQL major update

postgres:18-alpine is pinned independently of the application. A major change needs a new data volume: two server majors must never share a cluster directory. Take a backup, start the new major on an empty volume, and restore the dump with restore.sh. Keep the old volume until the new database is verified.

Rollback

Never start an older Miniflux over a database that newer migrations have already touched. Restore the previous exact tag together with the pre-update archive:

docker compose down --timeout 60
./restore.sh ./backups/miniflux-BEFORE-UPDATE.tar

After a failed PostgreSQL change, attach the old image to its preserved old volume, or restore a compatible dump into an empty volume.

Stop and remove

docker compose down preserves the database. After verifying an off-server backup, remove everything irreversibly:

docker compose down
docker volume rm miniflux-database
rm -rf ~/services/miniflux

Substitute the actual name when MINIFLUX_DB_VOLUME differs.

Sources: Docker installation, configuration parameters, release 2.3.3, and PostgreSQL upgrades.

Troubleshooting

The container never becomes healthy

docker compose ps
docker compose logs --tail=200 miniflux database
docker inspect --format '{{json .State.Health}}' "$(docker compose ps -q miniflux)"

Miniflux exits on a bad configuration value rather than starting with a partial one, and the message names the key. Two values fail most often: a database password containing @, /, or #, which breaks DATABASE_URL, and an empty MINIFLUX_TRUSTED_PROXY_NETWORKS, which is rejected with “at least one CIDR notation network is required”. The health check itself calls /healthcheck, which pings the database, so a healthy container also means migrations ran.

The administrator account was not created

CREATE_ADMIN only acts while the user table is empty. If the first start failed after the schema was created, the account may be missing. Create it by hand instead of wiping the volume:

docker compose exec miniflux miniflux -create-admin

MINIFLUX_BASE_URL must match the public address exactly: scheme, host, optional port and path, no trailing /. Miniflux builds cookie scope, OAuth2 redirects, and the Google Reader endpoint from it. Change the variable and recreate the container; the value is read at startup.

A mobile client cannot sign in

Use the Google Reader endpoint at MINIFLUX_BASE_URL/googlereader (or the Fever endpoint at /fever/) and the separate API password from Settings → API keys, not the web password. Nginx must pass Authorization through — the sample in proxy/nginx.conf sets proxy_pass_header Authorization for that reason; Caddy and Traefik forward it by default.

Feeds stop updating or a feed is disabled

A feed is disabled after MINIFLUX_POLLING_PARSING_ERROR_LIMIT consecutive failures; the feed page shows the last error. Check the feed by hand before raising the limit:

docker compose exec miniflux miniflux -refresh-feeds
docker compose logs --tail=200 miniflux | grep -i "unable to"

A whole instance that stops polling usually means the poller cannot reach the internet from the container, or MINIFLUX_POLLING_FREQUENCY is far higher than expected.

The client IP in the logs is the Docker gateway

That is what the recipe intends: MINIFLUX_TRUSTED_PROXY_NETWORKS trusts loopback only, so forwarded headers are ignored and every request appears to come from the bridge. If you need real client addresses, add only the bridge network of this project to the variable and make sure the proxy sets X-Forwarded-For itself. Trusting a broad range lets a caller spoof both the client IP and, if AUTH_PROXY_HEADER is ever enabled, the authenticated user.

The reverse proxy returns 502

On the host, run curl -I http://127.0.0.1:8080/. If that answers, the proxy is looking at the wrong upstream: inside a proxy container 127.0.0.1 is the proxy itself, not the host, so use a host gateway address or put the proxy on the same Docker network. If it does not answer, the container is unhealthy — start from the first section.

The database volume keeps growing

Retained entries and downloaded icons dominate the size. Lower MINIFLUX_CLEANUP_ARCHIVE_READ_DAYS, then reclaim space during a maintenance window:

docker compose exec miniflux miniflux -flush-sessions
docker compose exec database vacuumdb --analyze --dbname=miniflux --username=miniflux

Official sources

Latest release: 2026-07-24 · GitHub Stars: 9,646 · metadata checked Sep 2, 2026, 7:40 PM UTC

Disclaimer

Fossary is an independent informational catalog and is not affiliated with the developers of the listed applications. We collect information from public sources and verify published recipes, but we do not develop, distribute, or control these applications.

A listing or validation status is not a requirement, endorsement, or personal recommendation to use an application. You decide whether it is suitable and use it at your own risk. We do not warrant an application's security, availability, or fitness for purpose and, to the extent permitted by law, are not liable for resulting loss or damage. Review its license, security settings, data processing, and backup procedures before installation.