Self-Host FreshRSS
Deploy FreshRSS with Docker Compose, import feeds via OPML, connect mobile clients through the Google Reader API, and manage multiple users on one instance.
Before you start
- ▸Docker Engine 24+ with the Compose plugin installed
- ▸A server or VPS with at least 256 MB free RAM and a static IP or hostname
- ▸Port 8080 open in your firewall (or a reverse proxy already configured)
- ▸An OPML export file from your previous RSS reader (optional but useful)
FreshRSS is a self-hosted RSS aggregator that punches well above its weight: a clean web UI, a compatible Google Reader API for mobile clients, multi-user support, and a container image that makes deployment straightforward. This guide walks through a production-ready Docker Compose deployment, importing your existing feeds via OPML, enabling the mobile API, and adding additional users.
Prerequisites and Directory Layout
You need Docker Engine 24+ and the Compose plugin (docker compose, not the legacy docker-compose binary) installed on your server. Confirm both are present:
docker version
docker compose version
Create a working directory and the two persistent data paths FreshRSS expects:
mkdir -p ~/freshrss/{data,extensions}
cd ~/freshrss
Write the Compose File
Create compose.yaml in ~/freshrss. The official image is freshrss/freshrss. Pin a specific tag in production rather than using latest; at the time of writing 1.24.1 is the current stable release.
cat > compose.yaml <<'EOF'
services:
freshrss:
image: freshrss/freshrss:1.24.1
container_name: freshrss
restart: unless-stopped
ports:
- "8080:80"
volumes:
- ./data:/var/www/FreshRSS/data
- ./extensions:/var/www/FreshRSS/extensions
environment:
TZ: America/New_York
CRON_MIN: "1,31"
FRESHRSS_ENV: production
EOF
CRON_MIN controls the two minutes past each hour when the built-in cron fetches new articles. Adjust TZ to your IANA timezone string. If you plan to put FreshRSS behind a reverse proxy (nginx, Caddy, Traefik), remove the ports block and use a shared Docker network instead.
Start the Container
docker compose up -d
docker compose logs -f freshrss
Wait for the log line AH00558 (Apache listening) or similar before proceeding. The initial startup takes only a few seconds.
Run the Web Installer
Open http://YOUR_SERVER_IP:8080 in a browser. The web installer walks you through five screens:
- Language — pick your preferred locale.
- System check — all items should show green. If the
data/directory permissions fail, runsudo chown -R 1000:1000 ~/freshrss/dataand refresh. - Database — choose SQLite for a single-user or lightly-loaded instance. For multi-user production use, add a PostgreSQL or MySQL service to your Compose file and select that here.
- General settings — set the instance URL exactly as you will access it (important for the API later).
- Admin account — choose a strong password; this account has full control.
After submission you land on an empty dashboard. FreshRSS is running.
Import Feeds via OPML
If you are migrating from another reader, export an OPML file there first. In FreshRSS:
- Click the gear icon (top right) → Subscription management.
- Select the Import/Export tab.
- Under Import, choose your
.opmlfile and click Import.
FreshRSS preserves folders (categories) encoded in the OPML. Large imports can take a minute to process; watch progress in the success banner. Feeds will not have articles until the next cron run or until you trigger a manual refresh:
docker exec freshrss php /var/www/FreshRSS/app/actualize_script.php
Enable the Mobile API
FreshRSS exposes a Google Reader–compatible API used by Android and iOS clients (Reeder, FeedMe, Read You, NewsFlash, etc.). It is disabled by default.
Instance-level setting
- Settings → Administration → Authentication.
- Check Allow API access and save.
Per-user API password
Each user must set a separate API password (it is not the login password):
- Click your username → Profile.
- Under API management, enter and confirm an API password, then save.
Verify the API endpoint is reachable:
curl -s http://YOUR_SERVER_IP:8080/api/greader.php
A valid response returns a short JSON object with {"API":"...","Auth":"..."}. If you get a 404, the FRESHRSS_ENV=production env var may not have been set; recreate the container after confirming it is present in compose.yaml.
Configuring a mobile client
In your client, select Fever or Google Reader / FreshRSS as the server type (client-dependent) and supply:
- Server URL:
http://YOUR_SERVER_IP:8080/api/greader.php - Username: your FreshRSS login username
- Password: the API password you set above (not the web login password)
Multi-User Setup
FreshRSS supports multiple independent users, each with their own feeds, read state, and settings.
Enable multi-user mode
- Settings → Administration → Users.
- Check Allow registration of new users if you want self-registration, or leave it unchecked and create accounts manually.
Create a user from the CLI
Creating users via the command line is more reliable for automation:
docker exec freshrss \
php /var/www/FreshRSS/cli/create-user.php \
--username alice \
--password 'Ch@ngeMeN0w' \
--api-password 'AliceApiPass1!' \
--email [email protected]
The --api-password flag sets the mobile API credential directly, saving the user a trip to the profile page.
User data isolation
Each user gets a separate subdirectory under data/users/ on the host. Backups are straightforward: snapshot the entire data/ volume.
Putting FreshRSS Behind a Reverse Proxy
Exposing port 8080 directly is fine for a local network, but for internet-facing installs, put FreshRSS behind Caddy or nginx with TLS. A minimal Caddyfile entry:
rss.example.com {
reverse_proxy freshrss:80
}
Add Caddy to the same Compose file on a shared network, remove the ports block from the FreshRSS service, and update the Base URL in FreshRSS under Settings → Administration → System to your HTTPS URL. The API URL also becomes https://rss.example.com/api/greader.php.
Verification
Run through this checklist after setup:
- Web UI loads and shows imported feeds with article counts.
docker exec freshrss php /var/www/FreshRSS/app/actualize_script.phpruns without PHP errors.- The API endpoint returns JSON, not a 404 or 500.
- A mobile client can authenticate and sync articles.
ls ~/freshrss/data/users/shows a directory for each user.
Troubleshooting
Blank page or 500 error after install
Check PHP and Apache logs directly:
docker compose logs freshrss 2>&1 | tail -50
The most common cause is a data/ directory that is not writable by the container's www-data user (uid 33 in the official image). Fix with:
sudo chown -R 33:33 ~/freshrss/data
Feeds not updating
Confirm the internal cron is running:
docker exec freshrss crontab -l
If cron is absent, the CRON_MIN env var was probably not applied. Recreate the container: docker compose up -d --force-recreate. You can also drive refreshes from the host cron instead, calling actualize_script.php directly.
API returns 403
The API is enabled at the instance level but the user has not set an API password, or the wrong password is being sent. Re-check the per-user API password in the Profile page and confirm the client is not sending the web login password.
OPML import produces duplicate feeds
FreshRSS deduplicates by feed URL within a single user, but if you import twice it will try to add them again. Remove duplicates via Subscription management → select the feed → Delete.
Frequently asked questions
- Can I use a database other than SQLite?
- Yes. Add a PostgreSQL or MySQL service to your compose.yaml, then select that database type during the web installer. SQLite is fine for one or two users; switch to PostgreSQL if you expect many concurrent users or large feed volumes.
- Which mobile apps work with FreshRSS?
- Any app supporting the Google Reader API works: FeedMe and Read You on Android, Reeder and NetNewsWire (with GReader plugin) on iOS, and NewsFlash on Linux desktop. Use the greader.php endpoint, not the Fever endpoint, unless your app only lists Fever.
- How do I back up FreshRSS?
- Snapshot the entire data/ directory on the host. It contains the SQLite database, user settings, cached articles, and feed lists. Stop the container first for a consistent backup: docker compose stop && tar czf freshrss-backup.tar.gz data/ && docker compose start.
- How do I upgrade to a new FreshRSS version?
- Update the image tag in compose.yaml, then run docker compose pull && docker compose up -d. FreshRSS applies any necessary database migrations automatically on first start. Always back up data/ before upgrading.
- Can users register themselves, or must an admin create every account?
- Both modes are available. Enable Allow registration of new users in Administration → Users for self-registration. Leave it disabled and use the create-user.php CLI or the admin UI to maintain tighter control over who has access.
Related guides
Configure Prometheus Alertmanager
Configure Prometheus Alertmanager with routing trees, receivers, inhibition rules, grouping, Go templates, and PagerDuty/Slack on-call integrations.
Build an Intranet Server on Linux
Set up a complete small-office intranet on one Linux box: Nginx web server, dnsmasq local DNS, Samba file sharing, and a Wiki.js team wiki.
Build an nftables Firewall Script
Build a complete nftables firewall from scratch: tables, chains, sets, default-deny input policy, service allowlisting, and persistent systemd configuration.
Caddy as a Reverse Proxy
Set up Caddy as a reverse proxy with automatic HTTPS, load balancing, WebSocket passthrough, reusable snippets, and header control — no certbot required.