Miniflux is a minimalist, opinionated feed reader written in Go. It ships as a single static binary, stores everything in PostgreSQL, and uses very little memory, which makes it one of the most efficient self-hosted RSS readers you can run on a small VPS. It supports RSS, Atom, and JSON Feed, full-content scraping, keyboard navigation, passkeys, and Fever and Google Reader compatible APIs for mobile clients, plus dozens of integrations (Wallabag, Readwise, Pocket alternatives, Telegram, webhooks, and more).
This guide installs Miniflux 2.3.3 from the official release binary on Ubuntu 24.04 LTS, connects it to a local PostgreSQL 16 database over a Unix socket with peer authentication (no database password to manage), runs it under a sandboxed systemd unit, and publishes it behind Nginx with Let's Encrypt.
What You Will Build
- Miniflux 2.3.3 binary at
/usr/local/bin/miniflux, checksum-verified - PostgreSQL 16 with a dedicated
minifluxrole and database - Configuration in
/etc/miniflux/miniflux.conf - A hardened systemd service listening on
127.0.0.1:8080 - Nginx reverse proxy with TLS and login rate limiting
- Nightly
pg_dumpbackups with retention
Server Sizing
Miniflux is extremely light. PostgreSQL and the number of feeds you poll drive resource usage, not the app.
| Use case | vCPU | RAM | Disk | Notes |
|---|---|---|---|---|
| Single user, under 300 feeds | 1 | 512 MB to 1 GB | 10 GB | Comfortable on the smallest plans |
| Household or small team, up to 1,000 feeds | 1 to 2 | 1 to 2 GB | 20 GB | Recommended starting point |
| Many users or 5,000+ feeds | 2 to 4 | 4 GB | 40 GB+ | Tune worker pool and batch size |
Database size grows with retained entries. The cleanup settings in Step 5 keep it predictable.
Prerequisites
- A RamNode VPS running Ubuntu 24.04 LTS (x86_64)
- Root or sudo access over SSH
- A DNS
Arecord (andAAAAfor IPv6) for a hostname such asrss.example.com
Replace rss.example.com throughout with your hostname.
Step 1: Prepare the System
sudo apt update && sudo apt -y full-upgrade
sudo apt -y install postgresql nginx certbot python3-certbot-nginx ufw unattended-upgrades curl
sudo dpkg-reconfigure -plow unattended-upgradesFirewall:
sudo ufw allow OpenSSH
sudo ufw allow 'Nginx Full'
sudo ufw enableStep 2: Create the Service Account
sudo useradd --system --home-dir /var/lib/miniflux --create-home \
--shell /usr/sbin/nologin minifluxThe OS user and the PostgreSQL role share the name miniflux. That lets PostgreSQL's default peer authentication on the local Unix socket identify the service without a password.
Step 3: Create the Database
sudo -u postgres createuser miniflux
sudo -u postgres createdb -O miniflux miniflux
sudo -u postgres psql -d miniflux -c 'CREATE EXTENSION IF NOT EXISTS hstore;'Miniflux's early schema migrations use the hstore extension, and creating an extension requires superuser rights. Creating it up front as postgres lets the unprivileged miniflux role run every migration on a fresh database.
Verify peer authentication works:
sudo -u miniflux psql -d miniflux -c 'select current_user;'Step 4: Install the Miniflux Binary
Pin the version and verify the checksum published with the release:
MINIFLUX_VERSION=2.3.3
cd /tmp
curl -fLO "https://github.com/miniflux/v2/releases/download/${MINIFLUX_VERSION}/miniflux-linux-amd64"
curl -fLO "https://github.com/miniflux/v2/releases/download/${MINIFLUX_VERSION}/miniflux-linux-amd64.sha256"
sha256sum -c miniflux-linux-amd64.sha256You should see miniflux-linux-amd64: OK. Then install it:
sudo install -m 0755 miniflux-linux-amd64 /usr/local/bin/miniflux
rm -f miniflux-linux-amd64 miniflux-linux-amd64.sha256
miniflux -versionStep 5: Write the Configuration
sudo mkdir -p /etc/miniflux
sudo pico /etc/miniflux/miniflux.conf# Network
LISTEN_ADDR=127.0.0.1:8080
BASE_URL=https://rss.example.com/
# Database: Unix socket + peer auth, no password
DATABASE_URL=user=miniflux dbname=miniflux host=/run/postgresql sslmode=disable
RUN_MIGRATIONS=1
# Polling
POLLING_FREQUENCY=60
BATCH_SIZE=100
WORKER_POOL_SIZE=16
POLLING_SCHEDULER=entry_frequency
# Retention: archive read entries after 60 days, unread after 180
CLEANUP_ARCHIVE_READ_DAYS=60
CLEANUP_ARCHIVE_UNREAD_DAYS=180
CLEANUP_REMOVE_SESSIONS_DAYS=30
# Metrics (Prometheus), loopback only
METRICS_COLLECTOR=1
METRICS_ALLOWED_NETWORKS=127.0.0.1/8
# Logging
LOG_DATE_TIME=1Key settings:
| Setting | Purpose |
|---|---|
LISTEN_ADDR | Loopback only; Nginx handles public traffic |
BASE_URL | Must match the public HTTPS URL exactly, including the trailing slash; used for links, cookies, and WebAuthn |
RUN_MIGRATIONS | Applies schema migrations on startup, including after upgrades |
POLLING_SCHEDULER=entry_frequency | Polls busy feeds more often and quiet feeds less often, which cuts wasted requests |
CLEANUP_ARCHIVE_* | Caps database growth |
Lock the file down and validate it:
sudo chown root:miniflux /etc/miniflux/miniflux.conf
sudo chmod 640 /etc/miniflux/miniflux.conf
sudo -u miniflux miniflux -c /etc/miniflux/miniflux.conf -config-dump-config-dump prints the parsed configuration. Any typo in a key name shows up here as a missing value.
Run migrations once by hand so problems surface before systemd gets involved:
sudo -u miniflux miniflux -c /etc/miniflux/miniflux.conf -migrateStep 6: Create the Admin User
sudo -u miniflux miniflux -c /etc/miniflux/miniflux.conf -create-adminMiniflux prompts for a username and password interactively, so the credentials never land in a config file or your shell history.
Step 7: Create the systemd Service
sudo pico /etc/systemd/system/miniflux.service[Unit]
Description=Miniflux feed reader
After=network-online.target postgresql.service
Wants=network-online.target
Requires=postgresql.service
[Service]
Type=simple
User=miniflux
Group=miniflux
ExecStart=/usr/local/bin/miniflux -c /etc/miniflux/miniflux.conf
Restart=on-failure
RestartSec=5
# Hardening
NoNewPrivileges=true
PrivateTmp=true
PrivateDevices=true
ProtectSystem=strict
ProtectHome=true
ProtectKernelTunables=true
ProtectKernelModules=true
ProtectControlGroups=true
ProtectClock=true
ProtectHostname=true
RestrictSUIDSGID=true
RestrictNamespaces=true
RestrictRealtime=true
LockPersonality=true
MemoryDenyWriteExecute=true
RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6
SystemCallArchitectures=native
CapabilityBoundingSet=
ReadWritePaths=/var/lib/miniflux
[Install]
WantedBy=multi-user.targetBecause Miniflux is a static Go binary that only needs the network and a database socket, it tolerates a much tighter sandbox than most web apps.
sudo systemctl daemon-reload
sudo systemctl enable --now miniflux
sudo systemctl status miniflux --no-pager
curl -s http://127.0.0.1:8080/healthcheckThe health check returns OK.
Step 8: Configure Nginx and TLS
Rate limit zone:
echo 'limit_req_zone $binary_remote_addr zone=mf_login:10m rate=10r/m;' | \
sudo tee /etc/nginx/conf.d/ratelimit-miniflux.confSite:
sudo pico /etc/nginx/sites-available/minifluxserver {
listen 80;
listen [::]:80;
server_name rss.example.com;
client_max_body_size 10M; # OPML imports
location = /login {
limit_req zone=mf_login burst=5 nodelay;
proxy_pass http://127.0.0.1:8080;
include /etc/nginx/snippets/miniflux-proxy.conf;
}
location = /metrics {
deny all;
}
location / {
proxy_pass http://127.0.0.1:8080;
include /etc/nginx/snippets/miniflux-proxy.conf;
}
}sudo pico /etc/nginx/snippets/miniflux-proxy.confproxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_redirect off;Enable and issue the certificate:
sudo ln -s /etc/nginx/sites-available/miniflux /etc/nginx/sites-enabled/
sudo rm -f /etc/nginx/sites-enabled/default
sudo nginx -t && sudo systemctl reload nginx
sudo certbot --nginx -d rss.example.com --redirect -m you@example.com --agree-tos --no-eff-email
sudo certbot renew --dry-runBrowse to https://rss.example.com and sign in with the admin account from Step 6.
Step 9: Post-Install Configuration
Import your feeds
Feeds > Import accepts an OPML file from any other reader. As of 2.3.0, Miniflux can also export and import its own per-feed settings (scraper rules, rewrite rules, crawler flags) in OPML, so a Miniflux OPML export is now a complete configuration backup for your subscriptions.
Add a passkey
Settings > Security lets you register a passkey for passwordless login. Since 2.3.0, only discoverable credentials (resident keys / passkeys) work for login. If you registered a non-resident security key on an older version, remove it and register it again as a passkey.
Mobile and desktop clients
Enable an API under Settings > Integrations:
| API | Endpoint | Clients |
|---|---|---|
| Google Reader | https://rss.example.com/ | Reeder, NetNewsWire, Read You, FocusReader |
| Fever | https://rss.example.com/fever/ | Unread, older Fever clients |
| Miniflux native | https://rss.example.com/v1/ with an API key from Settings > API Keys | Flux News, miniflux CLI tools, scripts |
Each user sets their own API username and password for Google Reader and Fever. These are separate from the web login.
Feeds on private networks
Since 2.2.18, Miniflux blocks fetches to private and loopback addresses to prevent SSRF. If you intentionally subscribe to feeds hosted on the same VPS or a private network (for example an RSS-Bridge instance on 127.0.0.1), add:
FETCHER_ALLOW_PRIVATE_NETWORKS=1Integrations pointing at private services (a self-hosted Wallabag on the LAN, for example) need INTEGRATION_ALLOW_PRIVATE_NETWORKS=1. Only enable these if you need them, and only on single-tenant instances where you trust every user.
Sites behind Cloudflare challenges
Miniflux 2.3.0 detects Cloudflare bot-challenge pages and reports a clear error instead of a parse failure. Those feeds generally need a different source URL or a bridge; Miniflux cannot solve the challenge.
Step 10: Backups
sudo pico /usr/local/bin/miniflux-backup#!/usr/bin/env bash
set -euo pipefail
DEST=/var/backups/miniflux
KEEP_DAYS=14
mkdir -p "$DEST"
chown postgres:postgres "$DEST"
chmod 700 "$DEST"
sudo -u postgres pg_dump -Fc miniflux > "$DEST/miniflux-$(date +%F).dump"
find "$DEST" -name 'miniflux-*.dump' -mtime +$KEEP_DAYS -deletesudo chmod 750 /usr/local/bin/miniflux-backup
echo '15 3 * * * root /usr/local/bin/miniflux-backup' | sudo tee /etc/cron.d/miniflux-backup
sudo /usr/local/bin/miniflux-backup && ls -lh /var/backups/minifluxRestore into an empty database:
sudo systemctl stop miniflux
sudo -u postgres dropdb miniflux
sudo -u postgres createdb -O miniflux miniflux
sudo -u postgres pg_restore -d miniflux /var/backups/miniflux/miniflux-YYYY-MM-DD.dump
sudo systemctl start minifluxCopy the dumps off the server with restic, rclone, or rsync.
Updating Miniflux
Miniflux releases are frequent and migrations run automatically on start. Back up, swap the binary, restart:
sudo /usr/local/bin/miniflux-backup
MINIFLUX_VERSION=NEW_VERSION
cd /tmp
curl -fLO "https://github.com/miniflux/v2/releases/download/${MINIFLUX_VERSION}/miniflux-linux-amd64"
curl -fLO "https://github.com/miniflux/v2/releases/download/${MINIFLUX_VERSION}/miniflux-linux-amd64.sha256"
sha256sum -c miniflux-linux-amd64.sha256
sudo install -m 0755 miniflux-linux-amd64 /usr/local/bin/miniflux
sudo systemctl restart miniflux
miniflux -version
sudo journalctl -u miniflux -n 30 --no-pagerRead each release's breaking-changes section before upgrading across minor versions. Recent examples: 2.2.18 introduced the private-network block, and 2.3.0 restricted WebAuthn login to passkeys and switched logout and feed refresh to POST-only.
Troubleshooting
| Symptom | Cause and fix |
|---|---|
pq: Peer authentication failed | The OS user running Miniflux must be miniflux. Check User= in the unit and that DATABASE_URL uses host=/run/postgresql |
permission denied to create extension "hstore" | Run the CREATE EXTENSION command from Step 3 as postgres |
| Logged out immediately or cookie errors | BASE_URL does not match the URL in the browser; fix scheme, hostname, and trailing slash |
| Passkey login fails after upgrade | Non-resident credentials no longer work for login in 2.3.x; delete and re-register as a passkey |
| Local feed shows a private network error | Set FETCHER_ALLOW_PRIVATE_NETWORKS=1 and restart |
| Feeds never refresh | Check sudo journalctl -u miniflux -f for fetch errors; confirm outbound HTTPS is allowed |
502 Bad Gateway | Service is down or bound elsewhere. `ss -ltnp |
| Database keeps growing | Lower CLEANUP_ARCHIVE_READ_DAYS and CLEANUP_ARCHIVE_UNREAD_DAYS; run VACUUM occasionally |
Security Checklist
- Miniflux bound to
127.0.0.1; only Nginx is public - Database reachable only over the local socket with peer auth; no password stored anywhere
- Config file readable only by root and the
minifluxgroup - systemd sandbox with an empty capability set and restricted address families
/metricsdenied at the edge and restricted to loopback in-app- Login rate limited; passkeys enabled for each user
- Private-network fetching left off unless required
- Off-server backups with a tested restore
Miniflux or FreshRSS?
Both are excellent. Pick Miniflux if you want a fast, keyboard-driven reader with almost no maintenance and a tiny footprint. Pick FreshRSS if you want extensions, theming, WebSub push, more elaborate filtering, or a choice of SQLite, MySQL, or PostgreSQL. See the companion FreshRSS guide for that deployment.
