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, thenparameterfilters it, thenanti-hotlinkrewrites media, thentemplateserializes it.cachewraps 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 anamespace: Namespaceobject (display name, sourceurl,description, language, categories).- One file per route — exports a
route: Routeobject (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 fromassets/build/routes.jswill 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
requirePuppeteerroute. 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_KEYis the only built-in authentication. It gates feed access, not the/apimetadata 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=trueon a public instance exposes a debug panel and/metricswith route hit/error counts and recently-requested paths. For a hardened public deployment setDEBUG_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 throwsInvalid URL property. This validation is the guardrail that makes user-supplied templates safe-ish — but keepALLOW_USER_HOTLINK_TEMPLATE=falseunless 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_CONFIGloads 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
ConfigEnvKeysunion and the matchingconfigobject inlib/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*_COOKIEis a full session hijack of that account.
5. Deployment
The same build runs on several targets. The npm scripts (package.json) define each path.
5.1 Docker (recommended)
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-bundledships Chromium inside the RSSHub container, so you can drop the separatebrowserlessservice (and itsPLAYWRIGHT_WS_ENDPOINT) at the cost of more memory and a larger image. Thedocker-compose.ymlcomments 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 thenodejs_compatflag plus Node shims inlib/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 toCACHE_REQUEST_TIMEOUTthen either serve the now-warm cache or receiveRequestInProgressError(503). - Cache keys include
formatandlimit, so?format=jsonand?format=rssof 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(redisfor production) and provideREDIS_URL. - [ ] Set
DEBUG_INFO=falsefor a hardened public instance. - [ ] Set
ACCESS_KEYif 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 /healthzreturns success. - [ ] If self-building for production/Workers, run
pnpm build:routesso 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)
- Create
lib/routes/<namespace>/namespace.ts(if the namespace is new) exporting aNamespace. - Create a route file exporting
route: Route— fillpath,name,maintainers,example,parameters, and an asynchandler(ctx)that returns normalizedData(title,link,item[]). - Add
radarrules so RSSHub Radar can auto-detect the feed from the source URL. - If the route needs credentials, read them from
config(add the key tolib/config.ts) and setfeatures.requireConfig. - Mark
features.requirePuppeteerif you drive a headless browser,features.nsfwif applicable. - Run
pnpm build:routesand test withpnpm 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 emptyguard 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.tsunion. 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 forchild_process,vm, and somexxhashAPIs. 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.
requirePuppeteerroutes 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) afterCACHE_REQUEST_TIMEOUT. Tuning that value trades responsiveness against duplicate upstream load. - Regex engine choice.
re2(default) is safe but lacks some PCRE features;regexpis full-featured but exposes the instance to ReDoS from user-suppliedfilterpatterns. There is no per-request engine override — it is instance-wide.