Getting FreshRSS + RSSHub running is easy. Keeping it stable is the hard part: lost state after restarts, repeated 403 errors, broken proxy headers, and risky upgrades.
This guide gives a production-ready Linux baseline focused on long-term stability, observability, and safe upgrades.
Architecture and Stability Goals
- FreshRSS: feed aggregation, reading, filtering rules
- RSSHub: dynamic feed generation
- Redis: cache layer for RSSHub to reduce upstream pressure
- Nginx/Caddy: reverse proxy + HTTPS
Target outcomes:
- Services auto-recover after crashes
- Persistent data volumes
- Key settings managed through env vars
- Health checks and clear log paths
1) Directory Layout and Prerequisites
sudo mkdir -p /opt/rss-stack/{freshrss-data,rsshub-cache,logs}
cd /opt/rss-stack
docker --version
docker compose version
2) docker-compose.yml (Production Baseline)
services:
freshrss:
image: freshrss/freshrss:latest
container_name: freshrss
restart: unless-stopped
environment:
TZ: Asia/Shanghai
CRON_MIN: '3,33'
TRUSTED_PROXY: 172.16.0.0/12 192.168.0.0/16
volumes:
- ./freshrss-data:/var/www/FreshRSS/data
- ./freshrss-data/extensions:/var/www/FreshRSS/extensions
healthcheck:
test: ["CMD", "wget", "-qO-", "http://127.0.0.1:80/"]
interval: 30s
timeout: 5s
retries: 5
networks:
- rss_net
redis:
image: redis:7-alpine
container_name: rsshub-redis
restart: unless-stopped
command: ["redis-server", "--appendonly", "yes"]
volumes:
- ./rsshub-cache:/data
networks:
- rss_net
rsshub:
image: diygod/rsshub:chromium-bundled
container_name: rsshub
restart: unless-stopped
depends_on:
- redis
environment:
NODE_ENV: production
CACHE_TYPE: redis
REDIS_URL: redis://redis:6379/
PUPPETEER_WS_ENDPOINT: ''
REQUEST_RETRY: 2
REQUEST_TIMEOUT: 10000
healthcheck:
test: ["CMD", "wget", "-qO-", "http://127.0.0.1:1200/healthz"]
interval: 30s
timeout: 5s
retries: 5
networks:
- rss_net
networks:
rss_net:
driver: bridge
Start services:
docker compose up -d
docker compose ps
3) Reverse Proxy + HTTPS (Nginx Example)
server {
listen 443 ssl http2;
server_name rss.example.com;
location / {
proxy_pass http://127.0.0.1:1200;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto https;
}
}
server {
listen 443 ssl http2;
server_name reader.example.com;
location / {
proxy_pass http://127.0.0.1:8080;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto https;
}
}
If FreshRSS is not exposed on host port 8080, use the actual mapped port or proxy through an internal Docker network.
4) Common Failure Cases and Fixes
RSSHub keeps returning 403 / timeout
- Lower crawl frequency and prioritize caching (Redis)
- Use proxy/IP rotation for heavily protected sources
- Increase
REQUEST_TIMEOUT(10s → 15s) and inspect logs
docker logs --tail=200 rsshub
FreshRSS scheduled updates do not run
- Verify
CRON_MIN - Verify container timezone
- Manually trigger refresh from admin panel
White screen or extension issues after upgrades
Always back up before pull:
tar czf backup-freshrss-$(date +%F).tgz /opt/rss-stack/freshrss-data
docker compose pull
docker compose up -d
5) Conservative Growth Operations
- Validate new feed routes in small batches for 24 hours
- Weekly maintenance:
docker compose pull && docker compose up -d - Tier RSS routes by value: keep high-value feeds stable, reduce frequency for low-value ones
Summary
The key is not just “it works”, but “it is recoverable under failure”.
With persistent data, Redis caching, health checks, and correct proxy headers, FreshRSS + RSSHub can run reliably on Linux and scale smoothly later (monitoring, migration to k3s, etc.).