Fully verified15,892

RSS and news

FreshRSS

FreshRSS collects RSS and Atom feeds, filters articles, keeps reading history, and offers APIs for third-party mobile and desktop clients. This recipe adds PostgreSQL 18, scheduled feed refreshes, a deterministic first run, and a web port bound safely to localhost.

Overview

FreshRSS is a lightweight RSS and Atom aggregator for a personal feed or a small trusted group. It groups sources, filters and labels articles, supports search, OPML, and WebSub, while its Google Reader and Fever APIs work with third-party Android, iOS, and desktop clients.

This recipe runs pinned FreshRSS 1.29.1 with PostgreSQL 18. Initialization is deterministic: it creates a dedicated administrator with form authentication, enables the API with a different password, selects the English interface and production environment, and refreshes feeds twice per hour. Data, extensions, and PostgreSQL use separate persistent volumes.

Security and recipe boundaries

The full HTTP smoke test has passed on amd64, arm64, and armv7; backup and restore remain untested in practice. The server fetches submitted feed URLs and can reach internal addresses, so this recipe is only for trusted users. FreshRSS 1.29.1 has no INTERNAL_HOST_ALLOWLIST option; the recipe neither invents it nor permits *. Untrusted users require a separate outbound network policy. Web binds to localhost, PostgreSQL has no published port, and proxy-header trust is disabled by default.

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 1.29.1Sep 1, 2026, 12:22 AM 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.

1.29.11.29.1
  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

    Manual steps after the pull. 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 not 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 FreshRSS 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/freshrss.

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+. Allocate at least 1 CPU, 512 MB RAM, and 2 GB disk; 1 GB RAM plus room for article history and backups is recommended. The official image declares amd64, arm64, and armv7.

docker --version
docker compose version

2. Prepare the recipe and independent secrets

mkdir -p ~/services/freshrss
cd ~/services/freshrss
cp .env.example .env
chmod 600 .env
login_password="$(openssl rand -hex 24)"
api_password="$(openssl rand -hex 24)"
db_password="$(openssl rand -hex 24)"
sed -i "s|^FRESHRSS_ADMIN_PASSWORD=.*|FRESHRSS_ADMIN_PASSWORD=$login_password|" .env
sed -i "s|^FRESHRSS_ADMIN_API_PASSWORD=.*|FRESHRSS_ADMIN_API_PASSWORD=$api_password|" .env
sed -i "s|^FRESHRSS_DB_PASSWORD=.*|FRESHRSS_DB_PASSWORD=$db_password|" .env
unset login_password api_password db_password

These secrets contain letters and digits only. The 1.29.1 entrypoint evaluates first-run option strings through shell eval, so spaces and shell metacharacters are unsafe here. All three values must differ. Store them in a password manager: API clients use the API password, not the web login password.

Every .env variable:

  • FRESHRSS_PORT is the local web port, default 8080;
  • FRESHRSS_BASE_URL is the exact public HTTPS URL without a trailing /; a dedicated subdomain is more reliable than a subpath;
  • FRESHRSS_ADMIN_USER is the ASCII-alphanumeric initial administrator login, distinct from secrets and changeable only before initialization;
  • FRESHRSS_ADMIN_PASSWORD and FRESHRSS_ADMIN_API_PASSWORD are independent alphanumeric form-auth and Google Reader/Fever API passwords;
  • FRESHRSS_ADMIN_EMAIL is the initial administrator email;
  • FRESHRSS_DB_PASSWORD is the required independent PostgreSQL password;
  • FRESHRSS_DB_NAME and FRESHRSS_DB_USER are the database and role names, changed only before the first start;
  • FRESHRSS_TIME_ZONE is an IANA time zone;
  • FRESHRSS_CRON_MIN selects built-in cron minutes; 13,43 refreshes twice per hour without joining the minute-zero spike;
  • FRESHRSS_TRUSTED_PROXY controls trust in forwarded client-IP and external-auth headers; the safe default is 0, never a broad network;
  • FRESHRSS_DATA_VOLUME, FRESHRSS_EXTENSIONS_VOLUME, and FRESHRSS_DB_VOLUME name persistent volumes;
  • FRESHRSS_BACKUP_DIR selects the host backup directory.

freshrss-data holds configuration, users, and service files; freshrss-extensions holds third-party extensions; freshrss-database holds PostgreSQL.

3. Run deterministic initialization

docker compose config
docker compose pull
docker compose up -d --wait
docker compose exec freshrss cli/health.php
docker compose exec freshrss cli/list-users.php

On an empty data volume, FRESHRSS_INSTALL creates a production PostgreSQL configuration with form auth, anonymous access disabled, API enabled, English as the interface language, and the exact base URL. FRESHRSS_USER creates the administrator. Changing these variables after initialization does not modify the existing user.

VPS deployment

Keep 127.0.0.1:${FRESHRSS_PORT}:80; only an HTTPS proxy on the host can reach web. PostgreSQL has no published port. Allow only SSH, HTTP, and HTTPS through the firewall. Configure DNS and replace FRESHRSS_BASE_URL and one proxy example before starting. Do not invite untrusted users: they can make the server fetch URLs from internal networks.

Trusted LAN access

Prefer ssh -L 8080:127.0.0.1:8080 user@server; before the first start, use FRESHRSS_BASE_URL=http://localhost:8080 for that route. For permanent LAN access, replace the localhost bind with one specific private IP, use a matching base URL, and restrict the port with a firewall. Do not use 0.0.0.0 without network controls.

