FreshRSS is a lightweight, self-hosted RSS and Atom feed aggregator with multi-user support, extensions and APIs that mobile and desktop feed readers can sync with. In this tutorial you will run FreshRSS and PostgreSQL with Docker Compose on Ubuntu 24.04, publish it over HTTPS with Nginx, import your feeds, connect a mobile app through the Google Reader API and set up backups.

Prerequisites

To follow this guide you need:

  • A server running Ubuntu 24.04 LTS, for example a CubePath VPS. FreshRSS is light: 1 GB of RAM is enough for a few users.
  • A non-root user with sudo privileges.
  • Docker Engine and the Docker Compose plugin installed. See How to install Docker on Linux.
  • A domain name, referred to as your_domain (for example rss.example.com), with a DNS A record pointing to your server.

Step 1 - Writing the Docker Compose file

Create the project directory. The extensions folder holds third-party extensions (Step 6):

sudo mkdir -p /opt/freshrss/extensions
cd /opt/freshrss

Generate a database password and store it in a .env file, which Docker Compose reads automatically:

echo "DB_PASSWORD=$(openssl rand -hex 24)" | sudo tee /opt/freshrss/.env > /dev/null
sudo chmod 600 /opt/freshrss/.env

Create the Compose file:

sudo nano /opt/freshrss/compose.yaml
services:
  freshrss:
    image: freshrss/freshrss:latest
    restart: unless-stopped
    depends_on:
      - freshrss-db
    ports:
      - "127.0.0.1:8080:80"
    environment:
      TZ: Europe/Madrid
      CRON_MIN: "3,18,33,48"
      TRUSTED_PROXY: 172.16.0.1/12 192.168.0.1/16
    volumes:
      - data:/var/www/FreshRSS/data
      - ./extensions:/var/www/FreshRSS/extensions

  freshrss-db:
    image: postgres:17
    restart: unless-stopped
    environment:
      POSTGRES_DB: freshrss
      POSTGRES_USER: freshrss
      POSTGRES_PASSWORD: ${DB_PASSWORD}
    volumes:
      - db:/var/lib/postgresql/data

volumes:
  data:
  db:

What these settings do:

  • CRON_MIN makes the container refresh all feeds at minutes 3, 18, 33 and 48 of every hour. You do not need a cron job on the host.
  • TRUSTED_PROXY tells FreshRSS to trust the forwarded headers sent by Nginx, which reaches the container through the Docker bridge network. Without it, FreshRSS logs the proxy's address instead of the client's and may build wrong URLs.
  • 127.0.0.1:8080:80 publishes the web port only on localhost. Ports published by Docker bypass UFW, so this keeps FreshRSS reachable only through Nginx.

Step 2 - Starting FreshRSS

Start both containers:

cd /opt/freshrss
sudo docker compose up -d

Check that they are running and that FreshRSS answers on the local port:

sudo docker compose ps
curl -sL -o /dev/null -w '%{http_code}\n' http://127.0.0.1:8080/
200

FreshRSS serves its installation wizard, which you will complete after setting up HTTPS.

Step 3 - Configuring Nginx and HTTPS

Install Nginx and Certbot:

sudo apt update
sudo apt install nginx certbot python3-certbot-nginx

Create the server block:

sudo nano /etc/nginx/sites-available/freshrss
server {
    listen 80;
    listen [::]:80;
    server_name your_domain;

    location / {
        proxy_pass http://127.0.0.1:8080/;
        proxy_redirect off;
        proxy_buffering off;
        proxy_read_timeout 90s;
        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_set_header Authorization $http_authorization;
        proxy_pass_header Authorization;
    }
}

The Authorization lines make sure API clients' credentials reach FreshRSS.

Enable the site, open the firewall and request a certificate:

sudo ln -s /etc/nginx/sites-available/freshrss /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx
sudo ufw allow OpenSSH
sudo ufw allow 'Nginx Full'
sudo ufw enable
sudo certbot --nginx -d your_domain

Step 4 - Running the installation wizard

Open https://your_domain in your browser. The wizard checks the PHP environment and then asks for the database. Enter:

FieldValue
Type of databasePostgreSQL
Hostfreshrss-db
Database usernamefreshrss
Database passwordthe value of DB_PASSWORD in /opt/freshrss/.env
Databasefreshrss
Table prefixfreshrss_ (default)

Print the password with:

sudo cat /opt/freshrss/.env

In the next screen, create the administrator account and keep the Web form authentication method. When the wizard finishes, log in with the new account.

To confirm that the installation is complete and the database is reachable, list the users from the command line. FreshRSS's CLI tools must run as the www-data user inside the container:

cd /opt/freshrss
sudo docker compose exec --user www-data freshrss cli/list-users.php
admin

Step 5 - Adding feeds and setting retention

To add a feed, click Subscription management (or the + next to the feed list), paste the URL of a site or its RSS/Atom feed and choose a category.

