A self-hosted RSS aggregator with a beautiful reader UI, full-text search, social sharing, and an admin CMS. Deploy anywhere with PHP, or run locally with Python.
Live examples:
- Seriously Photography — 90 photography & filmmaking feeds
- Seriously VC — 120 venture capital & startup feeds
- Reader — sidebar layout with single-column feed, pagination, dark/light theme toggle
- Search — full-text search across titles, summaries, authors, tags
- Filters — by publication, author, date range, and tags
- Social sharing — X, Facebook, LinkedIn, Instagram (copy link), Email
- RSS output — Atom feed at
/feedfor subscriber syndication - Admin CMS — login-protected site settings, feed management, OPML import
- Auto-refresh — batched incremental refresh with conditional GET (ETag/Last-Modified)
- Image extraction — 9-source chain including og:image fallback and YouTube thumbnails
- Audio/video — inline players for podcast and video enclosures
- PostgreSQL — optional, with full-text search vectors and automatic triggers
- Legacy URL support — 301 redirects for WordPress
/category/,/author/,/YYYY/MM/DD/patterns
git clone https://github.com/kteare/seriously.git mysite
cd mysite
./setup.shThe setup wizard will ask for your site name, admin password, and optional PostgreSQL credentials.
pip3 install feedparser
python3 seriously.pyOpens http://localhost:8420. Go to the admin (click + icon in sidebar), add some feeds, and hit Refresh.
Upload these files to your web root (public_html/):
| File | Purpose |
|---|---|
aggregated_feed.html |
Reader page (served as index) |
admin.html |
Admin CMS |
api.php |
PHP API backend |
cron_refresh.php |
Cron job script |
.htaccess |
URL routing + security |
.env |
Credentials (blocked from web) |
site_config.json |
Site settings |
team_feeds.json |
Feed list |
Set file permissions for PHP to write:
chmod 666 site_config.json team_feeds.json feed_data.jsonsudo apt update
sudo apt install -y php php-pgsql php-xml php-mbstring php-curl \
postgresql python3 python3-pip nginx
pip3 install feedparser
git clone https://github.com/kteare/seriously.git /var/www/mysite
cd /var/www/mysite
./setup.sh
# Copy and edit the Nginx config
sudo cp nginx.conf.example /etc/nginx/sites-available/mysite
sudo ln -s /etc/nginx/sites-available/mysite /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginx
# SSL (optional)
sudo apt install certbot python3-certbot-nginx
sudo certbot --nginx -d yourdomain.com
# Permissions
sudo chown -R www-data:www-data /var/www/mysite
sudo chmod 666 site_config.json team_feeds.json feed_data.json
# Cron
echo "*/30 * * * * /usr/bin/php /var/www/mysite/cron_refresh.php" | sudo crontab -Generate a deploy script for your server:
./make-deploy.sh
./deploy.sh --allSet up a cron to refresh feeds every 30-60 minutes:
/usr/local/bin/php /path/to/public_html/cron_refresh.php
Uses conditional GET to skip unchanged feeds — most runs take seconds.
Without PostgreSQL, data is stored in JSON files. With PostgreSQL:
- Full-text search with weighted ranking (title > summary/tags > author)
- Atomic writes — no data corruption from timeouts
- Concurrent read/write safety
- Automatic search vector triggers
Setup:
# During ./setup.sh, answer "y" to PostgreSQL
# Or manually:
php db_setup.phpAll settings are editable in /admin:
| Setting | Description |
|---|---|
| Title / Accent | Site name (e.g. "Seriously" + "Photography") |
| Tagline | Subtitle shown in sidebar |
| GA4 Measurement ID | Google Analytics tracking |
| Auto-refresh interval | Minutes between cron refreshes |
| RSS Feed URL | Path to Atom feed (shows subscribe icon) |
Reader (aggregated_feed.html)
├── Sidebar: logo, search, filters, tags, share
└── Main: single-column article cards
Admin (admin.html)
├── Site Settings (title, tagline, GA, refresh interval)
└── Feed Manager (add, remove, OPML import, refresh)
API (api.php)
├── /api.php?action=data → article feed (JSON)
├── /api.php?action=config → site config
├── /api.php?action=feeds → feed list
├── /api.php?action=add-feed → add feed (admin)
├── /api.php?action=remove-feed → remove feed (admin)
├── /api.php?action=refresh-batch → incremental refresh
├── /api.php?action=auth-login → admin login
└── /feed → Atom feed output
Storage
├── PostgreSQL (if configured)
└── JSON files (fallback)
MIT