Ever since the demise of Google Reader I have been looking for a suitable replacement RSS reader. In the past I used to use Liferea but that was when I used a single desktop machine; these days I want to be able to read on my phone and multiple machines. I moved to Feedly and it’s been mostly ok, but I’m hitting the limit of feeds available in the free tier, and $72/year is a bit more than I can justify to myself. Especially when I have machines already available to me where I could self host something.

The problem, of course, is what to host. It seems the best options are all written in PHP, so I had to get over my adverse knee-jerk reaction to that. I ended up on FreshRSS but if it hadn’t worked out I’d have tried TinyTinyRSS. Of course I’m hosting on Debian, and the machine I chose to use was already running nginx and PostgreSQL. So I needed to install PHP:

$ sudo apt install php7.4-fpm php-curl php-gmp php-intl php-mbstring \
	php-pgsql php-xml php-zip

I put my FreshRSS install in /srv/freshrss so I grabbed the 1.20.2 release from GitHub (actually 1.20.1 at the time, but I’ve upgraded to the latest since) and untared it in there. I gave www-data access to the data directory (sudo chown -R www-data /srv/freshrss/data) (yes, yes, I could have created a new user specifically for FreshRSS, but I’ve chosen not to for now). There’s no actual need to configure things up on the filesystem, you can do the initial setup from the web interface. Which is where the trouble came. I’ve been an Apache user since 1998 and as a result it’s what I know and what I go to. nginx is new to me. And I wanted my FreshRSS instance to live in a subdirectory of an existing TLS-enabled host, rather than have it’s own hostname. Now, at least FreshRSS copes with this (unlike far too many other projects), you just have to configure your webserver correctly. Which took me more experimentation than I’d like, but I’ve ended up with the following snippet:

    # PHP files handling
    location ~ ^/freshrss/.+?\.php(/.*)?$ {
        root /srv/freshrss/p;
        fastcgi_pass unix:/run/php/php-fpm.sock;
        fastcgi_split_path_info ^/freshrss(/.+\.php)(/.*)?$;
        set $path_info $fastcgi_path_info;
        fastcgi_param PATH_INFO $path_info;
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;

    location ~ ^/freshrss(/.*)?$ {
        root /srv/freshrss/p;
        try_files $1 /freshrss$1/index.php$is_args$args;

Other than the addition of the freshrss prefix this ends up differing slightly from the FreshRSS webserver configuration example. I ended up having to make the path info on the fastcgi_split_path_info optional, and my try_files in the bare directory location directive needed $is_args$args added or I just ended up in a redirect loop because the session parameters didn’t get passed through. I’m sure there’s a better way to do it, but I did a bunch of searching and this is how I ended up making it work.

Before firing up the web configuration I created a suitable database:

$ sudo -Hu postgres psql
psql (13.8 (Debian 13.8-0+deb11u1))
Type "help" for help.

postgres=# create database freshrss;
postgres=# create user freshrss with encrypted password 'hunter2';
postgres=# grant all privileges on database freshrss to freshrss;
postgres=# \q

I ran through the local configuration, creating myself a user and adding some feeds, then created a cronjob to fetch updates hourly and keep a log:

# mkdir /var/log/freshrss
# chown :www-data /var/log/freshrss
# chmod 775 /var/log/freshrss
# cat > /etc/cron.d/freshrss-refresh <EOF
33 * * * * www-data /srv/freshrss/app/actualize_script.php > /var/log/freshrss/update-$(date --iso-8601=minutes).log 2>&1

Experiences so far? Reasonably happy. The interface seems snappy enough, and works well both on mobile and desktop. I’m only running a single user instance at present, but am considering opening it up to some other folk and will see how that scales. And it clearly indicated a number of my feeds that were broken, so I’ve cleaned some up that are still around and deleted the missing ones. Now I just need to figure out what else I should be subscribed to that I’ve been putting off due to the Feedly limit!