Posted by: Matt Lewis

Published:

Illustration of FreshRSS running on a cPanel hosting account with cron jobs and backups.

FreshRSS Tips: Running It Well on Shared Hosting

The first two parts of this series were about getting more out of FreshRSS. This one is about keeping it healthy.

Most of this is a one-time setup. Done well, FreshRSS can run for years without filling your disk, polling publishers too aggressively, or losing your favorites when something goes wrong.

Who This Is For
Anyone running FreshRSS on a cPanel account. Most of it applies anywhere, but the examples assume cPanel’s Cron Jobs and File Manager. If you haven’t installed it yet, start with the Softaculous Spotlight on FreshRSS.

The Short Version

  • Check the refresh cron job and its schedule; check your version before scheduling a purge
  • Set longer refresh intervals for quiet or scraped feeds
  • Back up data/ and the database; OPML alone is not enough
  • FreshRSS can sync reading apps through its Google Reader API

1. Check the Refresh Cron Job

FreshRSS is refreshed by a single script, app/actualize_script.php. It walks your feeds, fetches the ones that are due, and exits.

If you installed FreshRSS through Softaculous on Hawk Host, the installer created a cron job using the schedule in its CRON Job fields. Check cPanel → Cron Jobs to confirm the job is there and see when it runs. For example, 27 0 * * * runs once a day at 00:27 server time. If you want more frequent updates, edit that existing job’s schedule. On another host or with a manual install, check whether a refresh job exists before adding one.

Here is one Hawk Host example from a PHP 8.1 install where rss.yourdomain.com points to /home/USERNAME/rss on the server. USERNAME stands in for the cPanel account name:

The feed refresh command

Cron
cPanel → Cron Jobs
/opt/alt/php81/usr/bin/php /home/USERNAME/rss/app/actualize_script.php > /home/USERNAME/rss/data/users/FreshRSS_cron.log 2>&1
Use the PHP binary and install path from the job created on your account. The output goes to a log you can check if refreshes stop.

Your PHP version, install path, and log path may differ, especially on another host. Keep the generated command if it is working; edit its schedule if you want more frequent checks. For four runs an hour, set Minute to 7,22,37,52 and the other schedule fields to *. If no job exists, create one using the PHP binary and FreshRSS path for your account. Do not add a second refresh job.

Four times an hour is plenty. FreshRSS respects each feed’s refresh interval. A new user’s default is typically one hour; check Configuration → Archiving on your installation. FreshRSS’s refresh guide explains the limits for global and individual feeds.

Purge old articles

Purging does not run on every refresh. FreshRSS triggers it on a random subset of refreshes to limit database work. Version 1.29.0 added cli/purge.php, which lets you run it on a predictable schedule. The Softaculous listing showed 1.29.1 when I checked in September 2026, but confirm the version on your own installation before adding the job. On an older version, use Configuration → Archiving → Purge now when you need an immediate purge.

If your installation has cli/purge.php, run it on a schedule:

A nightly purge

Cron
cPanel → Cron Jobs
23 4 * * * /opt/alt/php81/usr/bin/php /home/USERNAME/rss/cli/purge.php --user FRESHRSS_USER >/dev/null 2>&1
Applies the most specific archiving policy to every feed, checking the feed first, then its category, then the global default. Once a day at an off-peak hour is fine. Replace USERNAME in both places: the path uses your cPanel username, while the --user flag uses your FreshRSS username.
Getting the PHP Path Right
This Hawk Host example uses PHP 8.1. If your account uses another version, the binary path will differ. For extra jobs you add, copy the PHP binary from your working FreshRSS refresh job instead of guessing.

Debugging a cron job that is not working

The Hawk Host command above already writes output to data/users/FreshRSS_cron.log. If feeds stop updating, wait for the next run, then open that file in cPanel’s File Manager. Look for errors about the install path, PHP binary, or missing extensions.

If your job sends output to /dev/null, redirect it to a writable log while you debug. Check the log’s size occasionally if you leave logging on.


2. Set Refresh Intervals by Feed

By default, every feed inherits one global interval. As your subscriptions grow, it helps to check quiet feeds less often.

Each feed’s Archiving section has Do not automatically refresh more often than, which overrides the global setting for that feed alone.

Here are starting points, not targets to hit exactly:

The shortest per-feed interval in FreshRSS is 15 minutes. The global default cannot be set below 20 minutes. See the refresh documentation.

There is also a mute option on individual feeds, which disables automatic refresh entirely while keeping the subscription and its history. Better than deleting a feed you might come back to.

Let an Extension Work It Out
The AutoTTL extension sets each feed’s refresh interval automatically, based on how often that feed has actually been publishing. If you have hundreds of feeds and no appetite for tuning them by hand, this is the lazy answer and it is a good one.

