Scaling Lemmy Server Nodes: A Step-by-Step Setup Guide

Running a single-node Lemmy deployment is simple, but as traffic expands across the fediverse, splitting workloads across distinct processes and server nodes helps keep your community online. Distributing services allows rolling upgrades without downtime, keeps instances accessible if an individual machine fails, and provides architectural flexibility. In fact, as of Lemmy version 0.18.2, Lemmy appears to handle system resources more efficiently when workloads are split between multiple Lemmy processes. This guide details how to separate backend workers, federation queues, web requests, and media caching according to the Lemmy Horizontal Scaling documentation.
Step 1: Isolate Background Scheduled Tasks onto a Single Node
By default, every Lemmy process attempts to run internal scheduled tasks. If you launch multiple server processes without adjusting this setting, duplicate background tasks will trigger at the exact same moment and fight over identical database records. To prevent this, scheduled operations must run on exactly one server process across your entire cluster.
You have two architectural options to handle this constraint:
- Run every load-balanced web node with scheduled tasks disabled, and spin up one standalone worker server that handles scheduled jobs but takes zero incoming HTTP traffic.
- Permit exactly one load-balanced web node to execute scheduled tasks, while disabling scheduled tasks across every other web node.
To disable scheduled tasks on your general nodes, pass the --disable-scheduled-tasks CLI flag or set the environment parameter LEMMY_DISABLE_SCHEDULED_TASKS=true. When creating a dedicated background worker container that handles only scheduled tasks, disable both external HTTP serving and outgoing federation by setting LEMMY_DISABLE_HTTP_SERVER=true and LEMMY_DISABLE_ACTIVITY_SENDING=true (or using CLI flags --disable-http-server --disable-activity-sending).
Step 2: Partition Federation Delivery Across Distinct Worker Processes
Since Lemmy 0.19, outbound activity delivery relies on a persistent federation queue organized by federated domain. This workload can be partitioned into equal segments across independent worker processes. If you choose to run three dedicated federation workers, configure them with specific index and count values so each domain is processed by only one worker.
Launch each federation worker by configuring the count and unique index values:
- Worker 1:
--disable-http-server --disable-scheduled-tasks --federate-process-index=1 --federate-process-count=3(or environment variablesLEMMY_DISABLE_HTTP_SERVER=true,LEMMY_DISABLE_SCHEDULED_TASKS=true,LEMMY_FEDERATE_PROCESS_INDEX=1, andLEMMY_FEDERATE_PROCESS_COUNT=3). - Worker 2: Use matching count parameters with
--federate-process-index=2(orLEMMY_FEDERATE_PROCESS_INDEX=2). - Worker 3: Use matching count parameters with
--federate-process-index=3(orLEMMY_FEDERATE_PROCESS_INDEX=3).
It is vital that each index from 1 to N is assigned to exactly one process. Reusing or skipping indices causes undefined behavior, including duplicate activities, missing federation events, or process crashes. These federation workers can be started or stopped at will; after any interruption, workers resume delivering federation data from the last transmitted activity.
Step 3: Deploy Dedicated HTTP Backend Nodes
Once federation workers and scheduled jobs are separated, configure your API nodes to serve user traffic without managing background workloads. For each backend web process, ensure federation sending and scheduled jobs are turned off. Start each node with --disable-activity-sending --disable-scheduled-tasks or set LEMMY_DISABLE_ACTIVITY_SENDING=true and LEMMY_DISABLE_SCHEDULED_TASKS=true.
If you prefer a simpler infrastructure layout rather than maintaining independent load-balancing tiers, you can deploy NGINX, lemmy-ui, and lemmy_server together on each server, and point an external load balancer to the local NGINX proxy on each node. Learn more about containerized foundational layouts in our guide to deploying Lemmy with Docker Compose.
Step 4: Decouple Lemmy-UI and Manage Rolling Upgrades
Running multiple Lemmy-ui nodes behind a load balancer introduces risks during version rollouts. By default, Lemmy-ui serves static files through Express. If a visitor receives initial HTML from a server running version 1, that HTML references assets built specifically for that version. If the browser's subsequent asset requests route to a server already running version 2, the old files will not exist, resulting in 404 errors that break the user interface.
To safely upgrade without downtime, configure your reverse proxy to route static requests to an external object storage bucket that retains files for every active deployment. You can upload the contents of the lemmy-ui dist/ directory to a bucket path named static/$LEMMY_UI_HASH, calculating the hash via export LEMMY_UI_HASH=$(git rev-parse --short HEAD). In NGINX, map the static route using this structure:
location /static {
expires 1y;
add_header Cache-Control "public";
proxy_pass https://${bucket_fqdn};
limit_except GET {
deny all;
}
proxy_intercept_errors on;
error_page 403 =404 /@404static;
}
Because this object storage configuration requires public access on static files, restrict bucket modifications using an IP allowlist policy. If you do not offload assets to an object store, you can instead configure sticky sessions or IP-hash routing in your load balancer so user requests consistently hit the same UI instance during rollout windows.
Step 5: Cache Media to Protect Pict-rs
Unlike Lemmy's application processes, the media service pict-rs does not scale horizontally. It relies on Sled, an on-disk embedded database file, preventing concurrent access across nodes. Although the pict-rs developer has noted plans to add PostgreSQL support for multi-node deployments in the future, active development on that capability has not started.
Because pict-rs remains bound to a single node, you must shield it by caching images aggressively through NGINX or an edge CDN. Caching minimizes direct read traffic to the Sled database and prevents media downloads from overwhelming instance resources. Review our Lemmy platform overview for additional architectural context.
Step 6: Configure Real IP Headers and Calibrate Rate Limits
Lemmy includes an internal rate limiter that stores client IP addresses in memory. When distributing traffic across a reverse proxy or load balancer, ensure the real client IP is passed downstream; otherwise, requests will reflect the proxy's IP address and trigger frequent false blocks. In NGINX, define your load balancer IP and configure IP forwarding:
real_ip_recursive on;
real_ip_header X-Forwarded-For;
set_real_ip_from <your load balancer ip here>;
Because Lemmy tracks IP addresses in local memory on each node rather than a shared database, adding more Lemmy backend servers distributes the tracked request counts and makes built-in rate limits less strict overall. Adjust your instance limit policies to account for the total number of running backend nodes.
Finally, exercise caution when performing rolling upgrades across versions. Database migrations can occasionally contain backwards-incompatible schema changes that break older running nodes. Always review pending migrations before upgrading, and upgrade incrementally one version at a time (moving from version 1 to 2, then 2 to 3) to prevent unexpected migration errors, as outlined in the Lemmy Horizontal Scaling guide.
Sources
Published October 31, 2026. No affiliate links on this page. Facts come from the sources linked above; check them for updates.