English / 中文

RSSHub-Gateway

GoReleaser Go Version Release License

One gateway, many feeds. RSSHub-Gateway is a production-ready reverse proxy for RSSHub and Upvote RSS that keeps feed URLs stable while scaling horizontally.

Why teams use it: - Keep a single stable feed URL across multiple upstreams - Route by prefix with per-group load balancing and health checks - Enforce gateway auth while staying RSSHub-compatible - Observe everything with metrics, pprof, and structured logs - Reload safely and cache responses in memory or Redis

中文说明

Highlights

Tip: in a deployed instance, open /wiki to read the full project guide.

Core Concepts

Typical Use Cases

Architecture

Request flow stays simple: authenticate, route by prefix, pick upstream, proxy.

flowchart LR
    Client -->|HTTP| Gateway
    Gateway -->|/short/*| Short
    Short -->|301/302| Redirect
    Redirect --> Target
    Short -->|proxy internal| Router
    Short -->|proxy external| External
    Gateway --> Router
    Router --> Group
    Group --> LB
    LB --> RSSHub
    LB --> Upvote
    Gateway -->|/metrics| Prometheus
    Gateway -->|logs| Logger
sequenceDiagram
    participant C as Client
    participant G as Gateway
    participant R as Router
    participant LB as Load Balancer
    participant U as Upstream
    participant E as External

    C->>G: GET /short/fearnation?key=...
    alt short redirect (external)
        G->>G: Resolve short + strip key/code
        G-->>C: 301 Location: https://fearnation.club/rss/?...
    else short proxy (external)
        G->>G: Resolve short + strip key/code
        G->>E: Proxy request (UA + SNI)
        E-->>G: Response
        G-->>C: Response
    else normal proxy
        C->>G: GET /rsshub/path?key=...
        G->>G: Validate key/code
        G->>R: Select group by prefix
        R-->>G: Group name
        G->>LB: Pick upstream
        LB-->>G: Upstream
        G->>G: Remove key/code, inject upstream code (rsshub)
        G->>U: Proxy request
        U-->>G: Response
        G-->>C: Response
    end

Try It in 60 Seconds

Build locally and run with the sample config:

# build
make build

# run
./rsshub-gateway serve -c config.example.yaml

Docker

docker build -t rsshub-gateway:latest .
docker run --rm -p 8080:8080 rsshub-gateway:latest

Prebuilt images: - docker pull nerdneils/rsshub-gateway:latest - docker pull ghcr.io/nerdneilsfield/rsshub-gateway:latest

To use a custom config:

docker run --rm -p 8080:8080 \
  -v "$(pwd)/config.example.yaml:/app/config.yaml:ro" \
  rsshub-gateway:latest \
  /app/rsshub-gateway serve -c /app/config.yaml

Deployment Example (Docker Compose)

services:
  rsshub-gateway:
    image: ghcr.io/nerdneilsfield/rsshub-gateway:latest
    restart: unless-stopped
    ports:
      - "8080:8080"
    volumes:
      - ./config.yaml:/app/config.yaml:ro
    command: ["/app/rsshub-gateway", "serve", "-c", "/app/config.yaml"]
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8080/"]
      interval: 30s
      timeout: 5s
      retries: 3

Configuration

The full schema lives in config.example.yaml.

How to write config (details) - `routing.default_group` must match a group name. - Prefix rules use `allow`/`deny` with leading `/`; deny overrides allow. - `backend` must be `rsshub` or `upvote` (defaults to `rsshub`). - `strip_prefix` removes service prefixes like `/rsshub` or `/upvote` before proxying. - `gateway_auth` needs `access_key` and at least one of `accept_key`/`accept_code`. - `gateway_auth.bypass_paths` skips gateway auth for exact paths (still proxied/code-injected). - Upstream `access_key` is required for RSSHub code injection and healthcheck `?key=`. - Health check uses `path`, `interval_ms`, `timeout_ms`, `retries`. - `failover.passive_eject` requires `base_eject_ms <= max_eject_ms`. - Metrics require `metrics.accesskey` when enabled. - Pprof requires `pprof.accesskey` when enabled. - Cache requires provider (`memory` or `redis`) with TTL and size limits; caches GET 2xx/3xx only. - Auto reload polling uses config hash compare (`reload.auto.enabled` + `interval_ms`). - Short requires `short.path` (starts with `/`), unique entry names, and method `301`/`302`/`proxy`. External targets strip `key`/`code`.
Full config example ```yaml server: listen: ":8080" timeout_ms: 8000 gateway_auth: enabled: true access_key: "ILoveRSSHub" accept_key: true accept_code: true bypass_paths: - "/favicon.ico" - "/logo.png" - "/robots.txt" - "/manifest.json" metrics: enabled: true path: "/metrics" accesskey: "PROM_KEY_123" pprof: enabled: false path: "/debug/pprof" accesskey: "PPROF_KEY_123" cache: enabled: false provider: "memory" ttl_ms: 3600000 max_item_bytes: 2097152 max_total_bytes: 52428800 redis: addr: "127.0.0.1:6379" password: "" db: 0 dial_timeout_ms: 1000 read_timeout_ms: 1000 write_timeout_ms: 1000 key_prefix: "rsshub_gateway" reload: auto: enabled: false interval_ms: 30000 short: enabled: true path: "/short" entries: - name: "latepost" target: "/rsshub/latepost/4" method: "301" - name: "reddit-top" target: "https://example.com/rss?platform=reddit" method: "302" routing: default_group: "rsshub-public" failover: retry: enabled: true max_retries: 1 passive_eject: enabled: true fail_threshold: 3 base_eject_ms: 10000 max_eject_ms: 60000 groups: - name: "rsshub-public" backend: "rsshub" strip_prefix: "/rsshub" priority: 10 allow: ["/rsshub/"] deny: [] lb: policy: "wrr" fallback_groups: ["rsshub-backup"] health: active: enabled: true path: "/healthz" interval_ms: 30000 timeout_ms: 10000 retries: 3 upstreams: - url: "http://rsshub-1:1200" weight: 3 access_key: "UP1KEY" - url: "http://rsshub-2:1200" weight: 2 access_key: "UP2KEY" - name: "rsshub-backup" backend: "rsshub" strip_prefix: "/rsshub" priority: 1 allow: ["/rsshub/"] deny: [] lb: policy: "hash" upstreams: - url: "http://rsshub-b1:1200" weight: 1 access_key: "B1KEY" - name: "upvote" backend: "upvote" strip_prefix: "/upvote" priority: 5 allow: ["/upvote/"] deny: [] lb: policy: "wrr" upstreams: - url: "http://upvote-rss:80" weight: 1 ```

Authentication

Gateway access supports both key and code styles:

http://127.0.0.1:8080/rsshub/latepost/4?key=ACCESS_KEY
http://127.0.0.1:8080/rsshub/latepost/4?code=md5(path+ACCESS_KEY)
http://127.0.0.1:8080/upvote/?platform=reddit&key=ACCESS_KEY

Migration note (code auth): If you previously used ?code= with /latepost/..., update the path to /rsshub/latepost/... and compute md5("/rsshub/latepost/4"+ACCESS_KEY). Key-based access is unchanged.

Short Subscriptions

Short entries support method: 301|302|proxy. Redirects append the original query string; external targets strip key/code to avoid leaking credentials. Proxy mode forwards the request directly (internal targets keep key/code, external targets strip them).

GET /short/latepost?key=ACCESS_KEY
-> 301 Location: /rsshub/latepost/4?key=ACCESS_KEY

GET /short/reddit-top?sort=top&key=ACCESS_KEY
-> 302 Location: https://example.com/rss?platform=reddit&sort=top

GET /short/cdt?key=ACCESS_KEY
-> proxy https://chinadigitaltimes.net/chinese/feed

Upstream injection rules: - Remove client key and code - Inject code=md5(path+upstream_access_key) for RSSHub only

Routing and Load Balancing

Health Checks

Active health checks call /healthz on each upstream. If the upstream requires an ACCESS_KEY, the gateway automatically appends ?key=<upstream_access_key>.

If you use Docker Compose, update healthcheck like this:

healthcheck:
  test: ["CMD", "curl", "-f", "http://localhost:1200/healthz?key=${ACCESS_KEY}"]

Metrics

Metrics endpoint (requires access key):

GET /metrics?accesskey=<METRICS_ACCESS_KEY>
Metrics list - rsshub_gateway_requests_total{method,group,route_prefix,status} - rsshub_gateway_request_duration_seconds_bucket{group,route_prefix} - rsshub_gateway_upstream_requests_total{group,upstream,status} - rsshub_gateway_upstream_health{group,upstream} - rsshub_gateway_upstream_eject_total{group,upstream} - rsshub_gateway_retry_total{group} - rsshub_gateway_fallback_total{from,to} - rsshub_gateway_config_reload_total{result}

Pprof

Pprof endpoint (requires access key):

GET /debug/pprof/?accesskey=<PPROF_ACCESS_KEY>

Logging

Access logs are JSON per request. Event logs include health changes, ejections, and reload outcomes.

Suggested access log fields - ts - level - req_id - method - path - group - upstream - route_prefix - status - duration_ms - retries - fallback_chain - err_type - err

Reload

Send SIGHUP to reload config without downtime:

kill -HUP <pid>

Development

make test
make cover

Release

goreleaser release --snapshot --clean --skip-publish

License

MIT