kruno 60d2b872c1
Pull Request / cargo test (push) Waiting to run
Pull Request / cargo test (live Reddit) (push) Waiting to run
Pull Request / cargo fmt --all -- --check (push) Waiting to run
Pull Request / cargo clippy -- -D warnings (push) Waiting to run
Regenerate CREDITS
2026-10-26 13:23:23 +01:00
2026-10-25 15:44:25 +01:00
2026-10-26 11:59:27 +01:00
2026-10-26 11:19:39 +01:00
2026-10-26 12:16:14 +01:00
2024-05-31 17:10:36 +04:00
2026-10-26 13:23:21 +01:00
2026-10-26 12:25:24 +01:00
2026-10-26 12:25:24 +01:00
2026-10-26 13:23:23 +01:00
2026-10-25 15:44:25 +01:00
2026-10-25 15:44:25 +01:00
2025-02-02 23:16:59 -05:00
2020-10-25 13:48:44 -07:00
2026-10-26 13:23:21 +01:00
2026-10-26 13:23:21 +01:00

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

  1. Downdoot
  2. Instances
  3. About
  4. Comparison
  5. Deployment
  6. Configuration
  7. Security
  8. License

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 subreddit feed in the dark theme

A post and its comments, in the light theme:

A post with its comments in the light theme

The same feed on a phone:

A subreddit feed on a phone

Built with

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_id and comments through Reddit's Android PostComments operation, 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

Reddit

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_pass line.

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:

  1. command line flags
  2. environment variables
  3. the config file
  4. 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 .env file, allowing you to centralize and manage configuration in one place.

To configure Downdoot using a .env file, copy the .env.example file to .env and 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 .env to the command that runs Downdoot. For example:

docker run -d --name downdoot -p 8080:8080 --env-file .env gitfield.org/fsky/downdoot:latest

If using Docker Compose, no changes are needed as the .env file is already referenced in compose.yaml via the env_file: .env line.

Downdoot itself doesn't read a .env file: 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:

  1. The file named by --config <PATH> or by the DOWNDOOT_CONFIG environment variable. Downdoot refuses to start if it can't read that file.
  2. downdoot.scfg in the working directory.
  3. /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: ttl in cache { media { ttl 7d } } is cache.media.ttl. That is how the tables below, --check-config and 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, so cache media on { doesn't mean the same thing: it is a setting named cache.
  • 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/off settings also take true/false and yes/no.
  • A size is a number with KiB, MiB, GiB or TiB: 64MiB. A duration is a number with s, m, h or d: 30s, 7d. A bare number is bytes or seconds. Environment variables take these as well, for example DOWNDOOT_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; use 0.0.0.0 to listen on IPv4 only. Setting: listen.address, environment variable ADDRESS.
  • -p, --port <PORT>: Port to listen on. Default is 8080. Setting: listen.port, environment variable PORT.
  • --config <PATH>: Reads the config file at PATH instead of looking for downdoot.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_TTL to ask Reddit less often, at the cost of showing older posts and comments. Lower CACHE_MEMORY_MAX_BYTES on small machines, or set CACHE_MEMORY to off to 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.service has CacheDirectory=downdoot, so under systemd nothing needs configuring. Otherwise it is downdoot-cache in 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.yaml runs read-only. Mount a volume and point CACHE_MEDIA_DIR at it, and make sure the container's user can write to it (compose.yaml runs as nobody, uid 65534, so chown 65534:65534 a bind-mounted directory). A tmpfs works too, but it is memory, not disk.
  • What is kept. Only complete 200 responses that aren't web pages, aren't marked no-store, no-cache or private, and fit in CACHE_MEDIA_MAX_OBJECT_BYTES. Video, audio, HLS and RedGifs are never cached.
  • Space and age. Once CACHE_MEDIA_MAX_BYTES is used up, the least recently used files are deleted. Files older than CACHE_MEDIA_TTL are 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 Range request 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 separate audio link.
  • 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 by DOWNDOOT_MERGE_MAX_CONCURRENT and DOWNDOOT_MERGE_MAX_BYTES.
  • both: visitors choose with download_merge in their settings (browser, instance, or auto, 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 separate audio link.

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 proxy
  • https:// is the scheme for https proxy
  • socks4:// is the scheme for socks4 proxy
  • socks4a:// is the scheme for socks4a proxy
  • socks5:// is the scheme for socks5 proxy
  • socks5h:// 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.

S
Description
A privacy-respecting alternative front-end for Reddit
https://downdoot.com/
Readme AGPL-3.0
5.3 MiB
0 Stars 2 Watchers 0 Forks
Languages
Rust 75.2%
HTML 9.9%
CSS 6.7%
JavaScript 5.2%
Dockerfile 1.2%
Other 1.8%