Feed Reader
    PostgreSQL

    Deploy Miniflux on a VPS

    Install Miniflux 2.3.3 on a RamNode VPS with PostgreSQL 16 over a Unix socket, a hardened systemd service, Nginx TLS, and nightly backups.

    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 miniflux role 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_dump backups with retention

    Server Sizing

    Miniflux is extremely light. PostgreSQL and the number of feeds you poll drive resource usage, not the app.

    Use casevCPURAMDiskNotes
    Single user, under 300 feeds1512 MB to 1 GB10 GBComfortable on the smallest plans
    Household or small team, up to 1,000 feeds1 to 21 to 2 GB20 GBRecommended starting point
    Many users or 5,000+ feeds2 to 44 GB40 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 A record (and AAAA for IPv6) for a hostname such as rss.example.com

    Replace rss.example.com throughout with your hostname.

    Step 1: Prepare the System

    shell
    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-upgrades

    Firewall:

    shell
    sudo ufw allow OpenSSH
    sudo ufw allow 'Nginx Full'
    sudo ufw enable

    Step 2: Create the Service Account

    shell
    sudo useradd --system --home-dir /var/lib/miniflux --create-home \
      --shell /usr/sbin/nologin miniflux

    The 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

    shell
    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:

    shell
    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:

    shell
    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.sha256

    You should see miniflux-linux-amd64: OK. Then install it:

    shell
    sudo install -m 0755 miniflux-linux-amd64 /usr/local/bin/miniflux
    rm -f miniflux-linux-amd64 miniflux-linux-amd64.sha256
    miniflux -version

    Step 5: Write the Configuration

    shell
    sudo mkdir -p /etc/miniflux
    sudo pico /etc/miniflux/miniflux.conf
    shell
    # 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=1

    Key settings:

    SettingPurpose
    LISTEN_ADDRLoopback only; Nginx handles public traffic
    BASE_URLMust match the public HTTPS URL exactly, including the trailing slash; used for links, cookies, and WebAuthn
    RUN_MIGRATIONSApplies schema migrations on startup, including after upgrades
    POLLING_SCHEDULER=entry_frequencyPolls 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:

    shell
    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:

    shell
    sudo -u miniflux miniflux -c /etc/miniflux/miniflux.conf -migrate

    Step 6: Create the Admin User

    shell
    sudo -u miniflux miniflux -c /etc/miniflux/miniflux.conf -create-admin

    Miniflux 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

    shell
    sudo pico /etc/systemd/system/miniflux.service
    shell
    [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.target

    Because 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.

    shell
    sudo systemctl daemon-reload
    sudo systemctl enable --now miniflux
    sudo systemctl status miniflux --no-pager
    curl -s http://127.0.0.1:8080/healthcheck

    The health check returns OK.

    Step 8: Configure Nginx and TLS

    Rate limit zone:

    shell
    echo 'limit_req_zone $binary_remote_addr zone=mf_login:10m rate=10r/m;' | \
      sudo tee /etc/nginx/conf.d/ratelimit-miniflux.conf

    Site:

    shell
    sudo pico /etc/nginx/sites-available/miniflux
    shell
    server {
        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;
        }
    }
    shell
    sudo pico /etc/nginx/snippets/miniflux-proxy.conf
    shell
    proxy_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:

    shell
    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-run

    Browse 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:

    APIEndpointClients
    Google Readerhttps://rss.example.com/Reeder, NetNewsWire, Read You, FocusReader
    Feverhttps://rss.example.com/fever/Unread, older Fever clients
    Miniflux nativehttps://rss.example.com/v1/ with an API key from Settings > API KeysFlux 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:

    shell
    FETCHER_ALLOW_PRIVATE_NETWORKS=1

    Integrations 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

    shell
    sudo pico /usr/local/bin/miniflux-backup
    shell
    #!/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 -delete
    shell
    sudo 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/miniflux

    Restore into an empty database:

    shell
    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 miniflux

    Copy 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:

    shell
    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-pager

    Read 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

    SymptomCause and fix
    pq: Peer authentication failedThe 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 errorsBASE_URL does not match the URL in the browser; fix scheme, hostname, and trailing slash
    Passkey login fails after upgradeNon-resident credentials no longer work for login in 2.3.x; delete and re-register as a passkey
    Local feed shows a private network errorSet FETCHER_ALLOW_PRIVATE_NETWORKS=1 and restart
    Feeds never refreshCheck sudo journalctl -u miniflux -f for fetch errors; confirm outbound HTTPS is allowed
    502 Bad GatewayService is down or bound elsewhere. `ss -ltnp
    Database keeps growingLower 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 miniflux group
    • systemd sandbox with an empty capability set and restricted address families
    • /metrics denied 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.