Skip to content
Serverküche
Search

Loading search … (only available on the published site).

Applications Difficulty: Intermediate

FreshRSS: Your Own RSS Reader

All your news sources in one place, no algorithm and no tracking: FreshRSS as a self-hosted RSS reader behind Traefik – with mobile app sync and backup.

· 9 min read ·Duration: approx. 40 minutes
Table of contents

RSS is the best news format most people have forgotten: you subscribe to sources directly, with no algorithm, no ads and no one watching what you read. FreshRSS gathers all your sources in one self-hosted place – and even syncs with the RSS apps on your phone.

What are we building?

Your own RSS reader with FreshRSS (v1.30.4) behind Traefik, collecting your news sources and keeping them fresh via cron in the background. By the end you read news sites, blogs and podcast feeds in one place, grouped into categories – and can connect mobile apps like Reeder or FeedMe via the built-in Google Reader API. FreshRSS is refreshingly lightweight: SQLite is enough, no separate database needed.

Prerequisites

🍳 Recommendation Ad

VPS 1000 G12.5

4 vCores · 8 GB RAM · 128 GB SSD

from €14.50/month

FreshRSS is frugal – the smallest VPS handles many feeds.

Go to netcup →

💶 1 month free:

Single use, valid for VPS 1000 G12.5. Redeem in the cart →

💶 €5.95 voucher for new netcup customers: always valid · not for domains or VPS Lite

All netcup vouchers →

Step by step

Step 1: Create the Compose file

FreshRSS keeps everything in a data directory (SQLite database, config, cache). Create the project:

Terminal
mkdir -p /opt/freshrss && cd /opt/freshrss

The compose.yaml – replace YOUR_DOMAIN:

YAML
services:
  freshrss:
    image: freshrss/freshrss:1.30.4
    restart: unless-stopped
    volumes:
      - ./data:/var/www/FreshRSS/data
      - ./extensions:/var/www/FreshRSS/extensions
    environment:
      TZ: Europe/Berlin
      CRON_MIN: '13,43'
    networks: [proxy]
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.freshrss.rule=Host(`YOUR_DOMAIN`)"
      - "traefik.http.routers.freshrss.entrypoints=websecure"
      - "traefik.http.routers.freshrss.tls.certresolver=le"
      - "traefik.http.routers.freshrss.middlewares=freshrss-only-me"
      - "traefik.http.middlewares.freshrss-only-me.ipallowlist.sourcerange=YOUR_IPV4/32,YOUR_IPV6_PREFIX::/64"
      - "traefik.http.services.freshrss.loadbalancer.server.port=80"

networks:
  proxy:
    external: true

CRON_MIN is a nice touch of the official image: the startup code turns it into a cron entry inside the container that refreshes the feeds at the given minutes (here 13 and 43 of every hour) – you need no external timer.

Do not create the data and extensions directories yourself: the container creates them on first start and sets the permissions itself (root:www-data, group writable – see Users & permissions for what that means). That holds on hardened servers with UMASK 027 as well.

freshrss-only-me lets only your own address through until the setup is finished; everyone else gets 403 Forbidden. As long as no admin account exists, the instance otherwise belongs to whoever opens the page first – and scanners sometimes find new hostnames via the public certificate logs within seconds of the start. Look up your addresses on your own computer, not on the server: curl -4 -s https://ifconfig.me for the IPv4, curl -6 -s https://ifconfig.me for the IPv6 (take the first four blocks). If you have no IPv6, drop the second entry including the comma – but only then: browsers prefer IPv6, and an IPv4-only list locks you out yourself. More on this in Hardening Traefik.

Step 2: Start

Terminal
docker compose up -d

A quick look at the container status – Up, and port 80 stays inside the container because everything from outside goes through Traefik:

Terminal
docker compose ps
Ausgabe
NAME                  IMAGE                      COMMAND                  SERVICE    CREATED          STATUS          PORTS
freshrss-freshrss-1   freshrss/freshrss:1.30.4   "./Docker/entrypoint…"   freshrss   11 seconds ago   Up 10 seconds   80/tcp

From your own computer, check that FreshRSS responds with valid HTTPS (the 302 is the redirect to the install wizard; from the server, freshrss-only-me only returns 403):

Terminal
curl -sI https://YOUR_DOMAIN/ | head -1
Ausgabe
HTTP/2 302

If you get a certificate error instead of the 302, Traefik hasn’t issued the certificate yet – that takes a few seconds after the first start.

Step 3: The install wizard

Open https://YOUR_DOMAIN/ in the browser. The wizard walks you through setup in five steps – language, checks, database, general configuration, done:

The FreshRSS install wizard, step 1: language selection with version 1.30.4
The install wizard guides you through language, system check, database and admin account

Step 2 lists the PHP and file permission checks; with the official image you only get green “OK!” lines there, including the one for the data directory. Choose SQLite as the database type (no further details needed), Web form authentication and create your administrator account with a strong password. The last step confirms “Congratulations! The installation process was successful.” – only the click on Complete installation finishes setup and takes you to the login.

Alternative: setup from the command line

To script the initial setup without a browser: docker compose exec -u www-data freshrss ./cli/do-install.php --default-user admin --auth-type form --db-type sqlite --language en --base-url https://YOUR_DOMAIN, then ./cli/create-user.php --user admin --password 'YOUR_PASSWORD'. The --base-url matters: the graphical wizard fills it in by itself, the CLI path does not – and without it the status page at /api/ later advertises a wrong API address.

Once the installation is complete, remove the lock again: delete the two lines containing freshrss-only-me from the compose.yaml and apply the change:

Terminal
docker compose up -d