Domain, HTTPS, and API clients

Use a dedicated host such as https://rss.example.com without a trailing / in both .env and Caddy, Nginx, or Traefik. Caddy obtains a certificate, Nginx expects Certbot files, and Traefik uses the letsencrypt resolver. The proxy keeps Host and forwards X-Forwarded-Proto and Authorization, required by some Google Reader API clients. FreshRSS does not require WebSockets, so upgrade headers are deliberately absent. Never replace or remove FreshRSS’s CSP; upstream explicitly warns proxies not to override it.

FRESHRSS_TRUSTED_PROXY=0 does not trust forwarded client IP or external-auth headers. Form auth and the API work with an exact base_url without a broad trusted range. If external auth is genuinely needed, list only the exact IP/CIDR of the final secured proxy; an extra trusted address can spoof a user.

Backup

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

The script stops FreshRSS and its built-in cron, keeps PostgreSQL available for native pg_dump, archives complete data and extensions plus .env and Compose, then restarts the app. This is a consistent full backup. OPML is insufficient: it omits articles, users, feed credentials, refresh frequency, user agents, and XPath scraping rules. Encrypt the archive, copy it off the server, and test restores regularly.

Restore

Restore irreversibly replaces data, extensions, and PostgreSQL. Use the same FreshRSS version and the active .env, then run:

./restore.sh ./backups/freshrss-YYYYMMDDTHHMMSSZ.tar
docker compose exec freshrss cli/health.php

The script first backs up the state being replaced, recreates all three volumes, restores the native dump, and starts FreshRSS. The archived configuration.env is retained for comparison only. This procedure has not passed a practical restore test; test it on a separate server first.

Update FreshRSS

Create a backup and read release notes. Replace the exact freshrss/freshrss:1.29.1 tag with a reviewed version, never latest, then run:

docker compose pull
docker compose up -d --wait
docker compose exec freshrss cli/health.php
docker compose logs --tail=200 freshrss

Application migrations run at startup. Do not update FreshRSS and PostgreSQL at the same time, so rollback remains unambiguous.

PostgreSQL major update

postgres:18-alpine is pinned independently. A major change requires a new data volume; old and new server majors must never share a cluster directory. Stop FreshRSS, keep a native pg_dump and complete data/extensions backup, create a new volume with the new image, and restore the dump. Upstream also offers cli/db-backup.php and cli/db-restore.php for portable per-user SQLite exports. Keep the old volume until the new database is verified.

Rollback

Never run an older FreshRSS over migrated data. Restore the previous exact FreshRSS and PostgreSQL tags and the complete pre-update archive with restore.sh. After a failed PostgreSQL major change, attach the old image only to its preserved old volume, or restore its dump into an empty cluster of the same major.

Stop and remove

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

docker compose down
docker volume rm freshrss-data freshrss-extensions freshrss-database
rm -rf ~/services/freshrss

Substitute actual names when volume variables differ.

Sources: Docker and first run, backup and OPML, access control and SSRF, server CSP, and PostgreSQL upgrades.

Troubleshooting

FreshRSS does not become healthy

docker compose ps
docker compose logs --tail=200 freshrss database
docker inspect --format '{{json .State.Health}}' "$(docker compose ps -q freshrss)"
docker compose exec freshrss cli/health.php

Check the three required distinct secrets, PostgreSQL health, free disk, and first-run errors. Passwords must be alphanumeric without spaces or shell characters. If an empty installation failed before completion, inspect logs and only then remove the empty volumes and retry; never delete working data.

Wrong redirects, URL, or WebSub callback

FRESHRSS_BASE_URL must exactly match the public URL: scheme, host, optional port and path, without a trailing /. It applies only during installation; for an existing instance, change base_url in data config.php using upstream’s documented procedure, then recreate the container. A dedicated subdomain is simpler and more reliable than a subpath.

An API client cannot sign in

Ensure the client uses FRESHRSS_ADMIN_API_PASSWORD, not the web form password, and the Google Reader endpoint on the same HTTPS host. Verify that Nginx forwards Authorization; Caddy and Traefik preserve it by default. FreshRSS does not need WebSockets.

Feeds on internal addresses are reachable

This is FreshRSS 1.29.1 behavior, not a recipe error. Do not add a wildcard internal-host allowlist: this version has no such supported variable, and allowing every address would create a false sense of protection. For untrusted users, block container egress to loopback, RFC1918, link-local, and cloud metadata addresses with network/firewall policy, or isolate FreshRSS on a dedicated network.

The client IP is wrong behind a proxy

The safe FRESHRSS_TRUSTED_PROXY=0 disables forwarded client-IP handling. If an exact client IP is required, trust only the final proxy IP/CIDR and secure the connection to FreshRSS. A broad range permits spoofed IP and Remote-User/X-WebAuth-User headers, potentially granting administrator access.

An extension breaks the page or CSP

Disable the third-party extension through the UI or restore extensions from a backup. Do not hide the problem by suppressing the warning or overriding Content-Security-Policy at the reverse proxy; FreshRSS emits its own CSP.

The reverse proxy returns 502

On the host, run curl -I http://127.0.0.1:8080/. Inspect the upstream, firewall, and proxy namespace. Inside a container, 127.0.0.1 refers to the proxy itself, not FreshRSS on the host; use a host gateway or a shared private network.

Official sources

Latest release: 2026-05-20 · GitHub Stars: 15,892 · metadata checked Sep 1, 2026, 12:22 AM 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.