Features

Everything littleFedi does

One server, one binary. This page is the long version: what is in it, how it behaves, and where the edges are. Anything listed as missing at the bottom of the overview is missing here too.

Timelines

Home, local, federated, list, tag, direct and bubble. All of them are in time order, newest first, and they stay that way. No score decides what you see, nothing is inserted, nothing is hidden because an algorithm judged it dull.

Blocks, mutes and filters are applied when a timeline is read, so a filter you change now applies to the page you load next, everywhere, without a rebuild.

Long posts fold in timelines, search results, profiles, catch-up and thread replies: the body is clamped, the cut edge fades, and a Show more control opens it. The fold is CSS, not server-side truncation, so the whole text is always in the page. A reader without stylesheets or scripting gets everything. The post a thread is focused on is never folded.

Signed-out visitors land on the local timeline. Set public_local_timeline = false to send them to the sign-in page instead.

Catch-up

Chronological feeds have one real weakness: come back after three days and you scroll for twenty minutes. Catch-up is the answer to that, and it is deliberately a separate page at /catchup that you have to open yourself. It never leaks into a timeline.

It ranks posts your home feed already received, using signals this instance already stores:

How catch-up scores a post
SignalWeight
Each account you follow who boosted the post+2.5
Favourites delivered here (log)+1.0
Boosts delivered here (log)+0.8
Replies delivered here (log)+0.6
You interacted with the author in the last month+1.2
Carries a hashtag you follow+1.0
Continues a conversation you took part in+0.8
The post is a reply-1.0

The total halves every twelve hours since the post reached your feed. The interaction counts are this instance's own view, the activities actually delivered to it, not a popularity figure fetched from somewhere else. No remote service is consulted and no ranking data leaves the machine.

Rules that are not weights:

  • Your own posts are excluded, and so is anything you already favourited, bookmarked, boosted or replied to.
  • A post with no displayable reason is not listed at all. If the page cannot tell you why something is there, it does not show it.
  • At most two posts per author survive.
  • Twenty entries, then the page ends. No pagination, no infinite scroll.

The only thing stored is a catchup marker, written when you press Mark as read, and removed with the account like every other marker. Pressing it moves a divider rather than emptying the page: older entries stay listed under Already seen. Nothing about what you open, or how long you look at it, is recorded or learned from.

Set catchup = false under [web] and the page and its navigation entries disappear.

Posting and the composer

Posts, replies, boosts, favourites, bookmarks, polls, quotes, filters, scheduled posts, and self-expiring posts that delete themselves around a per-post timer.

The composer has three modes: plain text, Markdown, and a visual editor. The visual mode publishes Markdown underneath, so a post keeps an editable source and federates with the same text/markdown media type as one written by hand. Its toolbar covers headings, bold, italic, strikethrough, links, quotes, lists, inline and block code, horizontal rules, undo and redo. Tables stay available from Markdown source.

Preview uses the server's real renderer, not a second implementation that drifts from it. It shows the authored post with content warnings, selected media, sensitivity, polls and quotes. Local files are previewed in the browser and are not uploaded until you actually submit.

Without JavaScript

The textarea, the Markdown checkbox and ordinary form submission keep working. The visual editor is an addition to the composer, never a replacement for it.

Federation and cryptography

ActivityPub with WebFinger discovery, shared inboxes, relays, and a durable delivery queue that retries with backoff and survives a restart. Delivery is not best effort in memory: a job that has not been acknowledged is still there tomorrow.

