Repository navigation
reverse proxy
rDownloader binds to loopback by default and provides no TLS of its own. Remote access means putting a proxy in front and telling rDownloader about it; otherwise four things go wrong in ways that do not look like a configuration problem. Bound beyond loopback, the service belongs behind a proxy for one more reason: it sets no limit on how long a client may take to send its request headers, and the proxy does.
| Symptom | Cause |
|---|---|
| Everyone shares one rate limit; the session list shows one address for every device | No trusted proxy is configured, so the proxy's own address is treated as the client's |
| Signing in appears to work, and the next page is the login screen again | The session cookie is Secure but the deployment is plain HTTP, so the browser drops it |
| The page loads blank under a sub-path | The mount point is not configured, so the asset references point at the root |
| Anyone can claim to be any address | A forwarded header is believed from a peer that is not a configured proxy — this is why nothing is trusted by default |
All under Settings → Security → Reachability & reverse proxy; in the settings document they
are external_url, trusted_proxies, cookie_security and allowed_hosts.
external_url — what the outside world calls this installation, scheme and path included:
https://rd.example.com, or https://home.example.com/downloads.
trusted_proxies — the address ranges whose X-Forwarded-For is believed, as CIDR or bare
addresses: 127.0.0.1, 10.0.0.0/8, fd00::/8. Anything not in this list has its forwarded
headers ignored entirely and is treated as the client itself. Empty is the safe default.
Several X-Forwarded-For or Forwarded lines in one request are read as one list, so a proxy that
appends a line of its own is believed rather than the client.
cookie_security — auto follows the scheme of the external URL and is right for almost
every deployment. always is for a proxy that terminates TLS but is described by an http URL;
never is for a plain-HTTP LAN deployment, where a Secure cookie would simply be dropped.
rDownloader checks the host name of every request before it answers. These always work:
- an IP address —
http://192.168.1.20:8710,http://127.0.0.1:8710; -
localhost, and names ending in.localhost; - a name that exists only on the local network — a single label without a dot (
nas,syno-xyz, the Docker Compose service namerdownloader), a name ending in.local, and names under.home.arpaor.internal. Nobody can register these in public DNS, so no page can rebind to them; - the host of the external URL.
Any other name is refused with 403 and the code request.host_not_allowed until it is listed
under Allowed host names (allowed_hosts), one bare name per line — nas.lan,
downloads.example.org, no http://, no port. List the names you type into the browser
yourself, such as a NAS hostname under a router's own domain. Open the interface by its IP
address to add one. A browser that opens a refused name shows a short page instead of the
interface: the name it was opened by, where to allow it and the address to make the change from.
This is the defence against DNS rebinding, where a stranger's web page points its own domain at
127.0.0.1 to reach rDownloader through your browser: that domain is not on the list.
A request that changes something is refused with 403 and the code
request.cross_site_refused when the browser reports that a page of another site sent it — its
Origin names neither the address the request went to nor the external URL. That stops a
stranger's page from installing a plugin or importing rules through your browser, which with the
login switched off would otherwise run as the administrator. Behind a proxy, current browsers
pass anyway; if an older browser is refused, set the external URL, or have the proxy pass the
public name on (proxy_set_header Host $host). On a public port other than 80 or 443, set the
external URL. Scripts and command-line tools are not affected.
Switching the administrator login off applies to this machine only. Without a login, a request is let in only when it comes from a loopback address and no proxy forwarded it. Behind a reverse proxy — even one on the same machine — and from any other computer the password is still required.
A proxy or tunnel on the same machine must set X-Forwarded-For. One that forwards requests
without it connects from loopback on everybody's behalf, and with the login switched off everybody
behind it is the administrator. Settings → Security therefore warns while the login is off and
a trusted proxy or an external URL is set; switch the login back on under Settings → Security →
Sign-in, or make sure the proxy sets the header.
rdownloader doctor prints the resolved contract and warns about the half-configured
combinations. The settings are read at start and applied again whenever they are saved.
The whole application works under a sub-path — API, event stream and MCP endpoint included, and
every file the interface loads: its scripts and stylesheets, fonts and the logo are asked for under
the sub-path too, so a proxy that passes only /rd/ serves every page and nothing is requested at
the root.
server {
listen 443 ssl;
server_name rd.example.com;
location / {
proxy_pass http://127.0.0.1:8710;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme; # not read; the scheme comes from external_url
# The event stream is a long-lived response. Without these, updates arrive in
# bursts when a buffer fills, or the connection is closed after 60 seconds.
proxy_buffering off;
proxy_read_timeout 1h;
proxy_http_version 1.1;
}
}Settings: external_url = https://rd.example.com, trusted_proxies = 127.0.0.1.
Under a sub-path, keep the same path on both sides — rDownloader strips its own mount point, so the proxy must not strip it as well:
location /downloads/ {
proxy_pass http://127.0.0.1:8710; # no trailing path: the prefix is passed through
# …the same headers as above
}Settings: external_url = https://rd.example.com/downloads.
rd.example.com {
reverse_proxy 127.0.0.1:8710 {
flush_interval -1 # do not buffer the event stream
}
}Caddy sets X-Forwarded-For itself. Settings: external_url = https://rd.example.com,
trusted_proxies = 127.0.0.1.
For a home connection without a fixed address: a dynamic DNS name points at your router, the router forwards HTTPS to the machine running Caddy, and Caddy fetches and renews a Let's Encrypt certificate by itself. rDownloader keeps listening on loopback only.
-
Get a DynDNS name and keep it current. Most routers do this themselves — on a FRITZ!Box under Internet → Permit Access → DynDNS (or use its MyFRITZ! name); otherwise a small update client from your DynDNS provider. Example name:
rd.example.dyndns.org. -
Forward the ports in the router to the machine Caddy runs on — on a FRITZ!Box under Internet → Permit Access → Port Sharing:
- TCP 443 — the HTTPS traffic;
- TCP 80 — Let's Encrypt checks the name over plain HTTP before it issues a certificate, and Caddy answers that check itself and redirects everything else to HTTPS.
Never forward rDownloader's own port (
8710); only Caddy is reachable from outside. -
Run Caddy on that machine with this
Caddyfile:rd.example.dyndns.org { reverse_proxy 127.0.0.1:8710 { flush_interval -1 # do not buffer the event stream } }
On first start Caddy requests the certificate; from then on it renews it before it runs out. On Windows,
caddy runin the folder holding theCaddyfile, or install it as a service; on Linux thecaddypackage runs it as a systemd service. -
Tell rDownloader about it under Settings → Security → Reachability & reverse proxy:
external_url = https://rd.example.dyndns.org-
trusted_proxies = 127.0.0.1— or Caddy's address when it runs on another machine, or the container network (see In Docker).
The host of the external URL is allowed automatically; nothing needs to go under Allowed host names.
-
Check it from outside, for example from a phone on mobile data:
https://rd.example.dyndns.orgshows the sign-in page with a valid certificate, and the session list under Settings → Security → Sessions shows the phone's address rather than Caddy's.
When port 80 cannot be opened (some providers block it), Caddy can prove the name over DNS
instead: that needs a Caddy build with your DNS provider's module and an API token of that
provider, configured with tls { dns <provider> … }. Not every DynDNS service offers such an API.
Without a public IPv4 address (DS-Lite, carrier-grade NAT) nothing can be forwarded on IPv4. Either forward over IPv6 — the DynDNS name then needs an AAAA record, and your router has to open the port for the machine's IPv6 address — or use a tunnel such as Cloudflare Tunnel, which needs no open port at all.
Before you open it to the internet: a strong administrator password and a second factor — a passkey or an authenticator app (see Accounts, sessions and permissions). With the external URL set, passkeys work under the DynDNS name too (Passkeys need the external URL). HTTPS is also what Web Push needs: the interface installed as an app on a phone can receive notifications.
http:
routers:
rdownloader:
rule: "Host(`rd.example.com`)"
service: rdownloader
tls: {}
services:
rdownloader:
loadBalancer:
servers:
- url: "http://127.0.0.1:8710"
responseForwarding:
flushInterval: "1ms" # the event stream againThe trusted range is the container network, not loopback. For the default bridge that is
172.16.0.0/12.
rdownloader doctor prints the configured trusted range and its warnings, but it observes no
request — the address requests actually arrive from is in the proxy's access log or in
docker network inspect.
cloudflared connects out to Cloudflare and hands the requests for a public name on to
rDownloader; no port is opened to the internet, and Cloudflare provides the HTTPS. Point the
public hostname at rDownloader on loopback — in the tunnel's configuration file, or as the public
hostname's service (HTTP, 127.0.0.1:8710) in the Zero Trust dashboard:
ingress:
- hostname: rd.example.com
service: http://127.0.0.1:8710
- service: http_status:404The first visit through the tunnel shows rDownloader does not answer to this host name
(request.host_not_allowed): the name is not allowed yet. Open rDownloader by an address it
always answers to — http://127.0.0.1:8710 on the machine itself, or its LAN address — and set
under Settings → Security → Reachability & reverse proxy:
-
External URL
https://rd.example.com. Its host is always allowed, the session cookie is markedSecureand passkeys bind to it. If the tunnel is only a second way in and the external URL names something else, putrd.example.comunder Allowed host names instead. -
Trusted proxies
127.0.0.1.cloudflaredconnects from this machine; Cloudflare sends the visitor's address asX-Forwarded-For(beside its ownCF-Connecting-IP), which rDownloader believes only from a trusted proxy. Without it every visitor hascloudflared's address in the sign-in limiting and in the session list. Withcloudflaredin a container, trust its container network instead (see In Docker).
Signing in stays on through the tunnel, even with the login switched off for this computer. The live updates need no extra setting.
Cloudflare Access as a second lock. An Access application on rd.example.com puts
Cloudflare's own sign-in (a code by e-mail, your identity provider) in front of the tunnel, so a
stranger never sees rDownloader's login page. It comes in addition to rDownloader's password,
never instead of it (see below). Clients that are not a browser — the browser extension's
pairing, Sonarr, Radarr and the other *arr apps, the command line — cannot answer Access's
sign-in: give them an Access service token where they can send its headers, or keep them on the
LAN address.
A passkey is bound to the origin it was created at — that is its phishing resistance. rDownloader
takes that origin from external_url whenever it is set, never from a header the caller chooses.
Two consequences:
-
Without
external_url, passkeys work only athttp://localhost:<port>. Reaching the service athttp://127.0.0.1:8710does not work: browsers bind a passkey to a domain, never to an address. rDownloader refuses with that explanation. -
Changing
external_urlinvalidates existing passkeys. They were registered against the old origin and the browser will not offer them at the new one. The password still signs in; enrol the passkeys again afterwards. Moving from a bare host name to a subdomain, or fromhttptohttps, both count as a change.
Nothing else about passkeys depends on the proxy. They work under a base path, and the
X-Forwarded-For settings do not affect them.
Authentication at the proxy as a replacement for rDownloader's own (forward-auth). Any request reaching the service directly — a container on the same network, a misconfigured firewall — would then be unauthenticated, and trusted proxies vouch for an address, not for a person. The administrator password stays the credential; to sign in with the provider your proxy already uses, set it up as an OpenID Connect provider instead — see Sign in with an identity provider, whose redirect URI comes from the external URL.
Path rewriting in the proxy. Stripping the prefix at the proxy breaks as soon as something
generates an absolute URL. Pass the prefix through and set external_url instead.
Getting started
- Installation
- Building from source
- Translating rDownloader
- First run
- Your first download
- The interface
Using rDownloader
- The download queue
- Download history
- The LinkGrabber
- Site rules
- Hosters and accounts
- Usenet and NZB
- BitTorrent
- Video and audio downloads
- Image galleries
- Livestream recording
- FTP, SFTP and WebDAV
- Object storage
- Subscriptions
- Remote jobs
- Storage, categories and rules
- Watched folders
- Post-processing
- Bandwidth and power
- Notifications
- Automation
- Statistics
- Keyboard and accessibility
Integrations
- Browser extension
- Desktop capture agent
- Command line client
- REST API
- MCP server
- Automation tool compatibility
Operating
- Docker and NAS
- Synology NAS
- Reverse proxy
- Accounts, sessions and permissions
- Sign in with an identity provider
- External tools
- Backup and restore
- Updates
- Security and privacy
- Diagnostics and maintenance
- Troubleshooting
- Defaults and limits
- Support matrix
Plugins
Plugin development
- Developing a plugin
- Resolver
- Transfer backend
- Intake parser
- Intake parser with mirrors
- Folder crawler
- Remote job
- Sign-in
- OAuth sign-in
- Metadata enricher
- Notification destination
- Post-processing step
- Upload destination
- Stream transform
- Plugin reference
- Packaging, signing and distribution
- Running a plugin repository
Reference