Step 4: Subscribe to your first feeds

Click Subscription management → Add a feed or category at the top left. Enter a source’s feed URL (e.g. https://feeds.arstechnica.com/arstechnica/index) and assign it to a category. FreshRSS detects the feed format automatically. After adding several sources the management view looks like this:

The FreshRSS subscription management with feeds in the News, Blogs and Uncategorized categories
Subscription management: feeds grouped into categories, with import/export and statistics

Bring existing feeds via OPML

Switching from another reader (Feedly, Inoreader, old Google Reader)? Export an OPML file there and import it under “Import / export”. All your subscriptions move over in one click – including the category structure.

Step 5: Reading

Back in the reading view, FreshRSS collects articles from all sources chronologically, grouped by category. Unread counters per feed, stars for favorites, full-text search – it’s all there:

The FreshRSS reading view with recent articles from Ars Technica, The Verge and LWN.net, grouped in categories
The reading view: all sources in one place, with unread counters and search

Step 6: Connect mobile apps (optional)

FreshRSS speaks the Google Reader API, so many mobile apps can use it as a sync backend. That takes two switches, not one – and the first app login usually fails on exactly that.

First the server-wide switch: Settings → Administration → Authentication → Allow API access, then submit. FreshRSS asks for your password again before the administration pages; that reauthentication is then valid for 20 minutes.

The FreshRSS Authentication settings page with the Allow API access box ticked
The server-wide switch: without “Allow API access” the API does not answer at all

Then the personal password: Settings → Profile → External access via API, where you set a separate API password – not your login password, because it travels in clear text and can show up in server logs. The same page links the status page https://YOUR_DOMAIN/api/, which tells you the correct API address and tests the configuration itself.

In the app (e.g. Reeder, FeedMe, NetNewsWire) choose “FreshRSS” or “Google Reader API”, enter https://YOUR_DOMAIN/api/greader.php, your username and the API password – and keep reading in sync on the go.

Feeds from your own network since 1.30

Since FreshRSS 1.30, targets on the local network are blocked – a deliberate protection against server-side request forgery. A feed on 127.0.0.1 or on a neighbouring container is rejected with URL is not allowed to be resolved. If you need such a source (an RSS bridge in the same Compose network, say), allow it explicitly: INTERNAL_HOST_ALLOWLIST: rss-bridge:80 as an environment variable in the Compose file – one entry per host, host:port or CIDR. A * allows everything and defeats the protection.

When things go wrong

The page responds with 403 Forbidden. The lock from step 1 doesn’t let your address through. Usually the IPv6 is missing or has changed – look up both addresses again (see there), correct sourcerange and restart with docker compose up -d.

After setup you end up back at step 1 of the wizard. The installation only counts as finished once you click “Complete installation” in the last step – close the browser window before that and the next visit drops you back into the wizard. Just walk through it again and click all the way to the end this time. You don’t have to worry about file permissions along the way: the official image fixes group and mode of the data directory on every start – including a folder restored from a backup.

Feeds don’t refresh automatically. First check whether the cron entry inside the container exists at all: docker compose exec freshrss crontab -l must print a line starting with your CRON_MIN minutes (13,43 * * * * …). If it’s missing, CRON_MIN wasn’t set at start – add the variable and run docker compose up -d. What the last run did is logged inside the container at /tmp/FreshRSS.log (docker compose exec freshrss tail /tmp/FreshRSS.log). You can trigger a refresh anytime with docker compose exec -u www-data freshrss ./cli/actualize-user.php --user admin.

The mobile app won’t connect and reports a server error. If https://YOUR_DOMAIN/api/greader.php answers 503 Service Unavailable!, the server-wide switch from step 6 is missing: without the “Allow API access” box under Administration → Authentication, even a configured API password gets you nowhere. With both in place the API answers a login with 200 and returns an Auth token.

A feed from your own network stays empty. Since 1.30 FreshRSS blocks local targets; the log says URL is not allowed to be resolved or Fetching this URL is not allowed, because the host’s IP is not in the allowlist. The fix is the INTERNAL_HOST_ALLOWLIST environment variable (see the box above) – or the same list under Administration → System configuration. If a feed from the public internet stays empty, the URL is usually an HTML page instead of the feed; search the page source for application/rss+xml.

Older articles disappear after a while. That’s archiving, and its defaults are deliberately strict: FreshRSS keeps articles for three months and at most 200 per feed, but never fewer than 50 – favourites and labelled articles are left alone. If you want to scroll back further, raise the values under Settings → Archiving or per feed in its settings; the database grows accordingly.

Your problem is not listed? Search all error messages →

Maintenance & backups

  • Updates. Set the image tag (freshrss/freshrss:1.30.4) to the new version, then docker compose pull && docker compose up -d; FreshRSS migrates its database automatically at start. The project announces larger jumps in the release notes – 1.30, for instance, was a security release with one deliberate tightening (see the box about local feeds). Your normal update process handles the rest.
  • Backup. The entire state lives in the data folder (SQLite database + config). Add it to your Restic backup – then subscriptions, categories and read markers are safe. Copying a live SQLite file mid-write is a bad idea, though: docker compose exec -u www-data freshrss ./cli/db-backup.php writes a consistent copy to data/users/<user>/backup.sqlite first, which your backup then picks up. Additionally a regular OPML export of your feed list makes a human-readable, reader-independent backup.
  • Low effort. FreshRSS is a “set it up once, then it just runs” service. The only recurring task is curating your feed list – under Statistics → Idle feeds you see at a glance which sources haven’t delivered anything for months, and can prune them.

Last updated: Oct 31, 2026

You might also like