If you are moving from another reader, export your subscriptions as an OPML file there and import it in FreshRSS under Subscription management > Import / export.

To check that refreshing works without waiting for the next scheduled run, refresh your user's feeds from the command line:

sudo docker compose exec --user www-data freshrss cli/actualize-user.php --user admin

New articles then appear in the main view. From now on, the container refreshes feeds automatically at the minutes set in CRON_MIN.

Old articles accumulate in the database over time. Under Settings > Archiving, set how long to keep articles and the maximum number to keep per feed. Starred articles are always kept.

Step 6 - Installing an extension

Extensions add features such as embedded YouTube players or custom styling. The official collection lives in the FreshRSS/Extensions repository. Clone it to a temporary directory and copy the extension you want, for example the YouTube extension, into the mounted extensions folder:

sudo apt install git
git clone --depth 1 https://github.com/FreshRSS/Extensions.git /tmp/freshrss-extensions
sudo cp -r /tmp/freshrss-extensions/xExtension-YouTube /opt/freshrss/extensions/

Open Settings > Extensions. The YouTube extension appears in the list; click the toggle to enable it. Only administrators can enable system-wide extensions, while user extensions are enabled per account.

Step 7 - Connecting mobile and desktop apps

FreshRSS implements the Google Reader API and the Fever API. The Google Reader API supports categories and read status sync, so prefer it when your app offers both.

First allow API access for the whole instance as administrator: Settings > Authentication > check Allow API access and save.

Then each user sets a dedicated API password under Settings > Profile > API management. Apps use this password instead of the login password, so a leaked phone app password does not expose the web account.

Test the Google Reader API from your computer, replacing the username and API password:

curl 'https://your_domain/api/greader.php/accounts/ClientLogin?Email=admin&Passwd=your_api_password'
SID=admin/0f1e...
LSID=null
Auth=admin/0f1e...

An Auth= line means the API works. You can also open https://your_domain/api/ in a browser, which runs a self-check and shows the endpoint URLs.

In your app, choose FreshRSS or "Google Reader API" as the account type and enter:

Server:   https://your_domain/api/greader.php
Username: your FreshRSS username
Password: your API password

Apps that support FreshRSS this way include NetNewsWire (iOS and macOS), Read You and FeedMe (Android), and Fluent Reader (desktop).

Step 8 - Adding users and backing up

To create an account for someone else from the command line:

sudo docker compose exec --user www-data freshrss cli/create-user.php --user alice --password 'a_strong_password'

Administrators can also manage users under Settings > Manage users.

For backups, dump the PostgreSQL database, which holds all users' feeds, articles and settings:

sudo mkdir -p /var/backups/freshrss
cd /opt/freshrss
sudo docker compose exec -T freshrss-db pg_dump -U freshrss freshrss \
  | gzip | sudo tee /var/backups/freshrss/freshrss-$(date +%F).sql.gz > /dev/null

Also export each user's subscription list as OPML, which is useful to move to another reader:

sudo docker compose exec -T --user www-data freshrss cli/export-opml-for-user.php --user admin \
  | sudo tee /var/backups/freshrss/admin-$(date +%F).opml > /dev/null

Copy /var/backups/freshrss off the server with your usual backup tool, and schedule both commands in /etc/cron.d if you want daily copies.

Updating FreshRSS

Take a database dump first, then pull the new images and recreate the containers. FreshRSS updates its database schema automatically:

cd /opt/freshrss
sudo docker compose pull
sudo docker compose up -d

Keep the PostgreSQL major version (postgres:17) pinned. Moving to a new major version requires a dump and restore, not just a new image.

Troubleshooting

Feeds stop refreshing. Check the container log with sudo docker compose logs --tail 50 freshrss and confirm CRON_MIN is set. Run cli/actualize-user.php as shown in Step 5 to see errors for a specific user. Individual feeds with errors are marked in the feed list; open them to see the HTTP error returned by the site.

API login fails in the app but the web login works. Check that API access is allowed for the instance, that you are using the API password rather than the login password, and that the server URL ends in /api/greader.php. The https://your_domain/api/ self-check page points out common problems.

Wrong URLs or redirects to http:// or port 8080. FreshRSS does not trust the proxy headers. Make sure TRUSTED_PROXY is set as in Step 1 and the Nginx block sends X-Forwarded-Proto and X-Forwarded-Port, then run sudo docker compose up -d. The base URL detected during installation is stored as base_url in /var/www/FreshRSS/data/config.php inside the container; if it is wrong, correct it there and restart the container.

Conclusion

FreshRSS now runs on Ubuntu 24.04 with PostgreSQL behind Nginx with HTTPS, refreshes feeds every 15 minutes and syncs with mobile apps through the Google Reader API. As next steps, organize feeds into categories and filters (for example, marking articles with certain keywords as read automatically), create accounts for your team, and schedule the database dump with cron so your subscriptions and starred articles are always recoverable.