When you self-host FreshRSS on a managed VPS, you and your team read your RSS feeds from a machine of your own, which our teams keep up to date and monitor day and night. FreshRSS is free software under the AGPL-3.0 license. This guide follows its official documentation: installing with Docker Compose, HTTPS, feed refresh, accounts and login, the API for mobile apps, the database, backups and updates.
By Simon Vasconcelos Lee
Published on 11 November 202613 minute read
data and extensions, behind an HTTPS reverse proxy.CRON_MIN variable refreshes feeds on its own: the project recommends about two runs per hour.data volume and, with PostgreSQL or MySQL, the database, then update with docker compose pull.FreshRSS is a self-hosted RSS feed aggregator written in PHP. Its README describes it as lightweight, easy to work with, powerful and customizable, translated into more than twenty languages. It is a multi-user application, with an anonymous reading mode and custom tags.
FreshRSS is developed by its community on GitHub, and its official website is published by the FreshRSS community. Extensions live in a separate repository, and the documentation covers users, administrators and developers.
FreshRSS is published under the GNU Affero General Public License, version 3: its composer.json manifest declares it as AGPL-3.0, and its README links to the license text. That text affirms an unlimited permission to run the unmodified program. You can therefore use the image published by the project for your company and your team, with no license to buy.
Section 13 adds a condition specific to the AGPL: if you modify the program, your version must offer everyone who uses it remotely over a network a way to get its source code at no charge. A third-party extension added to the extensions volume follows its own license.
The project publishes its official images on Docker Hub, as freshrss/freshrss, along with an example Compose file. On a server with Docker installed, create a folder named freshrss and put this compose.yaml in it, drawn from that file and the Docker README:
volumes:
data:
extensions:
services:
freshrss:
image: freshrss/freshrss:latest
container_name: freshrss
hostname: freshrss
restart: unless-stopped
logging:
options:
max-size: 10m
volumes:
- data:/var/www/FreshRSS/data
- extensions:/var/www/FreshRSS/extensions
ports:
- 127.0.0.1:8080:80
environment:
TZ: UTC
CRON_MIN: '3,33'
TRUSTED_PROXY: 172.16.0.1/12 192.168.0.1/16
healthcheck:
test: ["CMD", "cli/health.php"]
timeout: 10s
start_period: 60s
start_interval: 11s
interval: 75s
retries: 3
docker compose up -d
127.0.0.1: FreshRSS is reached only through the reverse proxy, set up on the same server. The project’s example file publishes port 8080 for local use; the 127.0.0.1 address reserves it for the proxy here.data volume holds the configuration, the accounts and, with SQLite, each user’s database. The README points out that it contains all personal data. Docker Compose names it after the folder: freshrss_data.extensions volume holds third-party extensions, if you add any.TZ takes the server’s time zone, in PHP format; it defaults to UTC.TRUSTED_PROXY keeps the value from the example file: the private ranges the proxy’s requests come from. It decides which address goes into the logs, the visitor’s or the proxy’s.cli/health.php at regular intervals, so Docker knows whether the instance responds.The latest tag, the default, tracks the latest versioned release; edge tracks the rolling release; a tag like 1.30.4 pins a version, and the -alpine suffix gives the same thing on an Alpine base instead of Debian. The updates section comes back to this choice.
Once the reverse proxy is in place, open your HTTPS address: the setup wizard asks for the language, checks the server, offers the database choice, then creates the administrator account, with its password and the login method. SQLite works with nothing to prepare. Installation also works from the command line, with cli/do-install.php and cli/create-user.php, run through docker exec --user www-data freshrss.
On resources, the README asks for a light server, running Linux or Windows, and notes that FreshRSS even runs on a Raspberry Pi 1, with response times under a second, with 150 feeds and 22,000 articles. The size depends on the number of users, feeds and articles kept. On a managed VPS, describe your use to us and we will recommend the right size.
For production, the Docker README advises a reverse proxy on the host server, providing HTTPS. First choose a domain name or subdomain, such as rss.example.com, pointing to the server with A and AAAA records. The project recommends Traefik and publishes a full Compose file for it, with Let’s Encrypt certificates and the redirect from HTTP to HTTPS; it also gives examples for Apache, Nginx and Caddy.
Caddy gets the certificate and enables HTTPS on its own. This block follows the documentation’s example for a subdomain, pointed here at the port published locally:
rss.example.com {
reverse_proxy 127.0.0.1:8080
}
This block follows the Docker README’s Nginx example for a domain root, reduced to port 80 so that Certbot can set up HTTPS. The last two lines pass along the Authorization header, which the Google Reader API needs:
upstream freshrss {
server 127.0.0.1:8080;
keepalive 64;
}
server {
listen 80;
listen [::]:80;
server_name rss.example.com;
location / {
proxy_pass http://freshrss/;
proxy_redirect off;
proxy_buffering off;
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_set_header X-Forwarded-Port $server_port;
proxy_read_timeout 90;
proxy_set_header Authorization $http_authorization;
proxy_pass_header Authorization;
}
}
Certbot then installs the certificate and takes care of renewing it:
sudo certbot --nginx
FreshRSS keeps its public address in base_url, in the data/config.php file. The API and WebSub rely on it: check that it holds your https address. The Docker README shows where to find this file in the volume:
docker volume inspect freshrss_data
sudo nano /var/lib/docker/volumes/freshrss_data/_data/config.php
docker compose restart freshrss
'base_url' => 'https://rss.example.com/',
FreshRSS fetches new articles with its app/actualize_script.php script. The Docker image ships a scheduled job that runs it at the minutes given in CRON_MIN. Without this variable, or with an empty value, the job stays off and feeds no longer refresh on their own.
13,43; */20 works too.base_url, and the setup wizard turns it on when the server has one.Another documented method: a scheduled job on the host server, which calls the script inside the container. In that case, remove CRON_MIN from compose.yaml, to keep a single method:
7,37 * * * * root docker exec --user www-data freshrss php ./app/actualize_script.php > /tmp/FreshRSS.log 2>&1
Since version 1.30.0, FreshRSS refuses local network addresses such as 127.0.0.1 by default, to guard against server-side request forgery. A neighboring service that produces feeds, such as RSS-Bridge, is allowed through the INTERNAL_HOST_ALLOWLIST variable, in the form rss-bridge:80, or in the system configuration. The value * removes all checks, and the documentation calls it unsafe.
During installation, FreshRSS offers form login, which requires JavaScript. It is the default method, and it suits most instances. The documentation describes others:
| Method | What it does | Where it is set |
|---|---|---|
| Form | A username and password specific to FreshRSS | The setup wizard |
| HTTP authentication | The web server controls access, and new users are created on their first login | A p/i/.htaccess file mounted into the container, with its .htpasswd |
| External authentication | The proxy passes the logged-in user in the Remote-User or X-WebAuth-User header | TRUSTED_PROXY, which lists the trusted proxies |
| OpenID Connect | Login goes through your identity provider, using the Apache module mod_auth_openidc | OIDC_ENABLED and the OIDC_* variables, in the Debian image on x86_64 |
The documentation warns that FreshRSS trusts any address listed in TRUSTED_PROXY: a poorly secured proxy would let an attacker set the header and gain admin access. Here, the port listens only on 127.0.0.1, which reserves it for the server’s own proxy. The documentation also advises against any instance without authentication as soon as the server can be reached from outside your network.
mailer and smtp in data/config.php: the documentation says sending through PHP’s mail function is not yet supported inside the official Docker images.For an instance shared by several people, the documentation advises opening accounts to people you trust and keeping the instance behind a proxy or firewall that blocks access to internal address ranges.
FreshRSS works in the browser, on large and small screens alike, and in apps for Android, iOS, Linux, Windows and macOS. These apps use one of its two APIs: Google Reader, which the README rates as the best, and Fever, with more limited features, less efficient and less safe.
https://rss.example.com/api/greader.php, or https://rss.example.com/api/fever.php for Fever, with the username and that API password.To check, the link shown next to the API password, in the profile, opens a test page: PASS on the full check confirms that everything works. The Docker image already sets Apache to accept encoded slashes, which some apps need; with Nginx in front, keep the Authorization header forwarding.
The README keeps a table of compatible apps, with their platform, API and features: Capy Reader and Readrops on Android, FeedFlow on mobile and desktop, RSS Guard and NewsFlash on desktop, ReadKit and Reeder Classic on iOS and macOS, among others.
FreshRSS has SQLite built in, which the Docker README describes as the easiest option, with good performance. The requirements page recommends PostgreSQL 10 or newer, and also accepts MariaDB 10.6 and MySQL 8.0 or newer. You make the choice in the setup wizard.
| Database | Setup | Backup |
|---|---|---|
| SQLite | None: one file per user, data/users/<user>/db.sqlite | The data volume |
| PostgreSQL | A postgres service in the same compose.yaml | pg_dump, or cli/db-backup.php |
| MySQL or MariaDB | Your database server, and its host name in the wizard | mysqldump, or cli/db-backup.php |
For PostgreSQL, add this service to compose.yaml, drawn from the project’s docker-compose-db.yml file, and the db volume to the list of volumes. The password goes in a .env file, next to compose.yaml:
freshrss-db:
image: postgres:18
container_name: freshrss-db
hostname: freshrss-db
restart: unless-stopped
logging:
options:
max-size: 10m
volumes:
- db:/var/lib/postgresql
environment:
POSTGRES_DB: freshrss
POSTGRES_USER: freshrss
POSTGRES_PASSWORD: ${DB_PASSWORD}
DB_PASSWORD='a-long-unique-password'
In the wizard, choose PostgreSQL and enter freshrss-db as the host, with the user, the password and the freshrss database.
You can revisit this choice later. The documentation describes moving from one database to another, from MySQL to PostgreSQL for example, or to a newer major version of PostgreSQL: cli/db-backup.php copies each user’s database into a SQLite file, then cli/db-restore.php loads it into the new database.
The README states that FreshRSS comes with no warranty at all: your backups keep your subscriptions, your articles and your settings. Here is what the documentation asks you to back up:
| Item | What it holds | Backup |
|---|---|---|
data/ | The configuration, accounts, logs and, with SQLite, the databases | Required; cache/ can be skipped, FreshRSS rebuilds it |
extensions/ | Third-party extensions | Recommended, if you use any |
| The external database | With PostgreSQL, MySQL or MariaDB, the users’ data | Required, separately |
The documentation considers it safer to stop the service during maintenance operations. Docker volumes are copied with a temporary container, the method described in Docker’s documentation:
docker compose stop freshrss
docker run --rm --volumes-from freshrss -v "$PWD":/backup ubuntu tar -czf /backup/freshrss-$(date +%F).tgz --exclude=data/cache -C /var/www/FreshRSS data extensions
docker compose start freshrss
Since version 1.29.1, FreshRSS can export each user’s database to a SQLite file by itself, whatever database you use, and keep only the most recent ones. Turn this export on in data/config.php:
'auto_sqlite_export' => [
'enabled' => true,
'retention' => 7,
],
Then schedule the command on the server, here every night. Each run writes data/users/<user>/sqlite-backups/<date>.sqlite and deletes files beyond the last seven:
20 2 * * * root docker exec --user www-data freshrss cli/export-sqlite-auto.php --quiet
With PostgreSQL, add a dump with the database’s own tool, as in the Docker README. The -T option drops the terminal, which a scheduled job has no use for:
docker compose exec -T freshrss-db pg_dump -U freshrss freshrss | gzip -9 > freshrss-postgres-$(date +%F).sql.gz
The OPML export, from the interface, backs up your feed list, without some FreshRSS-specific settings such as credentials or page scraping rules. For a complete copy, keep the SQLite exports: stored on the server’s disk, they are also part of its nightly backups.
docker compose stop freshrss.docker run --rm --volumes-from freshrss -v "$PWD":/backup ubuntu tar -xzf /backup/freshrss-YYYY-MM-DD.tgz -C /var/www/FreshRSS.cli/db-restore.php --delete-backup --force-overwrite.docker compose start freshrss, then check that your feeds and articles are there.Repeat this exercise from time to time, on a copy: that is how you check that a backup works.
The Docker README gives two commands, from the compose.yaml folder. The volumes stay in place:
docker compose pull
docker compose up -d
FreshRSS publishes on two channels. Versioned releases, tag latest, come out a few times a year, on no set schedule, and their fixes are not backported to older versions. The rolling release, tag edge, gets new features and fixes as they land: since version 1.30.0, the project recommends it for faster security patches.
data/, extensions and the database before each update.1.29.1 make this path possible.To follow new releases, the project publishes an Atom feed of its versions: subscribe your own FreshRSS instance to it.
Self-hosting means running the server on top of the application. On a managed VPS, our teams take charge of the server and proactively monitor your instance; FreshRSS remains your software, with its settings and its data.
| Topic | Our teams | You |
|---|---|---|
| The server | System and security updates, server interventions included | The timing that suits your business |
| Monitoring | Server monitored 24/7, with intervention without waiting for your call | Reading your instance’s logs |
| FreshRSS | Proactive monitoring: errors, slowdowns and disk space spotted, fix recommended | Its installation, updates and settings, which you apply, yourself or through your AI assistant |
| Accounts and feeds | The server that carries them, monitored and backed up | Users, subscriptions, extensions and API passwords |
| Backups | Every night, encrypted and kept off the machine; restores included | FreshRSS’s own exports, and the restore request, when you need one |
| Migration | Your current server migrated, free of charge | The timing of the switch |
If you would rather have our teams work inside the application itself, that service is billed by the hour or at a fixed rate, with an estimate shared before the work starts.
With a Simafri Managed VPS, our teams keep your FreshRSS instance’s server up to date, monitor it day and night and step in without waiting for your call; they also proactively monitor your instance and recommend useful fixes, which you apply, yourself or through your AI assistant. Backups are encrypted every night and kept off the machine, restores are included, and migrating your current server is free.
Yes. FreshRSS is under the AGPL-3.0 license: you can run it at work with no license to buy. If you modify it, your version must offer its source code to the people who use it over the network. What remains is the cost of the server that runs it.
Create a compose.yaml with the freshrss/freshrss image, the data and extensions volumes and the port published on 127.0.0.1, then run docker compose up -d. Next, put an HTTPS reverse proxy in front, open your address and follow the setup wizard.
With the Docker image, give CRON_MIN the minutes to run at, for example 13,43: the project recommends about two runs per hour. Without CRON_MIN, the built-in job stays off. A scheduled job on the server that runs app/actualize_script.php inside the container does the same work.
SQLite is built in and needs nothing prepared; the Docker README describes it as the easiest, with good performance. The requirements page recommends PostgreSQL 10 or newer, and accepts MariaDB 10.6 and MySQL 8.0. The db-backup and db-restore commands let you switch databases later.
Turn on API access in the Authentication section, then set an API password in your profile. In a compatible app, enter your instance’s api/greader.php address, the Google Reader API being the best according to the README, or api/fever.php.
Not by default: the configuration limits accounts to the administrator’s. You create the others under Administration, Manage users, or you raise the number of accounts in the system configuration to show the signup form.
Copy the data and extensions volumes with the service stopped. Also turn on auto_sqlite_export and schedule cli/export-sqlite-auto.php, which exports each user’s database to SQLite every night. With PostgreSQL, add a pg_dump. Test the restore regularly.
A managed VPS covers the server: system, security, monitoring and backups. Our teams also proactively monitor your instance and recommend useful fixes, which you apply. Work inside the application is billed by the hour or at a fixed rate.
Simafri
Let's talk about your project
Tell us what you need in a few words: we will get back to you quickly.
Thank you! Your request has been sent. We'll get back to you shortly.
Prefer email? Write to us at support@simafri.com.