3. Let Publishers Push Instead

FreshRSS normally checks each feed on a schedule. Most checks come back empty; that’s just how polling works.

When a publisher supports WebSub, its hub gives FreshRSS a heads-up when a new article goes live. It can appear in your reader without waiting for the next scheduled check. FreshRSS supports this natively, and you will find it on some WordPress sites, Blogger, Medium, Friendica, and others.

The requirement is that your FreshRSS instance must be reachable from the public internet, because the publisher’s hub has to be able to make an HTTP request to it.

With a public domain and HTTPS already set up, a shared hosting installation can receive those notifications without extra network configuration. See the FreshRSS WebSub guide.

Enabling WebSub

Two settings and a check.

1
Confirm your base URL is correct
FreshRSS needs to know its own public address so it can tell hubs where to send notifications. This lives in data/config.php as base_url, and it should be the full external URL, for example https://rss.yourdomain.com/.
2
Turn WebSub on
In the same file, set:
'pubsubhubbub_enabled' => true,
Edit it through cPanel's File Manager. Take a copy of the file first.
3
Confirm the API folder is reachable
Hub callbacks arrive at p/api/, so that path has to be publicly accessible. If you followed the hardening step in the spotlight post, check that your .htaccess has not blocked it. The version FreshRSS ships specifically permits this path.
4
Check whether a feed actually supports it
Most do not, and there is no point wondering. Paste the feed URL into test.livewire.io and it will tell you whether the feed declares a hub.

If something is not working, FreshRSS writes WebSub activity to data/users/_/log_pshb.txt, which is the first place to look.

This Supplements Polling, It Does Not Replace It
Keep your cron job. WebSub covers the publishers that support it; everything else still needs to be polled, and subscriptions occasionally lapse and need renewing. Think of it as a fast path for a subset of your feeds, not a replacement for the refresh cycle.

4. Use Visibility Levels Instead of Reading Everything

Five priority levels for deciding what deserves attention.

Every feed has a Visibility setting, and it is one of the most under-used features in FreshRSS. The options:

That last option is easy to misunderstand. “Do not show” is not the same as unsubscribing. FreshRSS keeps fetching and storing the feed, and it remains searchable without appearing in a reading view.

That makes it perfect for feeds you want as an archive rather than a stream: a competitor’s blog, a regulator’s announcements page, a vendor changelog. You never read them casually, but when you need to know whether something was announced in March, it is there.

A search-only archive layer

Setting
Feed settings → Visibility
c:9 intitle:/\b(pricing|deprecat|end.of.life)\b/i date:P1Y
Set noisy reference feeds to ‘Do not show’, then open them through a saved query when needed. They stay out of the unread count while the archive keeps growing.

The mirror image is Show in important feeds, for the handful of sources you do not want to miss. FreshRSS excludes these feeds from its mark-as-read-while-scrolling behavior, so you cannot clear one by accident.


5. Set Retention by Category and Feed

Archiving policy cascades. A feed’s setting beats its category’s setting, which beats the global default. Anything left on “By default” falls through to the next level up.

That means you can be aggressive globally and make exceptions where they matter, rather than picking one compromise number for everything.

Global default keep 3 months ├─ News keep 2 weeks (high volume, low half-life) ├─ Deals keep 1 week (expires by definition) ├─ Blogs by default (inherits the 3 months) ├─ Research keep 5 years (this is the archive) └─ Reference keep 5 years └─ Vendor docs keep indefinitely (feed-level override inside the category)

The settings available at each level:

Two Things That Might Surprise You
Purging uses discovery date, not publication date. An article published in 2019 that your instance found last week counts as a week old.An article still present in the upstream feed is never purged, whatever your policy says. FreshRSS needs it in order to keep recognizing it as already-seen. So a feed that keeps fifty items in its XML will always keep at least fifty items in your database.

Keeping the database tidy

Deleting rows does not necessarily shrink the file. Configuration → Archiving has an Optimize database button that reclaims the space, and there is a CLI equivalent if you would rather schedule it:

41 5 1 * * /opt/alt/php81/usr/bin/php /home/USERNAME/rss/cli/db-optimize.php --user FRESHRSS_USER >/dev/null 2>&1

Monthly is plenty. There is no benefit to running it more often, and it is not a free operation.


6. Back Up More Than Your Subscription List

An OPML file preserves the subscription list. FreshRSS’s backup guide says a regular OPML export omits FreshRSS-specific settings, including XPath scraper rules and refresh intervals.

FreshRSS’s Import/export page lets you select what goes into an export:

These exports are useful for moving selected content. Keep a database backup for a complete restore.

What actually needs backing up

