A Reddit front-end that feels like a native mobile app — private, adless, and installable.
Rettid is a fork of Redlib (itself descended from Libreddit). It keeps Redlib's privacy guarantees and adds the mobile experience Redlib deliberately doesn't ship.
Redlib is excellent at what it sets out to be: a fast, minimal, JavaScript-free Reddit proxy. That minimalism is a deliberate design constraint, and it means a few things are explicitly out of scope upstream — a mobile-app-style interface, offline support, and any HTTP API a JavaScript client could consume.
Rettid is where those things live. The objective: make a self-hosted Reddit front-end that's genuinely pleasant on a phone, works offline, and can serve as a backend for custom clients — without giving up the privacy properties that made Redlib worth using.
Concretely, that means three additions on top of upstream:
| Feature | Why | |
|---|---|---|
| 📱 | Voyager layout — a mobile-first skin with a bottom tab bar, sticky top bar, and card feed | Redlib's layouts are desktop-shaped; this one is built for thumbs |
| 📶 | Installable PWA — service worker, manifest, offline fallback, media caching | Add to home screen, browse on bad connections |
| 🔌 | Opt-in JSON API — allowlisted read-only Reddit passthrough | Build your own client without your own Reddit credentials |
Everything Redlib already did well is inherited unchanged: Rust, no tracking, server-side media proxying, strict CSP, and full compatibility with existing REDLIB_* configuration.
- 🚀 Fast — Rust, no client-side framework, no hydration step
- ☁️ Light — no ads, no trackers, no bloat; the classic layouts remain JavaScript-free
- 🕵 Private — every request, including media, is proxied server-side; the browser never contacts Reddit
- 🔒 Secure — strict Content Security Policy
- ⚙️ Drop-in — same env vars, same config file format, same routes
Note
Scope and status. This is a personally maintained fork for self-hosting. It isn't listed in the public Redlib instance directory, and the changes here aren't intended to be upstreamed as-is — they run counter to Redlib's no-JS philosophy by design. If you want vanilla Redlib, use upstream; it's the better choice for that. Container images are published to Docker Hub; prebuilt binaries are not.
- Why this fork exists
- The three additions
- Quick start
- Deployment
- Configuration
- Privacy
- Inherited from Redlib
- Building & security notes
A mobile-first layout inspired by Voyager for Lemmy and Apollo-era Reddit clients. This is Rettid's default — both the Docker images and contrib/rettid.conf ship with REDLIB_DEFAULT_LAYOUT=voyager and REDLIB_DEFAULT_THEME=voyagerDark out of the box, since it's the whole point of this fork.
What it changes:
- Bottom tab bar for primary navigation (Home / Popular / All / Search / Settings)
- Sticky, translucent top bar instead of a fixed desktop navbar
- Full-bleed media, card-style feed, iOS-like geometry and spacing
- Comment scores inline beside the author rather than in a left-hand column
- Safe-area (notch) awareness and
theme-colormatching, so it looks right installed
It is purely presentational. All CSS lives in static/voyager.css scoped to body.voyager, and the template changes are wrapped in {% if prefs.layout == "voyager" %} conditionals. Selecting card, clean, or compact gives you exactly upstream's rendering — this layout can't leak into them.
To get upstream's classic desktop look instead, either switch it per-user in Settings → Layout, or override the instance default:
REDLIB_DEFAULT_LAYOUT=card REDLIB_DEFAULT_THEME=systemTip
The tab bar only exists in the voyager layout. If you're on card and wondering where it went, that's expected.
The instance is installable and usable offline, regardless of which layout you pick. It serves a web app manifest (/manifest.json) and registers a service worker (/sw.js) with three caching strategies:
| Content | Strategy |
|---|---|
| App shell (CSS, JS, fonts, icons) | Cache-first, revalidated in the background |
Proxied media (/img, /thumb, /preview, …) |
Cache-first and treated as immutable, size-capped |
| HTML navigations | Network-first, falling back to /offline.html |
HTML is deliberately network-first: preferences live in cookies, so serving a stale page could show the wrong theme or feed. Settings and streaming media (/settings, /vid/, /hls/) are never cached. Cache names embed the crate version, so each release prunes its predecessors on activation.
No configuration needed — it's part of the base install. The cache is entirely client-side and clears like any other site data.
An opt-in, read-only passthrough to Reddit's JSON endpoints, so a JavaScript client can be built against your instance without needing its own Reddit credentials or rate limit.
REDLIB_ENABLE_JSON_API=on
# Optional: allow a separate dev frontend to call it
REDLIB_API_CORS_ORIGIN=http://localhost:5173GET /api/reddit/<reddit-path>[?query]
Requests are forwarded through the same internal client the HTML pages use, so they inherit the instance's OAuth token, rate-limit accounting, and retry behavior. The browser never talks to Reddit directly.
- Off by default. It exposes only public data the HTML frontend already renders, but it's far easier to script against and spends your instance's rate limit, so it's opt-in.
- Allowlisted, not an open proxy. Forwarded prefixes:
r/,user/,comments/,subreddits/,api/info,by_id/,search,duplicates/,api/morechildren. Traversal (..) and absolute paths are rejected. - CORS is off unless configured. Same-origin only by default;
OPTIONSpreflight is handled once an origin is set. - Raw responses. No translation is done server-side — mapping Reddit's shapes to your client's types is the client's job.
The motivating use case: the HTML templates render scores and timestamps as pre-formatted display strings, which is fine for a server-rendered page but useless to a JS client that needs the raw numbers.
git clone https://github.com/gbrunoo/rettid && cd rettid
cargo runThen open http://localhost:8080.
To see the Voyager skin from a bare cargo run — the Docker image sets this for you, but a local build doesn't inherit it — either switch it in Settings → Layout, or start with:
REDLIB_DEFAULT_LAYOUT=voyager REDLIB_DEFAULT_THEME=voyagerDark cargo runImportant
This project builds BoringSSL from source. See Building & security notes for prerequisites if the build fails.
Docker is the easiest path for a long-running instance.
Images are published to Docker Hub for linux/amd64 and linux/arm64:
docker run -d --name rettid -p 8080:8080 gab360/rettid:latestThen open http://localhost:8080.
With configuration, and bound to loopback for use behind a reverse proxy:
cp .env.example .env # then edit it
docker run -d --name rettid -p 127.0.0.1:8080:8080 --env-file .env gab360/rettid:latestTags: latest tracks main; X.Y.Z and X.Y are published for release tags;
sha-<commit> pins an exact build.
docker build -f Dockerfile.ubuntu -t rettid .
docker run -d --name rettid -p 8080:8080 rettidImportant
Which Dockerfile to use. Dockerfile.ubuntu builds natively on both amd64
and arm64 (including Apple Silicon) and is the safe default.
Dockerfile.alpine produces a much smaller static image but hardcodes
x86_64-unknown-linux-musl, so on arm64 it only works under emulation.
The plain Dockerfile doesn't build anything — it downloads a release
tarball, which this fork doesn't publish, so it won't work as-is.
Expect the first build to take a while: BoringSSL is compiled from source.
compose.yaml pulls the published image, reads .env, and falls back to
building locally if the image isn't available:
cp .env.example .env # edit as desired
docker compose up -d
docker compose logs -fA Quadlet unit is provided in rettid.container. Build the image first, then:
podman build -f Dockerfile.ubuntu -t rettid .
cp rettid.container .env.example ~/.config/containers/systemd/
systemctl --user daemon-reload
systemctl --user start rettid.service
systemctl --user status rettid.serviceImportant
Requires a systemd-based distro with Podman, plus loginctl enable-linger <username> so the service survives logout. The unit references localhost/rettid:latest — adjust if you tag differently.
After cargo build --release, the binary is target/release/rettid:
sudo install -o root -g root -m 755 target/release/rettid /usr/bin/rettid
rettidImportant
If proxying through NGINX, add proxy_http_version 1.1; above your proxy_pass line.
Install contrib/rettid.service to /etc/systemd/system/rettid.service, and optionally configure it via /etc/rettid.conf (template: contrib/rettid.conf).
If NGINX runs on the same host, make it wait for the service. In /etc/systemd/system/rettid.service.d/reverse-proxy.conf:
[Unit]
Before=nginx.servicegit clone https://github.com/gbrunoo/rettid && cd rettid
cargo build --releasecp contrib/rettid.plist ~/Library/LaunchAgents/
launchctl load ~/Library/LaunchAgents/rettid.plistEnvironment variables keep upstream's REDLIB_* prefix, so existing Redlib configs work unchanged:
REDLIB_DEFAULT_SHOW_NSFW=on rettid
REDLIB_DEFAULT_LAYOUT=voyager REDLIB_DEFAULT_THEME=voyagerDark rettidOr use a redlib.toml file:
REDLIB_DEFAULT_LAYOUT = "voyager"
REDLIB_DEFAULT_USE_HLS = "on"Note
With Docker CLI or Compose, copy .env.example to .env and edit it. Docker CLI: add --env-file .env. Compose already wires it up via env_file: .env.
-4,--ipv4-only— listen on IPv4 only-6,--ipv6-only— listen on IPv6 only-a,--address <ADDRESS>— address to bind. Default[::]-p,--port <PORT>— port to bind. Default8080-H,--hsts <EXPIRE_TIME>— HSTS max-age. Default604800-r,--redirect-https— no longer functional
Prefix each with REDLIB_:
| Name | Possible values | Default | Description |
|---|---|---|---|
SFW_ONLY |
["on", "off"] |
off |
Filter all NSFW content instance-wide |
BANNER |
String | (empty) | Banner shown on the instance info page |
ROBOTS_DISABLE_INDEXING |
["on", "off"] |
off |
Ask search engines not to index this instance |
PUSHSHIFT_FRONTEND |
String | undelete.pullpush.io |
Frontend used for "removed" post links |
PORT |
Integer 0-65535 | 8080 |
Internal listen port |
ENABLE_RSS |
["on", "off"] |
off |
Enable RSS feed generation |
FULL_URL |
String | (empty) | Public base URL; currently only needed for RSS |
ENABLE_JSON_API |
["on", "off"] |
off |
Expose the read-only JSON API at /api/reddit/* |
API_CORS_ORIGIN |
String | (empty) | Access-Control-Allow-Origin for the JSON API. Unset ⇒ same-origin only |
Prefix each with REDLIB_DEFAULT_. These set the default; users can override them in Settings.
Note
The "Default" column below is the binary's built-in fallback when nothing else is set — it's what you get from a bare cargo build/cargo run. Rettid's Docker images and contrib/rettid.conf set LAYOUT=voyager and THEME=voyagerDark explicitly, which take precedence over this table for anyone using them.
| Name | Possible values | Default |
|---|---|---|
THEME |
["system", "light", "dark", "black", "dracula", "nord", "laserwave", "violet", "gold", "rosebox", "gruvboxdark", "gruvboxlight", "tokyoNight", "icebergDark", "doomone", "libredditBlack", "libredditDark", "libredditLight", "voyagerDark", "voyagerLight"] |
system |
FRONT_PAGE |
["default", "popular", "all"] |
default |
LAYOUT |
["card", "clean", "compact", "voyager"] |
card |
WIDE |
["on", "off"] |
off |
POST_SORT |
["hot", "new", "top", "rising", "controversial"] |
hot |
COMMENT_SORT |
["confidence", "top", "new", "controversial", "old"] |
confidence |
BLUR_SPOILER |
["on", "off"] |
off |
SHOW_NSFW |
["on", "off"] |
off |
BLUR_NSFW |
["on", "off"] |
off |
USE_HLS |
["on", "off"] |
off |
HIDE_HLS_NOTIFICATION |
["on", "off"] |
off |
AUTOPLAY_VIDEOS |
["on", "off"] |
off |
SUBSCRIPTIONS |
+-delimited subreddits (sub1+sub2+sub3) |
(none) |
HIDE_AWARDS |
["on", "off"] |
off |
DISABLE_VISIT_REDDIT_CONFIRMATION |
["on", "off"] |
off |
HIDE_SCORE |
["on", "off"] |
off |
HIDE_SIDEBAR_AND_SUMMARY |
["on", "off"] |
off |
FIXED_NAVBAR |
["on", "off"] |
on |
REMOVE_DEFAULT_FEEDS |
["on", "off"] |
off |
Outbound requests honor the standard HTTP_PROXY / HTTPS_PROXY variables; ALL_PROXY sets both.
Supported schemes: http://, https://, socks4://, socks4a://, socks5://, socks5h://.
What Reddit collects. Per Reddit's privacy policy and cookie notice: IP address, user-agent, browser and OS, referral URLs, device IDs and settings, pages visited, links clicked, and search terms; location via GPS, Bluetooth, or IP where consented; and cookies for authentication, functionality, analytics, advertising, and third-party tracking.
What Rettid collects.
- Logging — nothing in release builds. Debug builds log fetched post IDs to aid troubleshooting.
- Cookies — first-party only, storing just the settings you choose. No personal data, never cross-site.
- PWA cache — static assets and already-proxied media, cached in your own browser. Nothing is sent to a third party; clear it like any other site data.
- Media — proxied through the server, so Reddit's CDNs never see your IP.
Rettid inherits all of this from upstream, unchanged.
Built with: Rust · Hyper (HTTP server) · wreq with BoringSSL (outbound requests) · Askama (templates)
How Redlib evades rate limits:
- OAuth token spoofing — mimics the official Android client's OAuth flow (iOS was tried, but anonymous iOS clients hit tighter content restrictions)
- Token refreshing — rotates the auth token every 24 hours, like the real app
- Header mimicking — forwards as many of the official app's headers as possible
Relationship to Libreddit: Redlib began as a Libreddit fork and was renamed for trademark reasons — Reddit only permits their name in the form "XYZ For Reddit." Same reasoning applies here, which is why this fork is "Rettid" rather than anything containing "reddit."
Find upstream Redlib on 💬 Matrix ·
GitHub · 🦊 GitLab, and its public instances in the redlib-instances repo.
Tip
🔗 To redirect Reddit links to your instance automatically, use LibRedirect or Privacy Redirect.
TLS goes through BoringSSL, built from source with patches from the wreq project. Certificates validate against an embedded Mozilla trust store.
Because boring-sys2 compiles BoringSSL, the build needs a C/C++ toolchain.
Linux / macOS — see the BoringSSL build docs for dependencies (typically cmake, perl, pkg-config, libclang).
Windows — MSVC is likely already installed with Rust; then:
# -i keeps installers interactive, since some don't update PATH on their own.
winget install -i Kitware.CMake
winget install -i NASM.NASM
winget install -i LLVM.LLVM
# For tests.
winget install GoLang.GoLicense: AGPL-3.0-only, same as upstream Redlib. See LICENSE and CREDITS.

