Jesse A. Green / notebook

Live demo: the DocCompiler guide library as a static, read-only snapshot (October 2026). No backend, no accounts, nothing to submit.

RSSHub Admin Guide

Generated June 2026 from DIYgod/RSSHub at 3567ced

AI-generated documentation. The library is checked by sampling (three repositories so far); this guide was not individually reviewed. Verify against the project before relying on it.

This guide is the operational and architectural reference for RSSHub — the open-source, self-hostable feed-generation framework. It is written for the people who run it and the people who extend it: operators deploying and tuning an instance, and developers adding routes or understanding the internals.

RSSHub is a TypeScript application built on the Hono web framework. A single instance loads over 1,500 route namespaces (thousands of individual routes), each of which scrapes one source and returns a normalized feed. The same codebase runs unchanged on Node.js, Cloudflare Workers, Vercel, and Fly.io because Hono is runtime-agnostic.

If you are looking for how to consume feeds (URL structure, output formats, query parameters), see the User Guide. This guide covers everything behind the feed.


1. Executive Summary

Attribute Value
Project RSSHub — "Everything is RSSible"
Author DIYgod and contributors
License AGPL-3.0
Language TypeScript (ESM, "type": "module")
Runtime Node.js ^22.20.0 || ^24; also Cloudflare Workers, Vercel, Bun-class runtimes
Web framework Hono 4.x
Package manager pnpm 10.x
Default port 1200
HTTP surface GET /:namespace/* (feeds), GET /api/* (metadata API), plus /, /healthz, /robots.txt, /metrics
Caching backends in-memory (LRU), Redis, or external HTTP cache
Configuration Environment variables, read in lib/config.ts

What it does. RSSHub receives an HTTP GET for a feed path, dispatches to the matching route handler, which fetches and parses a source into a normalized data structure, then a middleware chain post-processes that data (filtering, full-text, hotlink rewriting) and renders it as RSS, Atom, JSON Feed, or RSS3.

What makes it operationally distinctive. Routes are auto-discovered from the filesystem and compiled into a static registry at build time. A single shared middleware chain implements every universal query parameter once, so all routes behave consistently. Caching is request-coalescing: concurrent requests for the same feed are serialized so the source is hit at most once per cache window.


2. Architecture

2.1 Request lifecycle

            ┌──────────────────────────────────────────────────────────────┐
  HTTP GET  │  Hono app (lib/app-bootstrap.tsx)                             │
 ─────────► │                                                              │
            │  GLOBAL MIDDLEWARE (run in order, each wraps the next):      │
            │    trimTrailingSlash → compress → jsxRenderer                 │
            │    → logger → trace → honeybadger → sentry                    │
            │    → access-control → debug → template → header               │
            │    → anti-hotlink → parameter → cache                         │
            │                          │                                    │
            │                          ▼                                    │
            │  ROUTE REGISTRY (lib/registry.ts)                             │
            │    app.basePath('/'+namespace).get(routePath, handler)        │
            │                          │                                    │
            │                          ▼                                    │
            │  ROUTE HANDLER (lib/routes/<ns>/<route>.ts)                   │
            │    fetch source → parse → return normalized Data              │
            │                          │                                    │
            │      (unwinds back up the middleware chain)                   │
            │                          ▼                                    │
            │  template middleware renders Data → RSS / Atom / JSON / RSS3  │
            └──────────────────────────────────────────────────────────────┘
                                       │
                                       ▼  Response

The chain is declared in lib/app-bootstrap.tsx. Each middleware calls await next() and then runs its post-processing as the stack unwinds — so template, header, anti-hotlink, parameter, and cache all act on the route's output after the handler returns. The route registry (lib/registry.ts) is mounted last with app.route('/', registry); the metadata API is mounted at app.route('/api', api).

2.2 The middleware chain in detail

Order Middleware Source Responsibility
1 trimTrailingSlash hono/trailing-slash Normalize trailing slashes.
2 compress hono/compress gzip the response.
3 jsxRenderer hono/jsx-renderer Stream JSX as XML (the RSS/Atom views are JSX). Sets the XML doctype.
4 logger lib/middleware/logger.ts Per-request logging.
5 trace lib/middleware/trace.ts OpenTelemetry request tracing/metrics.
6 honeybadger lib/middleware/honeybadger.ts Honeybadger error reporting (if configured).
7 sentry lib/middleware/sentry.ts Sentry error reporting (if configured).
8 access-control lib/middleware/access-control.ts Enforce ACCESS_KEY via ?key=/?code=.
9 debug lib/middleware/debug.ts Debug counters and ?debug= gating.
10 template lib/middleware/template.tsx Render Data into the requested output format.
11 header lib/middleware/header.ts Response headers (CORS via ALLOW_ORIGIN, etc.).
12 anti-hotlink lib/middleware/anti-hotlink.ts Rewrite media URLs through a hotlink template.
13 parameter lib/middleware/parameter.ts Apply universal feed parameters (filter, limit, fulltext, brief, opencc, chatgpt, …).
14 cache lib/middleware/cache.ts Serve from / populate the route cache; coalesce concurrent identical requests.

Note

Because middleware post-processing runs as the stack unwinds, the textual order above is also the response-shaping order in reverse: the handler produces Data, then parameter filters it, then anti-hotlink rewrites media, then template serializes it. cache wraps the whole route execution to store the serialized result.

2.3 Route discovery and the registry

Routes live under lib/routes/<namespace>/. Each namespace directory contains:

  • namespace.ts — exports a namespace: Namespace object (display name, source url, description, language, categories).
  • One file per route — exports a route: Route object (path template, parameters, example, handler, radar rules, feature flags).

How they get loaded depends on NODE_ENV (lib/registry.ts):

Mode Source of routes
development (default) directoryImport() scans lib/routes/ at startup and imports every .ts/.tsx.
production Pre-built assets/build/routes.js (generated by pnpm build:routes).
test Pre-built assets/build/routes.json.
Package/library (IS_PACKAGE) assets/build/routes.js.

For each namespace the registry creates a Hono sub-app with app.basePath('/'+namespace) and registers every route's path as a GET handler. Routes are sorted so literal path segments take priority over :parameter segments, ensuring specific paths match before catch-alls. Route handlers are lazy: the handler module is imported on first hit in production via a module() thunk.

Important

Adding or renaming a route requires rebuilding the static registry for production/worker deployments (pnpm build:routes). In development the directory scan picks up changes on restart. A route present on disk but missing from assets/build/routes.js will 404 in production.

2.4 The route contract (Route type)

Defined in lib/types.ts. Every route exports an object with these fields:

Field Required Purpose
path ✅ Path template, e.g. /issue/:user/:repo/:state?/:labels?. May be an array of templates.
name ✅ Human-readable route name (also the default Radar rule title).
maintainers ✅ GitHub usernames responsible for the route.
handler ✅ (ctx) => Data | Response | null — does the actual fetching/parsing.
example ✅ A working example URL, shown in docs and used by tests.
parameters – Per-segment docs: description, default, and enumerated options.
categories – Catalog categories (e.g. programming, social-media).
view – Suggested reader view type (ViewType).
radar – Source→target rules for RSSHub Radar auto-detection.
description – Extra notes (Markdown).
features – Capability/requirement flags (see below).

The features object signals operational requirements:

Flag Meaning
requireConfig Route needs environment configuration (named keys with descriptions); used to surface "this route needs a token/cookie".
requirePuppeteer Route drives a headless browser (Playwright/Puppeteer).
antiCrawler Source has anti-bot measures.
supportBT / supportPodcast / supportScihub Route emits BitTorrent enclosures / podcast metadata / supports the scihub parameter.
nsfw Route is NSFW — filtered out entirely when DISABLE_NSFW=true.

2.5 Output rendering

The template middleware (lib/middleware/template.tsx) selects the serializer from ?format=:

format Renderer Notes
rss (default) <RSS> JSX view RSS 2.0 XML.
atom <Atom> JSX view Atom 1.0 XML.
json json() JSON Feed; Content-Type: application/feed+json.
rss3 / ums rss3() RSS3 JSON (ums retained for backward compatibility).
debug.json raw debug JSON Only when DEBUG_INFO is enabled.
N.debug.html item N's raw description HTML Only when DEBUG_INFO is enabled; for route debugging.

The middleware also normalizes data before rendering: collapses whitespace, trims titles to TITLE_LENGTH_LIMIT, strips Unicode control characters from descriptions, converts dates to ISO-8601 for non-RSS formats, and computes the RSS <ttl>.

2.6 The metadata API (/api)

Mounted at /api, built with @hono/zod-openapi (lib/api/index.ts). It powers the documentation site, RSSHub Radar, and external tooling:

Endpoint Purpose
GET /api/namespace/all All namespaces + routes.
GET /api/namespace/:namespace One namespace's routes.
GET /api/category/:category Routes by category.
GET /api/radar/rules/all All Radar detection rules.
GET /api/radar/rules/:domain Radar rules for a domain.
GET /api/route/status Route status data.
GET /api/follow/config Folo integration config.
GET /api/openapi.json OpenAPI 3.1 document.
GET /api/reference Interactive API reference (Scalar UI).

3. System Requirements

Resource Recommendation
Node.js ^22.20.0 || ^24 (only if running from source/npm; the Docker image bundles Node 24).
Memory ≥512 MB for a basic instance; ≥1–2 GB if you enable the headless browser (Playwright/Chromium) for browser-dependent routes.
Redis Optional but recommended for production caching (redis:alpine is sufficient).
Headless browser Optional. Needed only for routes with requirePuppeteer. Provide via a remote endpoint (PLAYWRIGHT_WS_ENDPOINT) or the chromium-bundled image.
Disk Modest for the app; the Chromium-bundled image is substantially larger.
Outbound network RSSHub makes outbound HTTP to source sites; many routes target sites that may require a proxy in certain regions.

Tip

Start with the lightweight default image (no bundled browser) and add a separate headless-browser container only when you actually need a requirePuppeteer route. It keeps memory and image size down.


4. Configuration Reference

All configuration is via environment variables, parsed once in lib/config.ts. Values are read from process.env (and a .env file via dotenv). Booleans accept true/1 for true and false/0/empty for false. This section is the authoritative, source-derived reference.

Note

Defaults below are exactly as coded in lib/config.ts. An empty "Default" cell means the value is unset/optional unless noted.

4.1 Application

Variable Default Purpose
DISALLOW_ROBOT false Block search-engine crawlers (adds robots directives).
ENABLE_CLUSTER false Run in Node cluster mode (multiple workers).
IS_PACKAGE unset Internal: instance is running as the published npm library.
NODE_NAME unset A name for this node, surfaced in logs/metrics.
PUPPETEER_WS_ENDPOINT unset WebSocket endpoint of a remote Puppeteer browser.
PLAYWRIGHT_WS_ENDPOINT falls back to PUPPETEER_WS_ENDPOINT WebSocket endpoint of a remote Playwright browser.
CHROMIUM_EXECUTABLE_PATH unset Path to a local Chromium binary (set automatically in the arm Docker image).

4.2 Network

Variable Default Purpose
PORT 1200 Listening port.
LISTEN_INADDR_ANY true Allow connections from any address (public). Set 0 to bind localhost-only.
DISABLE_IPV6 false Force IPv4 for outbound requests.
REQUEST_RETRY 2 Outbound request retry count on failure.
REQUEST_TIMEOUT 30000 Outbound request timeout (ms).
UA a Chrome desktop UA string User-Agent for outbound requests.
NO_RANDOM_UA false If true (and UA unset), use the fixed RSSHub UA instead of a randomized one.
ALLOW_ORIGIN unset CORS Access-Control-Allow-Origin value for the API/feeds.

4.3 Caching

RSSHub caches route output to avoid re-scraping sources. The cache key is xxhash64(path + format + limit), so different formats and limits cache independently.

Variable Default Purpose
CACHE_TYPE memory memory (LRU), redis, http, or empty string to disable caching.
CACHE_EXPIRE 300 Route cache TTL in seconds (also drives the RSS <ttl>).
CACHE_CONTENT_EXPIRE 3600 TTL for "unchanging" content (e.g. full-text article bodies), seconds.
CACHE_REQUEST_TIMEOUT 60 Request-coalescing lock TTL (seconds) — how long a duplicate request waits.
MEMORY_MAX 256 Max items in the in-memory LRU cache.
REDIS_URL redis://localhost:6379/ Redis connection URL (when CACHE_TYPE=redis).
CACHE_HTTP_URL unset External HTTP cache endpoint (when CACHE_TYPE=http).
CACHE_HTTP_TOKEN unset Auth token for the HTTP cache.

Warning

Setting CACHE_TYPE='' (empty) disables caching entirely. The instance will re-scrape on every request — heavy load on both RSSHub and the source sites, and it lowers the RSS <ttl> to 1 minute. Only do this for debugging.

4.4 Proxy

For reaching sources that are geo-blocked or rate-limited. Configure either a single URI, a list, or discrete parts.

Variable Default Purpose
PROXY_URI unset Single proxy URI (http://, https://, socks://, etc.).
PROXY_URIS unset Comma-separated list of proxy URIs (load-balanced/failover).
PROXY_PROTOCOL unset Proxy protocol when specifying parts.
PROXY_HOST unset Proxy host.
PROXY_PORT unset Proxy port.
PROXY_AUTH unset Proxy Authorization value.
PROXY_URL_REGEX .* Only proxy outbound URLs matching this regex.
PROXY_STRATEGY all all (proxy every request) or on_retry (only after a direct attempt fails).
PROXY_FAILOVER_TIMEOUT 5000 Failover timeout (ms) when using PROXY_URIS.
PROXY_HEALTH_CHECK_INTERVAL 60000 Proxy health-check interval (ms).
PAC_URI unset Proxy auto-config (PAC) file URI.
PAC_SCRIPT unset Inline PAC script.

4.5 Access control

Variable Default Purpose
ACCESS_KEY unset If set, every feed request must include ?key=<ACCESS_KEY> or ?code=<md5(path+ACCESS_KEY)>. The root, robots.txt, favicon.ico, and logo.png remain open.

Important

ACCESS_KEY is the only built-in authentication. It gates feed access, not the /api metadata endpoints' shape — but those run behind the same middleware, so a set key protects them too (except the always-open paths). It does not encrypt anything; treat the key as a shared secret and serve over HTTPS.

4.6 Logging, errors, and observability

Variable Default Purpose
DEBUG_INFO true Controls the / debug panel, /metrics, and debug.json/.debug.html formats. true = always on; false = off; any other string = on only when the request includes ?debug=<that string>.
LOGGER_LEVEL info Winston log level (debug, info, warn, error).
NO_LOGFILES false Disable writing log files (log to console only).
SHOW_LOGGER_TIMESTAMP false Prefix log lines with timestamps.
OTEL_SECONDS_BUCKET 0.01,0.1,1,2,5,15,30,60 OpenTelemetry histogram buckets (seconds).
OTEL_MILLISECONDS_BUCKET 10,20,50,100,250,500,1000,5000,15000 OpenTelemetry histogram buckets (ms).
HONEYBADGER_API_KEY unset Enable Honeybadger error reporting.
SENTRY unset Sentry DSN (enables Sentry error reporting).
SENTRY_ROUTE_TIMEOUT / ERROR_TRACKING_ROUTE_TIMEOUT 30000 Report routes slower than this (ms) to error tracking.
ENABLE_REMOTE_DEBUGGING false Expose a remote Node debugging endpoint (use only in trusted networks).

Caution

Leaving DEBUG_INFO=true on a public instance exposes a debug panel and /metrics with route hit/error counts and recently-requested paths. For a hardened public deployment set DEBUG_INFO=false, which also disables /metrics.

4.7 Feed behavior

Variable Default Purpose
TITLE_LENGTH_LIMIT 150 Max item title length (bytes; CJK chars count as 2) before truncation with ....
SUFFIX unset HTML appended to every item description.
FILTER_REGEX_ENGINE re2 Engine for filter* parameters: re2 (safe, linear-time) or regexp (full JS RegExp, allows backtracking).
ALLOW_USER_HOTLINK_TEMPLATE false Allow users to pass image_hotlink_template / multimedia_hotlink_template query params.
HOTLINK_TEMPLATE unset Operator-forced media-URL rewrite template (overrides user templates). Supports ${property} and ${property_ue} (URL-encoded) interpolation over URL parts (host, pathname, href, …).
HOTLINK_INCLUDE_PATHS unset Comma-separated route-path prefixes to which the hotlink template applies.
HOTLINK_EXCLUDE_PATHS unset Comma-separated route-path prefixes to exclude from the hotlink template.
ALLOW_USER_SUPPLY_UNSAFE_DOMAIN false Permit routes that take a user-supplied target domain (SSRF risk — see Security).
DISABLE_NSFW false Filter out all routes flagged features.nsfw at registry build.

Note

The hotlink template only accepts a fixed allowlist of URL properties (hash, host, hostname, href, origin, password, pathname, port, protocol, search, searchParams, username). An unknown property throws Invalid URL property. This validation is the guardrail that makes user-supplied templates safe-ish — but keep ALLOW_USER_HOTLINK_TEMPLATE=false unless you need it.

4.8 OpenAI / AI features (the chatgpt parameter)

Variable Default Purpose
OPENAI_API_KEY unset Enables the ?chatgpt= parameter. Without it, chatgpt is a no-op.
OPENAI_MODEL gpt-3.5-turbo-16k Model name.
OPENAI_API_ENDPOINT https://api.openai.com/v1 API base URL (point at any OpenAI-compatible endpoint).
OPENAI_TEMPERATURE 0.2 Sampling temperature.
OPENAI_MAX_TOKENS unset Max completion tokens.
OPENAI_INPUT_OPTION description What to process: description, title, or both.
OPENAI_PROMPT "Please summarize the following article…" System prompt for description processing.
OPENAI_PROMPT_TITLE "Please translate the following title into Simplified Chinese…" System prompt for title processing.

4.9 Folo integration

Variable Default Purpose
FOLLOW_OWNER_USER_ID unset Folo owner user ID for this instance.
FOLLOW_DESCRIPTION unset Instance description shown in Folo.
FOLLOW_PRICE unset Listing price (Folo marketplace).
FOLLOW_USER_LIMIT unset User limit for the instance listing.

4.10 Remote configuration

Variable Default Purpose
REMOTE_CONFIG unset URL to fetch a JSON config object at startup; its keys are merged over the environment and config is recomputed.
REMOTE_CONFIG_AUTH unset Value for the Authorization: Basic header sent when fetching REMOTE_CONFIG.

Warning

REMOTE_CONFIG loads configuration over the network at boot. If the fetch fails the instance logs an error and continues with local config. Only point it at a source you fully control and serve it over HTTPS — it can inject any setting, including credentials and proxies.

4.11 Route-specific credentials (per-site)

Beyond the operational settings above, lib/config.ts defines ~150 site-specific variables that supply cookies, tokens, API keys, or login credentials to individual routes. A route's docs (and its features.requireConfig) tell you which it needs. These follow consistent naming conventions:

Pattern Examples Notes
<SITE>_COOKIE / <SITE>_COOKIES DOUBAN_COOKIE, WEIBO_COOKIES, ZHIHU_COOKIES, LOFTER_COOKIE A logged-in session cookie for the source.
<SITE>_TOKEN / <SITE>_*_TOKEN NOTION_TOKEN, TELEGRAM_TOKEN, READWISE_ACCESS_TOKEN, ZSXQ_ACCESS_TOKEN API or session tokens.
<SITE>_USERNAME + <SITE>_PASSWORD IWARA_USERNAME/IWARA_PASSWORD, DIDA365_*, MANGADEX_*, NHENTAI_* Login credential pairs.
<SITE>_API_KEY / <SITE>_KEY DISQUS_API_KEY, LASTFM_API_KEY, PIXABAY_KEY, YOUTUBE_KEY Third-party API keys.
OAuth client triplets SPOTIFY_CLIENT_ID/SPOTIFY_CLIENT_SECRET/SPOTIFY_REFRESHTOKEN, TUMBLR_*, YOUTUBE_CLIENT_ID/SECRET/REFRESH_TOKEN, TWITTER_CONSUMER_*/TWITTER_ACCESS_* Full OAuth app credentials.