The upstream backup documentation is refreshingly specific about this, and it is shorter than you would expect:

WhatNeeded?Notes
data/RequiredConfiguration, user settings, tokens. You can skip data/cache/
The MySQL databaseRequiredYour articles live here, not in data/
extensions/RecommendedOnly if you use third-party extensions
i/themes/OptionalOnly if you added custom themes
Everything elseNoIt comes back with a reinstall or upgrade

Check that your hosting backup covers both data/ and the database, and that you know how to restore each. A portable SQLite export is another way to keep a copy of a user’s database.

Exporting a portable backup

For a one-time export, use FreshRSS’s CLI from the installation directory. The OPML file is handy for another reader; the SQLite file preserves the user’s FreshRSS data and settings:

php cli/export-opml-for-user.php --user FRESHRSS_USER > /home/USERNAME/backups/freshrss-feeds.opml.xml
php cli/export-sqlite-for-user.php --user FRESHRSS_USER --filename /home/USERNAME/backups/freshrss-user.sqlite

Create the backups directory first and write exports outside public_html. Choose a new SQLite filename for each export. For a whole-instance database backup, cli/db-backup.php writes a portable SQLite copy for each user into data/users/*/backup.sqlite. FreshRSS 1.29.1 added retained automatic SQLite exports using auto_sqlite_export in data/config.php and a scheduled cli/export-sqlite-auto.php run. Check the version installed on your account before using that option.

Test the Restore, Not the Backup
A backup you have never restored is a hypothesis. Once a year, restore a portable SQLite export into a disposable FreshRSS install and confirm that feeds, filters, scraper settings, labels and article state return. Import the OPML separately as a portability check, not as proof of a full recovery.

7. Connect a Reading App

The web interface does not have to be where you read.

FreshRSS implements the Google Reader API. That means it can do the fetching, filtering, deduplicating and state-syncing while a completely different application handles the reading. Your read/unread state, favorites and labels sync across every client.

FreshRSS also implements the older Fever API. The project recommends the Google Reader one, which supports more and is more efficient. Use Fever only if your client of choice supports nothing else.

Connecting a Reading App

1
Enable the API
Configuration → Authentication → **Allow API access (required for mobile apps)**.
2
Set an API password
Go to Configuration → Profile → **API password**. Every user needs a separate API password because mobile clients store it on their devices.
3
Run the server check
Click the API address link next to the password field, then **Check full server configuration**. You want **PASS**. If the check fails, follow the page's explanation before trying the mobile app. One possible cause is a server that does not accept encoded slashes.
4
Point a client at it
Your API address looks like https://rss.yourdomain.com/api/greader.php. Type that, your FreshRSS username, and the API password into the client. Choose "FreshRSS" or "Google Reader API" as the account type.

FreshRSS keeps a list of compatible Google Reader clients, including Read You and Readrops on Android, Reeder Classic on Apple devices, and NewsFlash on Linux. Check the list when you pick an app; client support changes.


8. Extensions Without Shell Access

It is a file copy. That is the whole procedure.

FreshRSS extensions are not installed from a store. You put a folder on the server. That sounds worse than it is, and cPanel’s File Manager handles it entirely.

Installing an Extension

No SSH required.

1
Download the extension
Configuration → Extensions lists the known ones with links to their repositories. Download the extension as a ZIP from its repository.
2
Upload and unzip
In cPanel's File Manager, go to your FreshRSS extensions/ folder, upload the ZIP, and extract it there.
3
Check the folder shape
You should end up with a folder whose contents include extension.php and metadata.json directly inside it. The most common mistake is a doubled folder such as extensions/Thing-main/Thing/. GitHub ZIPs add a wrapper directory, so move the inner folder up a level.
Leave These Two Files Alone
Do not delete or overwrite extensions/.gitignore or extensions/README.md. They are supposed to be there.
4
Enable it
Back in Configuration → Extensions, it should now be listed. Enable it, then configure it with the gear icon.

Extensions come in two flavors. User extensions are enabled and configured per user. System extensions can only be managed by an administrator and apply to everyone on the instance.

A handful worth looking at:

Extensions Are Third-Party Code
Most are small projects maintained by one person, and they run inside your FreshRSS install. If you are unsure about one, read its source before installing it; many are only a few hundred lines. Keep the number you install manageable.

9. When the Instance Gets Big

A few settings that matter at scale and are irrelevant below it.

Past a few hundred feeds, some of the interface defaults start to cost you.

Favicons. FreshRSS displays a favicon per feed. If the sidebar feels slow with hundreds of feeds, try turning favicons off or using emoji instead.

Sidebar simplification. A threshold setting removes per-feed dropdown menus and favicons once the sidebar exceeds a certain number of feeds. Try lowering it if the sidebar feels sluggish.

Articles per page. The default is comfortable. If you are on a slow connection or a phone, lowering it does more for perceived speed than anything else on this list.

Lazy loading images. On by default and worth leaving on, particularly if you use full-text extraction.

Hide categories with no unread articles. With two hundred feeds the sidebar is mostly zeroes. This collapses it to just the things that need attention.

Global View Is the One to Learn
FreshRSS has three views: normal, global and reading. Global view shows every category and feed with its unread count in a grid, and it will show you how many articles per feed match a search expression. It is the fastest way to spot the feed that has started publishing forty items a day, and it is the tool for auditing a filter rule that is too broad. Press 2 to switch to it.

Adding a few more readers

An administrator can create separate accounts under Administration → Manage Users. Each person gets their own subscriptions and reading state. The same app/actualize_script.php cron job refreshes the instance; you do not need one job per person. More users and subscriptions still mean more feed requests and stored articles.

If everyone should start with the same subscriptions, FreshRSS reads data/opml.xml as the default feed list for new users. It will not change existing accounts. The installation guide documents this file, and the CLI reference lists create-user.php for scripted account creation.

Leave Allow anonymous reading of the default user’s articles off unless you intend to publish that account’s feed list and articles to visitors. The default-user documentation explains what that switch exposes.


10. Updating

A Softaculous-installed FreshRSS has two update paths. Pick one and stay with it rather than alternating between them.

Softaculous tracks the install and can update it for you, including automatic updates if you enable them. If you installed with Softaculous, this is the simplest path.

FreshRSS’s own updater can update from within the application. It may leave Softaculous’s installation record out of sync with the files on disk.

Check which updater manages your install before changing versions.

Before Any Update
Take a backup first, and make sure it includes both data/ and the database. FreshRSS updates are generally uneventful, but ‘generally uneventful’ is not a restore plan.

11. A Short Troubleshooting Checklist

FreshRSS also has a logging area, and per-feed error state is visible in Subscription management. Filter the list to show only feeds with errors when you need to find a broken one.


Operational Questions

Four times an hour is a good starting point, and there is little benefit in going higher. If the schedule Softaculous created runs once daily, edit that job in cPanel if you want updates more often. FreshRSS respects each feed’s own refresh interval, so extra runs usually find nothing due and exit.

Use odd minute values rather than */15, so you are not hitting other people’s servers at the same instant as everyone else’s scheduler.

