How to Install FreshRSS on Ubuntu VPS
Install FreshRSS 1.29.1 on Ubuntu 24.04 VPS with Apache, PHP 8.3 and MariaDB. Set cron refresh, virtual host, encoded slashes and mobile app API.
Wetin you dey build
Self-hosted RSS reader na feed reader wey dey run for server wey you own, so nobody fit shut am down or change wetin e dey show you. This guide go install FreshRSS for Ubuntu 24.04 VPS: Apache go dey front, PHP go dey behind am, MariaDB go store the data, and one cron job go fetch new articles. RSS (really simple syndication) na file format wey website publishes so software fit read its articles. FreshRSS na PHP application wey dey collect those files, keep the articles, and give you web interface plus API (application programming interface) wey phone apps dey use communicate.
The installation itself small: unpack one release, create database, write one virtual host, and run one command line installer. Most of the work wey people dey get wrong na the things after installation: the refresh job, encoded slashes for the mobile API, and file ownership.
FreshRSS 1.29.1 na the current release as of July 2026, and e need PHP 8.1 or newer. Ubuntu 24.04 ships PHP 8.3, so the distribution packages enough and you no need third party PHP repository.
Start from LAMP stack wey dey work
FreshRSS na normal PHP application, so e need the same base like any other one. If you never build that base, follow LAMP stack setup for Ubuntu 24.04 first, then come back here. The short version na:
sudo apt update
sudo apt install -y apache2 mariadb-server php libapache2-mod-php
sudo systemctl enable --now apache2 mariadbsystemctl status apache2 suppose report active (running). If Apache no go start, the usual reason na say another process don already hold port 80, and sudo ss -ltnp | grep :80 go show the process name.
PHP extensions wey FreshRSS need
FreshRSS dey treat libxml, cURL, JSON, PDO_MySQL, PCRE and ctype as compulsory. E still need mbstring, iconv, Zlib and ZipArchive, plus GMP for 32 bit system. For Ubuntu, these ones dey come as distribution packages:
sudo apt install -y php-curl php-mbstring php-xml php-zip php-mysql php-intl php-gmp
sudo systemctl restart apache2Use php -m check wetin PHP really load. If extension dey miss, installer no go stop from starting. E go stop for requirements screen and show red line wey name the extension. E confusing to discover the problem for there, so confirm am now. Restarting Apache dey important because libapache2-mod-php dey keep PHP inside Apache process. So, newly installed extension no go visible to web server until Apache restart.
Download di release
Install FreshRSS outside the default web root, and point Apache go am. If you keep the application directory separate from the document root, na only the public folder HTTP fit reach.
cd /tmp
curl -fsSLO https://github.com/FreshRSS/FreshRSS/archive/refs/tags/1.29.1.tar.gz
tar xzf 1.29.1.tar.gz
sudo mv FreshRSS-1.29.1 /srv/freshrssNow make we set the permissions. FreshRSS documentation strict for this: web server user own the whole tree, group fit read every file, and group fit write to ./data/.
sudo chown -R www-data:www-data /srv/freshrss
sudo chmod -R g+r /srv/freshrss
sudo chmod -R g+w /srv/freshrss/dataIf you skip this, installer go fail when e dey write configuration, because PHP dey run as www-data and www-data no fit write inside directory wey root own.
Create database
FreshRSS dey support SQLite, MariaDB, MySQL and PostgreSQL. SQLite no need setup, and e dey okay for one person wey get few hundred feeds. MariaDB na better choice once several people dey share the instance, because concurrent writes from the refresh job and the web interface no longer dey contend for one file lock.
sudo mariadb -e "CREATE DATABASE freshrss CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;"
sudo mariadb -e "CREATE USER 'freshrss'@'localhost' IDENTIFIED BY 'ReplaceThisPassword';"
sudo mariadb -e "GRANT ALL PRIVILEGES ON freshrss.* TO 'freshrss'@'localhost';"
sudo mariadb -e "FLUSH PRIVILEGES;"Use utf8mb4, no be utf8. Feeds fit carry emoji and non-Latin scripts, and the old three byte utf8 encoding dey truncate article title for the first four byte character.
The Apache virtual host
The public directory na p/, e no be top of the tree. Everything else, including the configuration file wey hold your database password, dey above the document root where Apache no go ever serve am.
<VirtualHost *:80>
ServerName rss.example.com
DocumentRoot /srv/freshrss/p/
<Directory /srv/freshrss/p>
AllowOverride AuthConfig FileInfo Indexes Limit
Require all granted
</Directory>
ErrorLog ${APACHE_LOG_DIR}/freshrss_error.log
CustomLog ${APACHE_LOG_DIR}/freshrss_access.log combined
AllowEncodedSlashes On
</VirtualHost>Save am as /etc/apache2/sites-available/freshrss.conf and enable am:
sudo a2enmod rewrite
sudo a2ensite freshrss
sudo a2dissite 000-default
sudo apache2ctl configtest
sudo systemctl reload apache2configtest suppose print Syntax OK. AllowEncodedSlashes On look like say e optional, but e no be: Google Reader API dey send feed identifiers wey contain %2F, and without this directive Apache go reject dem, so mobile apps no go sync while the web interface dey work perfectly.
Add HTTPS before you log in
You wan type password for this site, so get certificate first. Point an A record to the server, then follow Certbot setup for Apache on Ubuntu and run sudo certbot --apache -d rss.example.com. Certbot go rewrite the virtual host for port 443 and add the redirect. Confirm am with curl -I https://rss.example.com/; e suppose return 200 or redirect go the login page.
Run installer from command line
FreshRSS get browser installer, but command line version fit repeat am, and e dey keep record of exactly wetin you choose.
sudo -u www-data php /srv/freshrss/cli/do-install.php \
--default-user admin --auth-type form --environment production \
--base-url https://rss.example.com --language en --api-enabled \
--db-type mysql --db-host localhost --db-user freshrss \
--db-password 'ReplaceThisPassword' --db-base freshrss
sudo -u www-data php /srv/freshrss/cli/create-user.php \
--user admin --password 'a-long-passphrase' --api-password 'a-different-passphrase'Run both commands as www-data. If you run dem as root, e go create configuration files wey root own, and web interface go later fail to save any setting. --environment production matter too, because development setting dey print PHP notices inside the page.
Load https://rss.example.com/ and log in as admin.
Why feeds no dey refresh by themselves
Nothing go poll your feeds until you tell am to. FreshRSS dey refresh while browser dey open for am, so an instance wey you visit two times for one day fit show you articles wey don stale for twelve hours. The fix na the script wey the project releases for this, app/actualize_script.php, make cron run am.
sudo crontab -u www-data -eAdd one line:
*/20 * * * * php /srv/freshrss/app/actualize_script.php > /tmp/FreshRSS.log 2>&1Twenty minutes na the sensible minimum, because the script no go refresh any single feed more often than once every twenty minutes. Any tighter schedule go only waste CPU. Run am by hand one time first:
sudo -u www-data php /srv/freshrss/app/actualize_script.phpHealthy output go name each feed wey e fetch, and e go end without PHP error. If e no print anything at all, the cron user no correct. Permission error for data/ mean say dem skip chmod -R g+w step.
Add your first feeds
Use di plus button for top-left side of the interface, paste site address, and FreshRSS go discover the feed link for you. Plenty sites still publish one even when dem no advertise am, usually for /feed, /rss or /atom.xml. Categories na folders, and to move feed between dem na drag.
If you dey come from another reader, export OPML file there and import am under the subscription management page. OPML (outline processor markup language) na the standard feed list format, and every reader wey worth leaving supports am. Big import dey slow for the first refresh because every feed go fetch once, so give the first cron run time before you judge the speed.
Read am for your phone
FreshRSS dey speak Google Reader API, wey almost every RSS app dey support. Two things must dey correct. For the authentication settings, "Allow API access" must dey on; the --api-enabled flag wey dey above don already set am. For your profile, the API password field must get value. E dey separate from your login password on purpose because phone dey easier to lose.
Go https://rss.example.com/api/ and choose "Check full server configuration". Correct setup go return PASS. If e fail here, na almost always because AllowEncodedSlashes On line dey miss. For the app, enter server address as https://rss.example.com/api/greader.php, username as your FreshRSS user, and API password as the password.
The Docker alternative
If you no wan maintain PHP and Apache by hand, the project dey publish official freshrss/freshrss image, and one compose file go give you the application and database together. The trade-off na the usual one: fewer things to manage for the host, but one extra layer to debug when something spoil, and you still need reverse proxy for TLS (transport layer security). If this one suit you better, the Docker Compose basics for a VPS explain the file format, and the cron line go become docker exec --user www-data freshrss php ./app/actualize_script.php.
Backups and upgrades
Things wey dey hold your state na the database and /srv/freshrss/data/. Use sudo mysqldump freshrss > freshrss.sql dump the first one, copy the second one, and keep both for another place wey no be this server. Your subscription list dey worth occasional OPML export too, because that file fit rebuild your reading setup for any RSS software.
To upgrade, unpack newer release for the same directory, then run the ownership commands again. FreshRSS go apply im own database migrations when the next page load happen. Back up first, because if migration fail for database wey you no fit restore, you no go fit recover am. Reader na service wey get low risk to run, and e good as first candidate if you dey work through list of wetin worth self-hosting.
FAQ
Why my feeds dey only update when I open FreshRSS?
Na because no scheduler dey until you create one. FreshRSS dey refresh feeds while browser session dey open, and e no dey do anything when you close the tab. Add the cron line wey dey call app/actualize_script.php as user www-data, then run the script by hand once and read wetin e output. If e quiet, e usually mean say cron dey run am as the wrong user, so PHP no fit write to data/.
Why my mobile app no fit connect, but the website dey work?
The Google Reader API dey put encoded slashes (%2F) inside request paths, and Apache dey reject dem by default. Add AllowEncodedSlashes On inside the virtual host and reload Apache. Confirm the fix by opening https://rss.example.com/api/ and running "Check full server configuration", wey suppose report PASS. Also check say API password dey set for your profile, because e different from your login password.
I suppose use SQLite or MariaDB?
Use SQLite for one user, because you no need install anything and you no get password to manage. Use MariaDB when more than one person dey read for the instance, or when you pass a few hundred feeds, because refresh job and web interface dey write at the same time and one file lock become the limit. You fit move between dem later through the export and import commands, so this no be permanent decision.
The installer dey fail when e dey write configuration. Wetin be the problem?
PHP dey run as www-data under Apache, and that user no fit write inside directory wey root own. Run sudo chown -R www-data:www-data /srv/freshrss and sudo chmod -R g+w /srv/freshrss/data again, then start the installer again. If you don already run the command line installer as root, delete the files wey e create under data/ before you try again, because na their ownership really cause the problem.
How much server self-hosted RSS reader need?
E need very little. A few hundred feeds for small plan dey okay, because the load na short bursts of HTTP fetching every twenty minutes and the database remain small once old articles dey purged. Disk size dey grow based on retention, so set article purge policy for the archiving settings instead of keeping everything forever.