A handful use a dynamic suffix so you can supply several accounts for the same source. The suffix after the prefix becomes a lookup key:

Prefixed variable Keyed by Used for
BILIBILI_COOKIE_<uid> the bilibili UID Per-account bilibili cookies.
DISCUZ_COOKIE_<cid> a Discuz config id Per-site Discuz cookies.
MEDIUM_COOKIE_<username> a Medium username (lowercased) Per-account Medium cookies.
EMAIL_CONFIG_<id> an arbitrary id IMAP mailbox config for email-to-RSS routes.
DISCOURSE_CONFIG_<id> an arbitrary id A JSON Discourse instance config (host + key).

Tip

For the complete, current list of which routes need which variable, consult each route's page on docs.rsshub.app or the source of truth — the ConfigEnvKeys union and the matching config object in lib/config.ts. Provide only the credentials for routes you actually use; every credential variable is optional.

Caution

These variables hold real account credentials. Inject them as secrets (Docker secrets, wrangler secret put, your platform's secret store), never bake them into an image or commit them. A leaked *_COOKIE is a full session hijack of that account.


5. Deployment

The same build runs on several targets. The npm scripts (package.json) define each path.

Single container, in-memory cache:

docker run -d --name rsshub -p 1200:1200 --restart always diygod/rsshub

Production stack with Redis and a headless browser — docker-compose.yml ships in the repo:

services:
    rsshub:
        image: diygod/rsshub
        restart: always
        ports:
            - '1200:1200'
        environment:
            NODE_ENV: production
            CACHE_TYPE: redis
            REDIS_URL: 'redis://redis:6379/'
            PLAYWRIGHT_WS_ENDPOINT: 'ws://browserless:3000'
        healthcheck:
            test: ['CMD', 'curl', '-f', 'http://localhost:1200/healthz']
            interval: 30s
            timeout: 10s
            retries: 3
        depends_on:
            - redis
            - browserless
    browserless:
        image: browserless/chrome
        restart: always
    redis:
        image: redis:alpine
        restart: always
        volumes:
            - redis-data:/data
volumes:
    redis-data:
docker compose up -d
[+] Running 3/3
 ✔ Container rsshub-redis-1        Started
 ✔ Container rsshub-browserless-1  Started
 ✔ Container rsshub-rsshub-1       Started

Tip

The image tag diygod/rsshub:chromium-bundled ships Chromium inside the RSSHub container, so you can drop the separate browserless service (and its PLAYWRIGHT_WS_ENDPOINT) at the cost of more memory and a larger image. The docker-compose.yml comments mark exactly which lines to remove for the bundled approach.

The image exposes 1200, runs npm run start under dumb-init as PID-1, and defaults to TZ=Asia/Shanghai — override TZ if you want local timestamps.

5.2 Cloudflare Workers

RSSHub builds a Worker bundle (wrangler.toml, dist-worker/worker.mjs). Caching uses a KV namespace, and a Browser binding provides Playwright.

npm run worker-deploy   # builds the worker bundle, then `wrangler deploy`

Important

On Workers, set sensitive config with wrangler secret put <KEY> (e.g. ACCESS_KEY, GITHUB_ACCESS_TOKEN), not in [vars]. The Workers Paid plan is recommended — the Free plan caps CPU at 10 ms/request, which is too tight for many routes; the Paid plan allows 30 s. Browser-dependent routes (requirePuppeteer) need the Browser binding, which is Paid-only. The Worker uses the nodejs_compat flag plus Node shims in lib/shims/ — some Node-only operations (child process, vm) are deliberately unsupported there.

5.3 Vercel

vercel.json declares the Hono framework preset. Build with npm run vercel-build (which runs build:routes then tsdown --config tsdown-vercel.config.ts). Connect the repo to Vercel and set environment variables in the project dashboard.

5.4 Fly.io

fly.toml is preconfigured: internal port 1200, forced HTTPS, autoscaling with suspend, and a /healthz health check.

fly deploy

5.5 From source / npm

pnpm install
pnpm build        # build:routes + tsdown
pnpm start        # NODE_ENV=production node dist/index.mjs

For development with hot reload and the directory-scan route loader:

pnpm dev          # tsx watch lib/index.ts, NODE_ENV=dev

RSSHub is also published as a library (rsshub on npm, IS_PACKAGE mode) for embedding the feed engine into another app.


6. Operations

6.1 Health and observability

Endpoint Use
GET /healthz Liveness probe. Returns success when the app is serving. Wire it into Docker/K8s/Fly health checks.
GET /metrics Prometheus metrics (request counts, errors, latency histograms). Available only when DEBUG_INFO != false.
GET / Landing/debug page.

OpenTelemetry is built in (the trace middleware and @opentelemetry/* deps). Configure histogram buckets via OTEL_SECONDS_BUCKET / OTEL_MILLISECONDS_BUCKET. Errors can be shipped to Honeybadger (HONEYBADGER_API_KEY) and/or Sentry (SENTRY).

6.2 Logs

Winston handles logging at LOGGER_LEVEL. By default RSSHub writes log files; set NO_LOGFILES=true to log to console only (recommended for containers, where you want stdout capture). SHOW_LOGGER_TIMESTAMP=true adds timestamps if your log shipper does not.

docker logs -f rsshub

6.3 Caching operations

  • Pick Redis for any multi-instance or production deployment so the cache is shared and survives restarts. In-memory caching is per-process and lost on restart.
  • The request-coalescing lock (cache.ts) means a cold-cache feed that is requested concurrently makes the source call once; the other requests wait up to CACHE_REQUEST_TIMEOUT then either serve the now-warm cache or receive RequestInProgressError (503).
  • Cache keys include format and limit, so ?format=json and ?format=rss of the same route are cached separately.
  • To force a route to bypass caching, a handler can set Cache-Control: no-cache; the cache middleware then skips storing it.

6.4 Startup checklist

  • [ ] Choose CACHE_TYPE (redis for production) and provide REDIS_URL.
  • [ ] Set DEBUG_INFO=false for a hardened public instance.
  • [ ] Set ACCESS_KEY if the instance is private.
  • [ ] Configure a proxy (PROXY_URI/PROXY_URIS) if your sources need one.
  • [ ] Provide only the route credentials you need, as secrets.
  • [ ] Decide on headless-browser strategy (none / sidecar browserless / bundled image).
  • [ ] Confirm GET /healthz returns success.
  • [ ] If self-building for production/Workers, run pnpm build:routes so the static registry is current.

7. Security

Important

RSSHub is a server that fetches arbitrary external URLs on behalf of requests. Treat it as an outbound proxy and harden accordingly.

Concern Mitigation
SSRF (server-side request forgery) Some routes accept a user-supplied target domain. These are gated behind ALLOW_USER_SUPPLY_UNSAFE_DOMAIN (default false). Keep it off unless you trust all callers; turning it on lets users make your server fetch internal/metadata endpoints.
Open access By default any client can pull any feed. Set ACCESS_KEY for private instances; serve over HTTPS so the key/feeds are not sniffable.
Credential exposure All *_COOKIE/*_TOKEN/*_PASSWORD variables are live account secrets. Inject as platform secrets; never commit or bake into images. A leaked cookie hijacks that account.
Debug surface DEBUG_INFO=true exposes recently-requested paths, error counts, and /metrics. Set false on public instances.
Hotlink template abuse ALLOW_USER_HOTLINK_TEMPLATE=true lets users rewrite media URLs; mitigated by a strict URL-property allowlist, but leave it off unless required.
Regex DoS The default FILTER_REGEX_ENGINE=re2 is immune to catastrophic backtracking. Only switch to regexp (full JS RegExp) if you trust the users supplying filter patterns.
Remote config injection REMOTE_CONFIG fetches settings over the network at boot. Point it only at a source you control, over HTTPS, with REMOTE_CONFIG_AUTH.
Content sanitization RSSHub strips <script> tags, removes onclick/onerror/onload attributes, and sets referrerpolicy=no-referrer on images/iframes during item processing (parameter.ts), reducing the risk of malicious item HTML in readers.
Remote debugging ENABLE_REMOTE_DEBUGGING=true opens a Node inspector — use only inside a trusted network, never on a public host.

Error handling and status codes

The central error handler (lib/errors/index.tsx) maps failures to HTTP statuses and reports to Honeybadger/Sentry when configured:

Error class HTTP status Meaning
RejectError 403 Access control denied (bad/missing key/code).
NotFoundError 404 No matching route ("The route does not exist or has been deleted.").
RequestInProgressError 503 Duplicate request still fetching; sets a Cache-Control window.
HTTPError / RequestError / FetchError 503 Upstream source failed.
(default) 503 Any other route error.

In production, error responses show only error.message (not the stack); non-production shows the stack. Requesting ?format=json returns the error as JSON instead of an HTML page.


8. Codebase Map (for contributors)

lib/
  app-bootstrap.tsx     Hono app + global middleware chain
  app.ts                Entry that loads request-rewriter then the app
  index.ts / server.ts  Node server entry (@hono/node-server)
  worker.ts             Cloudflare Workers entry
  config.ts             ALL environment configuration (source of truth)
  registry.ts           Route auto-discovery + Hono mounting
  types.ts              Route / Namespace / APIRoute / Data type contracts
  middleware/           The 14-stage chain (access-control, parameter, cache, …)
  views/                RSS / Atom / JSON / RSS3 renderers (JSX)
  api/                  Public metadata API (namespace, category, radar, route)
  utils/                Shared helpers: got/ofetch HTTP, cache, playwright,
                        proxy, header-generator, parse-date, render, …
  routes/<namespace>/   1,500+ source integrations (namespace.ts + route files)
  routes-deprecated/    Legacy routes kept for backward compatibility
  shims/                Node-API shims for the Workers runtime
scripts/workflow/       build-routes / build-docs / format-description tooling
assets/build/           Generated static route registry (routes.js / routes.json)

Adding a route (high level)

  1. Create lib/routes/<namespace>/namespace.ts (if the namespace is new) exporting a Namespace.
  2. Create a route file exporting route: Route — fill path, name, maintainers, example, parameters, and an async handler(ctx) that returns normalized Data (title, link, item[]).
  3. Add radar rules so RSSHub Radar can auto-detect the feed from the source URL.
  4. If the route needs credentials, read them from config (add the key to lib/config.ts) and set features.requireConfig.
  5. Mark features.requirePuppeteer if you drive a headless browser, features.nsfw if applicable.
  6. Run pnpm build:routes and test with pnpm dev.

Note

Handlers return data, not a serialized feed. The shared middleware chain handles output format, filtering, limiting, caching, and hotlink rewriting — so a route author never re-implements those. This separation is why every route supports the same universal parameters automatically.


9. Known Limitations & Improvement Areas

Note

These are observations from the source at the documented commit, not defects. They are the natural trade-offs of a community-scale scraping framework.

  • Source fragility. Each route depends on an external site's structure; sites change and routes break. The this route is empty guard and the maintainers list mitigate this, but breakage is inherent. A route-status dashboard (/api/route/status) helps operators spot failures.
  • Credential sprawl. ~150 site-specific credential variables live in one config.ts union. It is exhaustive and well-organized, but onboarding a credential-heavy deployment means consulting per-route docs. A generated "which routes need which env var" matrix would help operators.
  • Workers runtime gaps. The Cloudflare Workers target relies on shims (lib/shims/) that explicitly throw for child_process, vm, and some xxhash APIs. Routes depending on those Node features will fail on Workers — there is no static guard preventing such a route from being deployed to a Worker; it surfaces at request time.
  • Headless-browser cost. requirePuppeteer routes need Chromium, which dominates memory and image size. The split (sidecar vs. bundled image vs. remote endpoint) is flexible but adds operational decisions.
  • Cache coalescing timeout. Under a cold cache and high concurrency for the same heavy route, waiters can hit RequestInProgressError (503) after CACHE_REQUEST_TIMEOUT. Tuning that value trades responsiveness against duplicate upstream load.
  • Regex engine choice. re2 (default) is safe but lacks some PCRE features; regexp is full-featured but exposes the instance to ReDoS from user-supplied filter patterns. There is no per-request engine override — it is instance-wide.