Three usual reasons.

Purging only runs on a random subset of refreshes, so it may fire less often than you assume. Use Purge now for an immediate run; on FreshRSS 1.29+, you can also schedule cli/purge.php.

“Never delete unread articles” is on and you have a large unread backlog, which overrides your age policy.

Articles still present in the upstream feed are never purged regardless of policy, because FreshRSS needs them to keep recognizing what it has already seen.

Yes. It is properly multi-user, with separate feeds, categories, filters and API passwords per person. An administrator creates the accounts.

Bear in mind that each user’s feeds are refreshed independently, so five people with two hundred feeds each is five times the fetching work of one.

No. The application itself is light. The load comes from network activity during refreshes, and from database size over time.

The two things that change this materially are full-text extraction, which multiplies your outbound requests, and long retention across many feeds, which grows the database. Both are under your control.

Only if you have no other option. FreshRSS can be refreshed by requesting a URL, which lets an external service trigger it, but it is a workaround for environments without cron.

On cPanel you have real cron, which is more reliable, does not depend on a third party, and does not expose a refresh endpoint to the internet.

It is designed to be, and WebSub requires it. Keep it updated, use a strong password, make sure the certificate is valid, and confirm that only the p/ folder is reachable. The spotlight post covers that last check.

If you want more, FreshRSS supports HTTP authentication and OpenID Connect in addition to its own login form.

FreshRSS will start failing to write, which typically shows up as refreshes appearing to run without new articles appearing.

Prevention is a retention policy plus the scheduled purge, and an occasional database optimization. If you are already there, tighten retention on your highest-volume categories, purge immediately, then optimize to actually reclaim the space.


That's the Series

Three articles is a lot of words about a feed reader, which is either a sign that I have lost perspective or a sign that FreshRSS is a much deeper piece of software than its category suggests. I would argue the second.

The short version of all of it: let FreshRSS do the boring work. Fetch on a schedule, filter hard, keep what matters, throw away the rest, and read it wherever you like. What you get back is a view of the web assembled by rules you wrote, rather than by someone else’s engagement metrics.

That was the original promise of RSS. It still works.


UPSTREAM DOCS
FreshRSS Administrator Documentation
Backups, updates, access control, and the CLI reference, straight from the project.
Read the Docs
Ready to get started? Build your site from
$1.83/mo
GET STARTED NOW