Downdoot
An alternative private front-end to Reddit. Downdoot is a fork of Redlib, with its roots in Libreddit.
10-second pitch: Downdoot is a private front-end like Invidious but for Reddit. Browse the coldest takes of r/unpopularopinion without being tracked.
- 🚀 Fast: written in Rust for blazing-fast speeds and memory safety
- ☁️ Light: no ads, no tracking, no bloat, and it works without JavaScript
- 🕵 Private: all requests are proxied through the server, including media
- 🔒 Secure: strong Content Security Policy prevents browser requests to Reddit
Table of Contents
Instances
The list of public instances lives in the downdoot-instances repository: a table in its README, and instances.json for software that needs the same list in a machine-readable form.
To add your own self-hosted instance, follow the instructions in that README. Instances have to follow its rules.
About
Downdoot hopes to provide an easier way to browse Reddit, without the ads, trackers, and bloat. Downdoot was inspired by other alternative front-ends to popular services such as Invidious for YouTube, Nitter for Twitter, and Bibliogram for Instagram.
Downdoot currently implements most of Reddit's (signed-out) functionalities but still lacks a few features.
Screenshots
Show screenshots
A subreddit, here r/cats sorted by top of the month, in the dark theme:
A post and its comments, in the light theme:
The same feed on a phone:
Built with
- Rust - Programming language
- Hyper - HTTP server
- wreq - HTTP client
- Askama - Templating engine
- BoringSSL - TLS library
Keyboard shortcuts
On post pages, with JavaScript enabled:
| Key | Action |
|---|---|
j / k |
Next / previous comment |
t / p |
Next / previous top-level comment |
Enter |
Open the post's link (link posts only) |
Shift + Enter |
Open the post's link in a new tab (link posts only) |
The keys are ignored while typing in a field, and Enter only acts when nothing is focused.
How is it different from other Reddit front ends?
Teddit
Teddit is another open source alternative frontend to Reddit. There is no connection between the two, and you're welcome to use whichever one you favor.
The biggest differences are:
- Downdoot is themed around Reddit's redesign whereas Teddit appears to stick much closer to Reddit's old design. This may suit some users better as design is always subjective.
- Downdoot is written in Rust for speed and memory safety. It serves pages with Hyper, a speedy and lightweight HTTP implementation.
Redlib and Libreddit
Downdoot is a fork of Redlib, which itself originated as a fork of Libreddit. Libreddit was renamed to Redlib to avoid legal issues, as Reddit only allows the use of their name if structured as "XYZ For Reddit". Downdoot does not use the Reddit name either.
Technical improvements inherited from Redlib include:
- OAuth token spoofing: To circumvent rate limits imposed by Reddit, OAuth token spoofing is used to mimick the most common iOS and Android clients. While spoofing both iOS and Android clients was explored, only the Android client was chosen due to content restrictions when using an anonymous iOS client.
- Token refreshing: The authentication token is refreshed every 24 hours, emulating the behavior of the official Android app.
- HTTP header mimicking: Efforts are made to send along as many of the official app's headers as possible to reduce the likelihood of Reddit's crackdown on Downdoot's requests.
- GraphQL comments: Post pages fetch metadata through
/by_idand comments through Reddit's AndroidPostCommentsoperation, using the existing anonymous OAuth client. This handles Reddit's HTTP 403 block on the comments JSON endpoint. Comment sorting, focused threads, and cursor pagination work without a Reddit account or additional configuration. Each request loads up to 200 comments at 10 levels of depth; links load further comments and replies. The converter and rich text renderer are adapted from polak14's implementation. - Web comments fallback: If the GraphQL request fails, post pages fall back to the HTML comment fragments Reddit serves to logged-out visitors on its own website. These are parsed into the same structure, sanitized, and expanded breadth-first (up to 200 comments), so rendering, sorting, and focused threads stay the same. It uses its own cookie session per network, honors the onion service settings, and cannot show author flair or awards. Adapted from redlib#599 and baalajimaestro's fork.
- RedGifs: A post that links to a RedGifs page, and that Reddit has no video copy of, plays through Downdoot at
/redgifs/<id>. The server looks the gif up through RedGifs' anonymous API and streams the HD file (SD if there is no HD), so your browser never contacts RedGifs. Reddit's own copy is still used whenever there is one. Adapted from redlib#507.
Comparison
This section outlines how Downdoot compares to Reddit in terms of privacy.
Privacy
Logging: According to Reddit's privacy policy, they "may [automatically] log information" including:
- IP address
- User-agent string
- Browser type
- Operating system
- Referral URLs
- Device information (e.g., device IDs)
- Device settings
- Pages visited
- Links clicked
- The requested URL
- Search terms
Location: The same privacy policy goes on to describe that location data may be collected through the use of:
- GPS (consensual)
- Bluetooth (consensual)
- Content associated with a location (consensual)
- Your IP Address
Cookies: Reddit's cookie notice documents the array of cookies used by Reddit including/regarding:
- Authentication
- Functionality
- Analytics and Performance
- Advertising
- Third-Party Cookies
- Third-Party Site
Downdoot
For transparency, I hope to describe all the ways Downdoot handles user privacy.
Server
-
Logging: In production (when running the binary or hosting with docker), Downdoot logs nothing. When debugging (running from source without
--release), Downdoot logs post IDs fetched to aid with troubleshooting. -
Cookies: Downdoot uses optional cookies to store any configured settings in the settings menu. These are not cross-site cookies and the cookies hold no personal data.
Deployment
This section covers multiple ways of deploying Downdoot. Using Docker is recommended for production.
For configuration options, see the Configuration section.
Docker
Docker lets you run containerized applications. Containers are loosely isolated environments that are lightweight and contain everything needed to run the application, so there's no need to rely on what's installed on the host.
Container images for Downdoot are available at gitfield.org, with support for amd64, arm64, and armv7 platforms.
Each release is published as latest and under its version, for example 2026.10.0 and 2026.10.
Docker Compose
Important
These instructions assume the Compose plugin has already been installed. If not, follow these instructions on the Docker Docs for how to do so.
Copy contrib/compose/compose.yaml and modify any relevant values (for example, the ports Downdoot should listen on). To configure Downdoot with a config file, uncomment the volumes lines.
Start Downdoot in detached mode (running in the background):
docker compose up -d
Stream logs from the Downdoot container:
docker logs -f downdoot
Docker CLI
Deploy Downdoot:
docker pull gitfield.org/fsky/downdoot:latest
docker run -d --name downdoot -p 8080:8080 gitfield.org/fsky/downdoot:latest
Deploy using a different port on the host (in this case, port 80):
docker pull gitfield.org/fsky/downdoot:latest
docker run -d --name downdoot -p 80:8080 gitfield.org/fsky/downdoot:latest
If you're using a reverse proxy in front of Downdoot, prefix the port numbers with 127.0.0.1 so that Downdoot only listens on the host port locally. For example, if the host port for Downdoot is 8080, specify 127.0.0.1:8080:8080.
Stream logs from the Downdoot container:
docker logs -f downdoot
Podman
Podman lets you run containerized applications in a rootless fashion. Containers are loosely isolated environments that are lightweight and contain everything needed to run the application, so there's no need to rely on what's installed on the host.
Container images for Downdoot are available at gitfield.org, with support for amd64, arm64, and armv7 platforms.
Quadlets
Important
These instructions assume that you are on a systemd based distro with podman. If not, follow these instructions on podman's website for how to do so. It also assumes you have used
loginctl enable-linger <username>to enable the service to start for your user without logging in.
Copy contrib/quadlet/downdoot.container and the .env.example file to .config/containers/systemd/ and modify any relevant values (for example, the ports Downdoot should listen on, renaming the .env file and editing its values, etc.). To configure Downdoot with a config file, uncomment the Volume line.
To start Downdoot either reboot or follow the instructions below:
Notify systemd of the new files
systemctl --user daemon-reload
Start the newly generated service file
systemctl --user start downdoot.service
You can check the status of your container by using the following command:
systemctl --user status downdoot.service
Binary
If you're on Linux, you can grab a tarball from the newest release. There is one for each architecture (x86_64-unknown-linux-musl, aarch64-unknown-linux-musl and armv7-unknown-linux-musleabihf), with a .sha256 file next to it. The binary is statically linked.
Download the one for your machine using Wget, check it against its checksum, and unpack it (this example is for x86_64):
wget https://gitfield.org/fsky/downdoot/releases/latest/download/downdoot-x86_64-unknown-linux-musl.tar.gz
wget https://gitfield.org/fsky/downdoot/releases/latest/download/downdoot-x86_64-unknown-linux-musl.tar.gz.sha256
sha256sum -c downdoot-x86_64-unknown-linux-musl.tar.gz.sha256
tar xzf downdoot-x86_64-unknown-linux-musl.tar.gz
Install the binary to /usr/bin, owned by root:
sudo install -m 755 -o root -g root downdoot /usr/bin/downdoot
The man/ directory of the repository has the manual pages: downdoot(1) for the command line, and downdoot.scfg(5) for the config file and every setting. To install them:
sudo install -Dm644 man/downdoot.1 /usr/local/share/man/man1/downdoot.1
sudo install -Dm644 man/downdoot.scfg.5 /usr/local/share/man/man5/downdoot.scfg.5
To read one without installing it, run man ./man/downdoot.scfg.5.
Run Downdoot, which listens on port 8080 of all addresses ([::]:8080) by default:
downdoot
Important
If you're proxying Downdoot through NGINX (see issue #122), add
proxy_http_version 1.1;to your NGINX configuration file above your
proxy_passline.
Note
Downdoot serves HTTP/1.1 only, which is what reverse proxies use to reach a backend by default. Let the proxy offer HTTP/2 to browsers. Downdoot doesn't accept HTTP/2 without TLS (
h2c), so don't point a proxy at it that way. It also closes a connection whose client takes longer than 30 seconds to send its request headers.
Running as a systemd service
You can use the systemd service available in contrib/systemd/downdoot.service
(install it on /etc/systemd/system/downdoot.service).
The service reads its settings from /etc/downdoot/downdoot.scfg if that file
exists. Copy downdoot.example.scfg there and edit it, see the
config file. Without the file, Downdoot listens on [::]:8080;
set the address and port in the file's listen block.
When "Proxying using NGINX" where the proxy is on the same machine, you should
guarantee nginx waits for this service to start. Edit
/etc/systemd/system/downdoot.service.d/reverse-proxy.conf:
[Unit]
Before=nginx.service
Building from source
To deploy Downdoot with changes not yet included in the latest release, you can build the application from source. It needs Rust 1.89 or newer.
git clone https://gitfield.org/fsky/downdoot && cd downdoot
cargo build --release
./target/release/downdoot
The Makefile covers the same build and other common tasks. make runs cargo build --release, and make help lists the rest: tests and lints, static builds in Docker, container images, and installing the binary and man pages.
Downdoot uses boring-sys2, so building it also builds BoringSSL from source. That needs CMake, a C and C++ compiler, and libclang.
Linux and macOS
On Debian and Ubuntu:
sudo apt install git build-essential cmake libclang-dev
For other systems, refer to the BoringSSL documentation for dependencies.
Windows
Install MSVC, which you likely already have for Rust.
# Make sure to update your PATH, some of the installers don't do that by default (hense -i, interactive mode).
winget install -i Kitware.CMake
winget install -i NASM.NASM
winget install -i LLVM.LLVM
# For tests.
winget install GoLang.Go
launchd (macOS)
If you are on macOS, you can use the launchd service available in contrib/launchd/downdoot.plist.
Install it with cp contrib/launchd/downdoot.plist ~/Library/LaunchAgents/.
Load and start it with launchctl load ~/Library/LaunchAgents/downdoot.plist.
Configuration
Downdoot is configured with a config file, with environment variables, with command line flags, or any mix of them. When a setting is given more than once, the first of these wins:
- command line flags
- environment variables
- the config file
- the setting's default
Downdoot refuses to start when a setting is misspelled or holds something it can't use, and says what is wrong with each one. An environment variable that starts with DOWNDOOT_ but isn't a setting is reported and ignored. An empty value counts as not set.
downdoot --check-config prints every setting with its value and whether that came from the environment, the config file or the default.
The command line, the config file and every setting are also documented in the manual pages downdoot(1) and downdoot.scfg(5); see Binary for installing them.
Environment variables, for example:
DOWNDOOT_DEFAULT_SHOW_NSFW=on downdoot
DOWNDOOT_DEFAULT_WIDE=on DOWNDOOT_DEFAULT_THEME=dark downdoot
Note
If you're deploying Downdoot using the Docker CLI or Docker Compose, environment variables can be defined in a
.envfile, allowing you to centralize and manage configuration in one place.To configure Downdoot using a
.envfile, copy the.env.examplefile to.envand edit it accordingly. Every line in it is commented out: uncomment only what you want to change, because a variable set there overrides the same setting in a config file.If using the Docker CLI, add
--env-file .envto the command that runs Downdoot. For example:docker run -d --name downdoot -p 8080:8080 --env-file .env gitfield.org/fsky/downdoot:latestIf using Docker Compose, no changes are needed as the
.envfile is already referenced incompose.yamlvia theenv_file: .envline.Downdoot itself doesn't read a
.envfile: Docker, Compose and Quadlet pass its contents to it. Running the binary directly needs the variables set in the environment, or the settings in a config file.
Config file
Downdoot reads its config file from the first of these that exists:
- The file named by
--config <PATH>or by theDOWNDOOT_CONFIGenvironment variable. Downdoot refuses to start if it can't read that file. downdoot.scfgin the working directory./etc/downdoot/downdoot.scfg.
Without a config file, every setting has its default.
downdoot.example.scfg is a complete example: every setting, set to its default, each with a comment on what it does. Copy it and delete what you don't change:
sudo mkdir -p /etc/downdoot
sudo cp downdoot.example.scfg /etc/downdoot/downdoot.scfg
A config file that changes only a few things can be as short as this:
instance {
banner "Hosted by me. Be nice."
full-url https://downdoot.example
enable-rss on
}
cache {
media on {
dir /var/cache/downdoot
max-bytes 5GiB
}
}
defaults {
theme dracula
subscriptions rust linux
}
The format is scfg, a small language made for config files:
- A line is one setting: its name, then its value. A line ending in
{opens a block of related settings, and a line with only}closes it. - A setting is named by the blocks around it and its own name, joined by dots:
ttlincache { media { ttl 7d } }iscache.media.ttl. That is how the tables below,--check-configand error messages refer to it. - A setting that switches something on or off, or picks a mode, can be followed by a block of options for it, like
media on { ... }above. Every line starts with the name of one setting or block, socache media on {doesn't mean the same thing: it is a setting namedcache. - Comments are whole lines starting with
#. A#after a setting isn't a comment, it is read as one more value. - A value with spaces goes in
"double quotes"(use\"for a quote inside them) or'single quotes', which take everything literally. A value of""is the same as not setting it. on/offsettings also taketrue/falseandyes/no.- A size is a number with
KiB,MiB,GiBorTiB:64MiB. A duration is a number withs,m,hord:30s,7d. A bare number is bytes or seconds. Environment variables take these as well, for exampleDOWNDOOT_CACHE_MEMORY_TTL=5m. - A list is several words:
subscriptions rust linux. In an environment variable, join them with+:rust+linux. - A setting can appear once. Settings you leave out keep their defaults.
In containers and services
Downdoot looks in /etc/downdoot/ inside a container too, so mount the file there. It has to be readable by the container's user, which is nobody in compose.yaml:
docker run -d --name downdoot -p 8080:8080 -v "$PWD/downdoot.scfg:/etc/downdoot/downdoot.scfg:ro" gitfield.org/fsky/downdoot:latest
compose.yaml and downdoot.container have the equivalent lines, commented out. The systemd service reads /etc/downdoot/downdoot.scfg as it is.
Checking the configuration
downdoot --check-config loads the configuration the way a start would, prints what every setting ended up as, and exits. It exits with an error and the list of problems if the configuration isn't valid. With the file above and DOWNDOOT_SFW_ONLY=on set:
$ downdoot --check-config
Config file: /etc/downdoot/downdoot.scfg
Environment variables override the file. Command-line flags override both.
...
instance.sfw-only on env DOWNDOOT_SFW_ONLY
instance.banner Hosted by me. Be nice. file
instance.robots-disable-indexing off default
instance.pushshift-frontend undelete.pullpush.io default
instance.enable-rss on file
instance.full-url https://downdoot.example file
...
cache.media on file
cache.media.dir /var/cache/downdoot file
cache.media.max-bytes 5GiB file
cache.media.max-object-bytes 16MiB default
cache.media.ttl 7d default
...
A problem looks like this, with the line it is on:
Downdoot can't start, its configuration isn't valid:
downdoot.scfg:3: unknown setting `instance.full-ulr`; did you mean `instance.full-url`?
downdoot.scfg:5: unknown setting `theme`; did you mean `defaults.theme`?
DOWNDOOT_ENABLE_RSS: invalid value "2", expected on or off
Command Line Flags
Downdoot supports the following command line flags. -a and -p override the listen.address and listen.port settings of the config file, and the environment variables that go with them:
-a,--address <ADDRESS>: Sets address to listen on. Default is[::], every address over IPv4 and IPv6 where the system allows it; use0.0.0.0to listen on IPv4 only. Setting:listen.address, environment variableADDRESS.-p,--port <PORT>: Port to listen on. Default is8080. Setting:listen.port, environment variablePORT.--config <PATH>: Reads the config file atPATHinstead of looking fordowndoot.scfg.--check-config: Prints every setting with its value and where the value came from, then exits.-h,--help: Prints a summary of the flags, then exits.-V,--version: Prints the version, then exits.
The config file also sets listen.hsts, which has no flag or environment variable: how long browsers are told to use HTTPS only, as a duration like 7d (default 7d, 0 turns it off). It only has an effect behind a reverse proxy that serves HTTPS.
Instance settings
Assign a default value for each instance-specific setting in the config file, under the key shown, or by passing an environment variable to Downdoot in the format DOWNDOOT_{X}. Replace {X} with the name (see list below) in capital letters.
| Name | File key | Possible values | Default value | Description |
|---|---|---|---|---|
SFW_ONLY |
instance.sfw-only |
["on", "off"] |
off |
Enables SFW-only mode for the instance, i.e. all NSFW content is filtered. |
BANNER |
instance.banner |
String | (empty) | Allows the server to set a banner to be displayed. Currently this is displayed on the instance info page. |
ROBOTS_DISABLE_INDEXING |
instance.robots-disable-indexing |
["on", "off"] |
off |
Disables indexing of the instance by search engines. |
PUSHSHIFT_FRONTEND |
instance.pushshift-frontend |
String | undelete.pullpush.io |
Allows the server to set the Pushshift frontend to be used with "removed" links. |
ENABLE_RSS |
instance.enable-rss |
["on", "off"] |
off |
Enables RSS feed generation. |
FULL_URL |
instance.full-url |
String | (empty) | Allows for proper URLs (for now, only needed by RSS) |
REDDIT_ONION |
onion |
["off", "fallback", "only"] |
off |
Fetches from Reddit's onion service: fallback retries over onion when Reddit blocks or rate limits a clearnet request, only never uses clearnet. See Reddit onion service. |
ONION_PROXY |
onion.proxy |
URL | socks5h://127.0.0.1:9050 |
SOCKS proxy used to reach onion services (Tor). Must be socks5h so that the proxy resolves .onion names. |
VIDEO_MERGE |
video.merge |
["off", "client", "server", "both"] |
client |
Where a video's picture and sound are joined for downloads: in the visitor's browser (client), on this instance (server), either, with visitors choosing (both), or nowhere (off). See Video downloads with sound. |
MERGE_MAX_BYTES |
video.merge.max-bytes |
Size | 256MiB |
Largest video or audio file that server merging accepts. Larger ones are refused with HTTP 413. |
MERGE_MAX_CONCURRENT |
video.merge.max-concurrent |
Integer | 2 |
Merges the instance runs at once. Further requests get HTTP 503 until one finishes. |
CACHE_MEMORY |
cache.memory |
["on", "off"] |
on |
Caches Reddit responses, comment trees and compressed pages in memory. off caches nothing. See Memory cache. |
CACHE_MEMORY_MAX_BYTES |
cache.memory.max-bytes |
Size | 64MiB |
Memory the cache may use for Reddit data (three quarters) and compressed pages (a quarter). Small lookups add at most 2 MiB. |
CACHE_MEMORY_TTL |
cache.memory.ttl |
Duration | 30s |
How long Reddit data is served from memory before it is fetched again. |
CACHE_MEMORY_LONG_TTL |
cache.memory.long-ttl |
Duration | 30m |
How long what rarely changes is kept: short link targets, video audio files, RedGifs URLs and compressed pages. |
CACHE_MEDIA |
cache.media |
["on", "off"] |
off |
Keeps images, thumbnails, emoji and other small media on disk. Needs a writable directory. See Media cache. |
CACHE_MEDIA_DIR |
cache.media.dir |
Path | $CACHE_DIRECTORY, else downdoot-cache |
Where the media cache keeps its files. Downdoot refuses to start if it can't write there. |
CACHE_MEDIA_MAX_BYTES |
cache.media.max-bytes |
Size | 1GiB |
Disk space the media cache may use. The least recently used files are deleted first. |
CACHE_MEDIA_MAX_OBJECT_BYTES |
cache.media.max-object-bytes |
Size | 16MiB |
Largest file the media cache keeps. Bigger ones are still served, just not stored. |
CACHE_MEDIA_TTL |
cache.media.ttl |
Duration | 7d |
How long a cached file is served before it is fetched from Reddit again. |
Sizes and durations are written like 64MiB and 7d, a bare number being bytes or seconds. The settings for where Downdoot listens are described under Command Line Flags.
Memory cache
Downdoot keeps recent Reddit responses in memory, so a popular page is fetched from Reddit once per CACHE_MEMORY_TTL
instead of once per visitor. Visitors who ask for the same uncached page at the same moment share one request to Reddit.
- The size limit is a byte budget, estimated from the data held, not a count of entries. Once it is full, the least useful entries are dropped first. Expired entries are released within a minute even when the instance is idle.
- Failed requests are never cached.
- Media (images, thumbnails, video) is never kept in memory. See Media cache for keeping it on disk.
- Raise
CACHE_MEMORY_TTLto ask Reddit less often, at the cost of showing older posts and comments. LowerCACHE_MEMORY_MAX_BYTESon small machines, or setCACHE_MEMORYtooffto cache nothing.
Responses that involved a cache carry a Downdoot-Cache header, so you can see what a request cost:
| Value | Meaning |
|---|---|
HIT |
Everything came from the cache (memory, or disk for media); Reddit wasn't contacted. |
MISS |
Everything was fetched from Reddit, and stored if it qualified. Includes failed fetches. |
PARTIAL |
Some came from memory and some from Reddit, e.g. a cached post with fresh comments. |
COALESCED |
Another visitor's request was already fetching it, and this one waited for the answer. |
BYPASS |
A cache is off, or couldn't be used for this request, e.g. a Range request for media that isn't cached yet. |
Responses that never consult a cache, such as static files, video streams and unknown routes, have no header.
Media cache
Without it, every image, preview, thumbnail, emoji and award is fetched from Reddit each time a visitor's browser asks
for it. With CACHE_MEDIA on, Downdoot keeps them on disk and serves repeats itself. It matters most on busy
instances, where the same thumbnails appear in many visitors' feeds. It is off by default because it needs a directory
Downdoot can write to.
- Where files go.
CACHE_MEDIA_DIR, or the directory systemd provides:contrib/systemd/downdoot.servicehasCacheDirectory=downdoot, so under systemd nothing needs configuring. Otherwise it isdowndoot-cachein the working directory. Downdoot checks it at startup and refuses to run if it can't write there. - Containers. The images ship without a writable directory, and
compose.yamlruns read-only. Mount a volume and pointCACHE_MEDIA_DIRat it, and make sure the container's user can write to it (compose.yamlruns asnobody, uid 65534, sochown 65534:65534a bind-mounted directory). Atmpfsworks too, but it is memory, not disk. - What is kept. Only complete
200responses that aren't web pages, aren't markedno-store,no-cacheorprivate, and fit inCACHE_MEDIA_MAX_OBJECT_BYTES. Video, audio, HLS and RedGifs are never cached. - Space and age. Once
CACHE_MEDIA_MAX_BYTESis used up, the least recently used files are deleted. Files older thanCACHE_MEDIA_TTLare fetched again, and deleted within ten minutes if nobody asks for them. - Restarts. Files survive them. Downdoot indexes the directory at startup, which takes a moment for very large caches. Anything half-written when it stopped is discarded, and so is a file that is truncated or doesn't match its recorded length when it is next requested. (The contents aren't checksummed.)
- Range requests. Cached files answer them. A
Rangerequest for a file that isn't cached yet goes straight to Reddit and isn't stored, since a part of a file is no use later. - Limits worth knowing. Two visitors asking for the same new file at the same moment each fetch it from Reddit, but
it is only written once. A different query string is a different file, so someone can request made-up variants to push
other files out. They can't use more than
CACHE_MEDIA_MAX_BYTES, but if your instance is exposed to that, put rate limiting in front of it.
Default user settings
Assign a default value for each user-modifiable setting in the defaults block of the config file, under the key shown, or by passing an environment variable to Downdoot in the format DOWNDOOT_DEFAULT_{Y}. Replace {Y} with the name (see list below) in capital letters. Visitors start with these until they change them in their settings.
| Name | File key | Possible values | Default value |
|---|---|---|---|
THEME |
defaults.theme |
["system", "light", "dark", "black", "redlibDark", "redlibLight", "redlibBlack", "dracula", "nord", "laserwave", "violet", "gold", "rosebox", "gruvboxdark", "gruvboxlight", "tokyoNight", "icebergDark", "doomone", "midnightPurple", "libredditBlack", "libredditDark", "libredditLight", "catppuccinLatte", "catppuccinFrappe", "catppuccinMacchiato", "catppuccinMocha"] |
system |
FRONT_PAGE |
defaults.front-page |
["default", "popular", "all"] |
default |
LAYOUT |
defaults.layout |
["card", "clean", "compact"] |
card |
WIDE |
defaults.wide |
["on", "off"] |
off |
POST_SORT |
defaults.post-sort |
["hot", "new", "top", "rising", "controversial"] |
hot |
COMMENT_SORT |
defaults.comment-sort |
["confidence", "top", "new", "controversial", "old"] |
confidence |
BLUR_SPOILER |
defaults.blur-spoiler |
["on", "off"] |
off |
SHOW_NSFW |
defaults.show-nsfw |
["on", "off"] |
off |
BLUR_NSFW |
defaults.blur-nsfw |
["on", "off"] |
off |
USE_HLS |
defaults.use-hls |
["on", "off"] |
off |
HIDE_HLS_NOTIFICATION |
defaults.hide-hls-notification |
["on", "off"] |
off |
AUTOPLAY_VIDEOS |
defaults.autoplay-videos |
["on", "off"] |
off |
SUBSCRIPTIONS |
defaults.subscriptions |
List of subreddits: sub1 sub2 sub3 in the config file, +-delimited (sub1+sub2+sub3+...) in an environment variable |
(none) |
FILTERS |
defaults.filters |
List of subreddits to hide, written like SUBSCRIPTIONS |
(none) |
HIDE_AWARDS |
defaults.hide-awards |
["on", "off"] |
off |
DISABLE_VISIT_REDDIT_CONFIRMATION |
defaults.disable-visit-reddit-confirmation |
["on", "off"] |
off |
HIDE_SCORE |
defaults.hide-score |
["on", "off"] |
off |
HIDE_SIDEBAR_AND_SUMMARY |
defaults.hide-sidebar-and-summary |
["on", "off"] |
off |
FIXED_NAVBAR |
defaults.fixed-navbar |
["on", "off"] |
on |
REMOVE_DEFAULT_FEEDS |
defaults.remove-default-feeds |
["on", "off"] |
off |
DOWNLOAD_MERGE |
defaults.download-merge |
["auto", "browser", "instance"] |
auto |
DOWNLOAD_MERGE only applies where the instance's VIDEO_MERGE is both; see below. A visitor's choice is kept in a cookie but is not part of exported settings or restore links, so that settings exported by older versions keep importing.
Video downloads with sound
Reddit stores a video's picture and sound in two separate files, so downloading the video file alone gives a silent clip. When a post's video has sound, Downdoot merges the two files into one MP4. Nothing is re-encoded: the files are Reddit's own fragmented MP4s and are only repackaged, so quality is untouched.
DOWNDOOT_VIDEO_MERGE decides where that happens:
client(default): in the visitor's browser, with a small script (/mergeVideo.js). The instance only proxies the two files it already proxies for playback, and does no extra work. Without JavaScript the download link is the video alone, and the post offers a separateaudiolink.server: on the instance, at/vid/<id>/merged/<dash|cmaf>/<height>.mp4. Works without JavaScript. The merge is streamed with one fragment per file in memory, but it still costs a Reddit fetch and an upload per download, so it is limited byDOWNDOOT_MERGE_MAX_CONCURRENTandDOWNDOOT_MERGE_MAX_BYTES.both: visitors choose withdownload_mergein their settings (browser,instance, orauto, which uses the browser when JavaScript is available). The instance link is also the fallback when the browser can't merge, e.g. for a file too large to hold in memory.off: downloads stay video-only, with the separateaudiolink.
Merged files are fragmented MP4. Current players and browsers play them; a few older tools expect a regular MP4. Posts without sound, such as GIFs, are unaffected.
Forward Proxies
Downdoot supports proxy usage using the standard HTTP_PROXY and
HTTPS_PROXY environment variables. Use ALL_PROXY to set both at the same time (which you want to do).
http://is the scheme for http proxyhttps://is the scheme for https proxysocks4://is the scheme for socks4 proxysocks4a://is the scheme for socks4a proxysocks5://is the scheme for socks5 proxysocks5h://is the scheme for socks5h proxy
Reddit onion service
Reddit's clearnet API blocks or rate limits some servers. Reddit also runs onion services, which Downdoot can fetch
from through Tor. Set DOWNDOOT_REDDIT_ONION to:
off(default): clearnet only.fallback: clearnet first. When Reddit answers a clearnet request with HTTP 403 or 429, an empty body, or can't be reached, the request is retried over onion, and API requests try onion first for the next minute. Media (images, video and thumbnails) always tries clearnet first, since Reddit's media hosts often keep answering when its API refuses the server, and onion is slow for large files. Answers like "private" or "quarantined" are not retried. Onion authentication happens on first use.only: onion only. Nothing is sent to Reddit over clearnet.
A Tor SOCKS proxy must be listening at DOWNDOOT_ONION_PROXY (default socks5h://127.0.0.1:9050). Only onion requests
use it; clearnet requests are unaffected (except by ALL_PROXY, as above). Onion requests are slower than clearnet
ones, and are allowed up to 90 seconds. A network that fails to authenticate is skipped for a minute rather than
retried by every request, and when every network fails the error page names each one's reason.
Each network has its own OAuth token and rate limit. Responses, comments, media (images, video and thumbnails)
and short links all work over onion. Reddit's onion API rejects paths ending in .json, so Downdoot requests the
equivalent extensionless path, and hostnames in onion responses are mapped back to their clearnet names.
Security
This project uses BoringSSL, built from source with patches from the wreq project. Certificates are validated against the embedded trust store from Mozilla.
License
Downdoot is free software, licensed under the GNU Affero General Public License, version 3 only. If you run a modified version for other people, the AGPL requires you to offer them its source code.
Downdoot is a fork of Redlib, which is a fork of Libreddit. The people who contributed to them and to Downdoot are listed in CREDITS.