HTTP Signatures
Outbound RSA (PKCS#1 v1.5) for compatibility with everything already deployed. Inbound RSA (rsa-sha256, rsa-sha512, hs2019) and Ed25519 (hs2019, ed25519). Verification uses the exact advertised key ID.
Actor keys
New local accounts carry an Ed25519 pair alongside their RSA pair. Legacy RSA stays in publicKey; Ed25519 is advertised separately as an assertionMethod Multikey with the required 0xed01 multicodec. Inbound publicKeyBase64 and Multikey encodings are normalized to PKIX PEM on ingest.
Object Integrity Proofs (FEP-8b32)
eddsa-jcs-2022 verification of inbound activities, always on, and accepted even when the HTTP Signature is absent or fails. Outbound signing is behind federation.sign_with_oip and off by default. Verified against the official FEP-8b32 test vector.
Legacy Linked Data Signatures
RsaSignature2017 is still verified as a fallback for forwarded activities.

Peer and client compatibility is recorded rather than claimed. The compatibility matrix is a ledger with pending rows, and a pending row is a release blocker, not an implied promise.

Accounts and sign-up

Single-user and multi-user instances from the same binary. Which one you get is a decision littlefedi init asks about, not a different build.

  • Invitations, manual approval, verified email, and TOTP multi-factor authentication with recovery codes.
  • require_admin_mfa refuses to let an administrator work without a second factor.
  • Lists, follow requests, hashtag follows, featured tags, and per-account preferences.
  • Web Push notifications, plus opt-in administrator email alerts for new accounts and for signups that used a specific administrator's invite.

Registration is closed by default. That is the correct default for a server nobody is moderating yet.

Moderation and administration

Everything an operator needs is in the admin console, and almost all of it is also a CLI command, so a locked-out administrator with shell access is never stuck.

  • Reports, audit logs, domain blocks, account suspension, silencing and disabling.
  • User administration from the shell: create, list, show, approve, reject, suspend, unsuspend, silence, unsilence, disable, enable, delete, reset a password, set an email, disable MFA, refresh remote actors.
  • Relay management, invitations, media administration, and a queue view that deliberately omits payload bodies.
  • An SMTP test that only ever sends to the current administrator's own address.
  • An effective-configuration page, so "what is this server actually running" is a question with an answer.
littlefedi --config littlefedi.toml admin create-user --username alice --admin
littlefedi --config littlefedi.toml admin list-users
littlefedi --config littlefedi.toml admin reset-password --username alice
littlefedi --config littlefedi.toml admin domain-block add example.invalid

The instance profile

What a server says about itself lives in one place, /admin/instance, and changes apply without a restart.

One form, many surfaces
FieldWhere it appears
Short descriptionServer directories, NodeInfo, the header of /about
DescriptionThe top of /about, GET /api/v1/instance
Extended description/about in full, GET /api/v1/instance/extended_description
Rules/about, the registration form, GET /api/v1/instance/rules
Moderation contact/about, the email field of both instance APIs
Terms of service/terms, GET /api/v1/instance/terms_of_service

The extended description and the terms are Markdown, rendered and sanitized before publication. Leave the terms empty and this server publishes none: /terms and its API endpoint answer 404, so clients hide the link rather than opening an empty page.

The same fields exist in the [instance] section of the config file, which init offers to fill in. The config file is the seed and the admin console is the override; the console writes to the database rather than back to the file. Pick one of the two and stay there.

One exception worth knowing

Rules do not follow the per-field fallback. Saving the profile even once makes the stored rules authoritative, including an empty list, so an administrator who clears the box publishes no rules instead of silently getting the configured ones back.

Media

Local disk or an S3-compatible object store, decided by a build tag and a configuration key. Two size knobs do different jobs and are easy to confuse:

[media]
max_file_size = 41943040    # bytes accepted per upload (default 40 MB)
max_stored_dimension = 2560 # downscale stored JPEG/PNG to this longest side
max_image_pixels = 0        # reject images decoding to more than this (0 = no limit)

max_stored_dimension is what keeps the media directory small. Uploads are downscaled once, on arrival, and every later read moves the smaller file: federation fetches, cache warming, backups. It applies to cached remote media too, and it is destructive, because no full-resolution copy is kept. Animated GIFs, WebP, video and audio are never re-encoded.

max_image_pixels bounds decoded memory instead, which a file-size limit cannot: a small compressed file can declare an enormous canvas and decode into gigabytes. Downscaling does not replace that bound, because an image must be decoded in full before it can be shrunk.

Owned media URLs are bearer URLs

Files under /media/ use unguessable public URLs, the same way Mastodon does, so images embedded in federated posts render without a littleFedi login. Status pages, API metadata and federation objects still enforce visibility, but anyone who obtains a raw media URL can fetch that file.

API, clients and streaming

A Mastodon-compatible OAuth flow and REST API, WebSocket and SSE streaming, and Web Push. Existing Mastodon clients connect and work. The legacy flow without PKCE is still supported, because some clients still need it.

Discovery endpoints for trends, suggestions and endorsements return empty results on purpose. Featured tags are implemented. Remote custom emoji are parsed and rendered; local custom-emoji upload and management is not implemented, so this server emits no local /emojis/{id} objects.

Your data: export, import, exit

An account can export itself, posts and media included, and import that archive into another littleFedi instance, along with its preferences, its custom CSS and its blog stylesheet. Moving on should not mean deleting your history. Account migration and account deletion are supported, and deletion emits signed activities that other servers actually receive, as long as you keep the queue workers running while decommissioning.

There is also an instance self-destruct command. A server that cannot be shut down cleanly leaves debris on every peer that ever heard of it, and that is somebody else's problem you created.

An officially supported one-off path exists for moving a single-user Akkoma or Pleroma instance onto littleFedi on the same domain, keeping the actor URI and the RSA key pair so that remote servers never notice the switch. It carries the identity, the profile, the followers and the following list. It does not carry old posts or media.

Storage and build variants

Optional dependencies are kept out of the default binary by build tags, so the server you run on a small machine does not carry a PostgreSQL driver it will never open.

Build variants
CommandTagAdds
make buildnoneEmbedded SQLite, local media. The default.
make build-postgrespostgresPostgreSQL storage and cross-process streaming
make build-s3s3S3-compatible media storage and migration
make build-postgres-s3bothThe clustered profile
make build-blogblogStatic blog generation

Interface, languages, accessibility

The web interface is rendered on the server. JavaScript improves it and is never required by it: the composer, the timelines, the settings pages, the moderation tools and the admin console all work with scripting off.

  • Ten themes, four of which dress the interface as a native application on macOS, Plasma, GNOME or Xfce, plus light, dark, true black and automatic modes, and a per-account custom stylesheet. See Theming.
  • Six interface languages: English, German, Spanish, French, Italian and Portuguese. A translation is not merged half-finished, because a completeness test fails.
  • Built-in themes are contrast-tested in the repository: text and muted text against every surface they can land on.

Operations

  • /health reports process health, /readyz checks readiness including storage, and /metrics exposes Prometheus metrics when enabled.
  • The process logs structured lines to stdout and lets the service manager decide where they go. Nothing in littleFedi assumes systemd, or syslog, or SMF.
  • Built-in backups of the database, the configuration and, optionally, local owned media, with a manifest and a restore command.
  • Housekeeping, remote media sweeps and periodic backups run on one designated process, so a cluster does not need a distributed election to decide who vacuums.
  • Written runbooks for failed migrations, full disks, stuck queues, compromised keys, spam bursts, SMTP outages, media outages and database outages.

Known limits

  • Trends, suggestions and endorsements are intentionally empty.
  • Local custom emoji cannot be uploaded or managed.
  • Blog generation is process-local. One process only.
  • There is no general read-only mode. Closing registration and rejecting write paths at the edge is the documented substitute.
  • The application rate limiter is per process, so a multi-process deployment needs a shared limit at the reverse proxy.
  • The built-in media backup covers local owned media only. With media.backend = "s3", asking for include_media = true is rejected rather than producing an incomplete backup.