Introduction

Lemmy is a selfhosted, federated social link aggregation and discussion forum. It consists of many different communities which are focused on different topics. Users can post text, links or images and discuss it with others. Voting helps to bring the most interesting items to the top. There are strong moderation tools to keep out spam and trolls. All this is completely free and open, not controlled by any company. This means that there is no advertising, tracking, or secret algorithms.

Federation is a form of decentralization. Instead of a single central service that everyone uses, there are multiple services that any number of people can use.

A Lemmy website can operate alone. Just like a traditional website, people sign up on it, post messages, upload pictures and talk to each other. Unlike a traditional website, Lemmy instances can interoperate, letting their users communicate with each other; just like you can send an email from your Gmail account to someone from Outlook, Fastmail, Proton Mail, or any other email provider, as long as you know their email address, you can mention or message anyone on any website using their address.

Lemmy uses a standardized, open protocol to implement federation which is called ActivityPub. Any software that likewise implements federation via ActivityPub can seamlessly communicate with Lemmy, just like Lemmy instances communicate with one another.

The fediverse ("federated universe") is the name for all instances that can communicate with each other over ActivityPub and the World Wide Web. That includes all Lemmy servers, but also other implementations:

In practical terms: Imagine if you could follow a Facebook group from your Reddit account and comment on its posts without leaving your account. If Facebook and Reddit were federated services that used the same protocol, that would be possible. With a Lemmy account, you can communicate with any other compatible instance, even if it is not running on Lemmy. All that is necessary is that the software support the same subset of the ActivityPub protocol.

Unlike proprietary services, anyone has the complete freedom to run, examine, inspect, copy, modify, distribute, and reuse the Lemmy source code. Just like how users of Lemmy can choose their service provider, you as an individual are free to contribute features to Lemmy or publish a modified version of Lemmy that includes different features. These modified versions, also known as software forks, are required to also uphold the same freedoms as the original Lemmy project. Because Lemmy is libre software that respects your freedom, personalizations are not only allowed but encouraged.

You can contribute to this documentation in the git repository.

This page is adapted from Mastodon documentation under CC BY-SA 4.0.

Choosing an instance

If you are used to sites like Reddit, then Lemmy works in a fundamentally different way. Instead of a single website like reddit.com, there are many different websites (called instances). These are operated by different people, have different topics and rules. Nevertheless, posts created in one instance can directly be seen by users who are registered on another. Its basically like email, but for social media.

This means before using Lemmy and registering an account, you need to pick an instance. For this you can browse the instance list and look for one that matches your topics of interest. You can also see if the rules match your expectations, and how many users there are. It is better to avoid very big or very small instances. But don't worry too much about this choice, you can always create another account on a different instance later.

[instance list screenshot]

Registration

Once you choose an instance, it's time to create your account. To do this, click sign up in the top right of the page, or click the top right button on mobile to open a menu with sign up link.

[registration page screenshot]

On the signup page you need to enter a few things:

  • Username: How do you want to be called? This name can not be changed and is unique within an instance. Later you can also set a displayname which can be freely changed. If your desired username is taken, consider choosing a different instance where it is still available.
  • Email: Your email address. This is used for password resets and notifications (if enabled). Providing an email address is usually optional, but admins may choose to make it mandatory. In this case you will have to wait for a confirmation mail and click the link after completing this form.
  • Password: The password for logging in to your account. Make sure to choose a long and unique password which isn't used on any other website.
  • Verify password: Repeat the same password from above to ensure that it was entered correctly.

There are also a few optional fields, which you may need to fill in depending on the instance configuration:

  • Question/Answer: Instance admins can set an arbitrary question which needs to be answered in order to create an account. This is often used to prevent spam bots from signing up. After submitting the form, you will need to wait for some time until the answer is approved manually before you can login.
  • Code: A captcha which is easy to solve for humans but hard for bots. Enter the letters and numbers that you see in the text box, ignoring uppercase or lowercase. Click the refresh button if you are unable to read a character. The play button plays an audio version of the captcha.
  • Show NSFW content: Here you can choose if content that is "not safe for work" (or adult-only) should be shown.

When you are done, press the sign up button.

It depends on the instance configuration when you can login and start using the account. In case the email is mandatory, you need to wait for the confirmation email and click the link first. In case "Question/Answer" is present, you need to wait for an admin to manually review and approve your registration. If you have problems with the registration, try to get in contact with the admin for support. You can also choose a different instance to sign up if your primary choice does not work.

Following communities

After logging in to your new account, its time to follow communities that you are interested in. For this you can click on the communities link at the top of the page (on mobile, you need to click the menu icon on the top right first). You will see a list of communities which can be filtered by subscribed, local or all. Local communities are those which are hosted on the same site where you are signed in, while all also contains federated communities from other instances. In any case you can directly subscribe to communities with the right-hand subscribe link. Or click on the community name to browse the community first, see what its posted and what the rules are before subscribing.

Another way to find communities to subscribe to is by going to the front page and browsing the posts. If there is something that interests you, click on the post title to see more details and comments. Here you can subscribe to the community in the right-hand sidebar, or by clicking the "sidebar" button on mobile.

These previous ways will only show communities that are already known to the instance. Especially if you joined a small or inactive Lemmy instance, there will be few communities to discover. You can find more communities by browsing different Lemmy instances, or using the Lemmy Community Browser. When you found a community that you want to follow, enter its URL (e.g. https://feddit.de/c/main) or the identifier (e.g. !main@feddit.de) into the search field of your own Lemmy instance. Lemmy will then fetch the community from its original instance, and allow you to interact with it. The same method also works to fetch users, posts or comments from other instances.

Setting up your profile

Before you start posting, its a good idea to provide some details about yourself. Open the top-right menu and go to "settings". Here the following settings are available for your public profile:

  • Displayname: An alternative username which can be changed at any time
  • Bio: Long description of yourself, can be formatted with Markdown
  • Matrix User: Your username on the decentralized Matrix chat
  • Avatar: Profile picture that is shown next to all your posts
  • Banner: A header image for your profile page

On this page you can also change the email and password. Additionally there are many other settings available, which allow customizing of your browsing experience:

  • Blocks (tab at top of the page): Here you can block users and communities, so that their posts will be hidden.
  • Interface language: Which language the user interface should use.
  • Languages: Select the languages that you speak to see only content in these languages. This is a new feature and many posts don't specify a language yet, so be sure to select "Undetermined" to see them.
  • Theme: You can choose between different color themes for the user interface. Instance admins can add more themes.
  • Type: Which timeline you want to see by default on the frontpage; only posts from communities that you subscribe to, posts in local communities, or all posts including federated.
  • Sort type: How posts and comments should be sorted by default. See Votes and Ranking for details.
  • Show NSFW content: Whether or not you want to see content that is "not safe for work" (or adult-only).
  • Show Scores: Whether the number of upvotes and downvotes should be visible.
  • Show Avatars: Whether profile pictures of other users should be shown.
  • Bot Account: Enable this if you are using a script or program to create posts automatically
  • Show Bot Accounts: Disable this to hide posts that were created by bot accounts.
  • Show Read Posts: If this is disabled, posts that you already viewed are not shown in listings anymore. Useful if you want to find new content easily, but makes it difficult to follow ongoing discussion under existing posts.
  • Show Notifications for New Posts: Enable this to receive a popup notification for each new post that is created.
  • Send notifications to Email: Enable to receive notifications about new comment replies and private messages to your email address.

Start posting

Finally its time to start posting! To do this it is always a good idea to read the community rules in the sidebar (below the Subscribe button). When you are ready, go to a post and type your comment in the box directly below for a top-level reply. You can also write a nested reply to an existing comment, by clicking the left-pointing arrow.

Other than commenting on existing posts, you can also create new posts. To do this, click the button Create a post in the sidebar. Here you can optionally supply an external link or upload an image. The title field is mandatory and should describe what you are posting. The body is again optional, and gives space for long texts. You can also embed additional images here. The Community dropdown below allows choosing a different community to post in. With NSFW posts can be marked as "not safe for work". Finally you can specify the language that the post is written in, and then click on Create.

One more possibility is to write private messages to individual users. To do this, simply visit a user profile and click Send message. You will be notified about new private messages and comment replies with the bell icon in the top right.

Text

The main type of content in Lemmy is text which can be formatted with Markdown. Refer to the table below for supported formatting rules. The Lemmy user interface also provides buttons for formatting, so it's not necessary to remember all of it. You can also follow the interactive CommonMark tutorial to get started.

TypeOr… to Get
*Italic*_Italic_Italic
**Bold**__Bold__Bold
# Heading 1Heading 1
=========

Heading 1

## Heading 2Heading 2
---------
Heading 2
[Link](http://a.com)[Link][1]

[1]: http://b.org
Link
![Image](http://url/a.png)![Image][1]

[1]: http://url/b.jpg
Markdown
> Blockquote
Blockquote
* List
* List
* List
- List
- List
- List
_ List
_ List
* List
1. One
2. Two
3. Three
1) One
2) Two
3) Three
1. One
2. Two
3. Three
Horizontal Rule
---
Horizontal Rule
***
Horizontal Rule

`Inline code` with backticksInline code with backticks
```
# code block
print '3 backticks or'
print 'indent 4 spaces'
```
····# code block
····print '3 backticks or'
····print 'indent 4 spaces'
# code block
print '3 backticks or'
print 'indent 4 spaces'
::: spoiler hidden or nsfw stuff
a bunch of spoilers here
:::
hidden or nsfw stuff

a bunch of spoilers here

Some ~subscript~ textSome subscript text
Some ^superscript^ textSome superscript text

CommonMark Tutorial

Images and video

Lemmy also allows sharing of images and videos. To upload an image, go to the Create post page and click the little image icon under the URL field. This allows you to select a local image. If you made a mistake, a popup message allows you to delete the image. The same image button also allows uploading of videos in .gif format. Instead of uploading a local file, you can also simply paste the URL of an image or video from another website.

Note that this functionality is not meant to share large images or videos, because that would require too many server resources. Instead, upload them on another platform like PeerTube or Pixelfed, and share the link on Lemmy.

Torrents

Since Lemmy doesn't host large videos or other media, users can share files using BitTorrent links. In BitTorrent, files are shared not by a single user, but by many users at the same time. This makes file sharing efficient, fast, and reliable, as long as several sources are sharing the files.

Lemmy supports posting torrent magnet links (links that start with magnet:) in the post URL field, or as markdown links within comments.

With this, Lemmy can serve as an alternative to centralized media-centric services like YouTube and Spotify.

How to watch Torrents

Beginner

To easily stream videos and audio on Lemmy, you can use any of the following apps. After clicking on a torrent link in Lemmy, a dialog will pop up asking you to open the link in the app.

Expert

For those who would like to help share files, you can use any of the following torrent clients.

If you'd like, you can also set up a media server to view this content on any device. Some good options are:

Votes and ranking

Lemmy uses a voting system to sort post listings. On the left side of each post there are up and down arrows, which let you upvote or downvote it. You can upvote posts that you like so that more users will see them. Or downvote posts so that they are less likely to be seen. Each post receives a score which is the number of upvotes minus number of downvotes.

When browsing the frontpage or a community, you can choose between the following sort types for posts:

  • Active (default): Calculates a rank based on the score and time of the latest comment, with decay over time
  • Hot: Like active, but uses time when the post was published
  • Scaled: Like hot, but gives a boost to less active communities
  • New: Shows most recent posts first
  • Old: Shows oldest posts first
  • Most Comments: Shows posts with highest number of comments first
  • New Comments: Bumps posts to the top when they are created or receive a new reply, analogous to the sorting of traditional forums
  • Top Day: Highest scoring posts during the last 24 hours
  • Top Week: Highest scoring posts during the last 7 days
  • Top Month: Highest scoring posts during the last 30 days
  • Top Year: Highest scoring posts during the last 12 months
  • Top All Time: Highest scoring posts during all time

Comments are by default arranged in a hierarchy which shows at a glance who it is replying to. Top-level comments which reply directly to a post are on the very left, not indented at all. Comments that are responding to top-level comments are indented one level and each further level of indentation means that the comment is deeper in the conversation. With this layout it is always easy to see the context for a given comment, by simply scrolling up to the next comment which is indented one level less.

Comments can be sorted in the following ways. These all keep the indentation intact, so only replies to the same parent are shuffled around.

  • Hot (default): Equivalent to the Hot sort for posts
  • Top: Shows comments with highest score first
  • New: Shows most recent comments first
  • Old: Shows oldest comments first

Additionally there is a sort option Chat. This eliminates the hierarchy, and puts all comments on the top level, with newest comments shown at the top. It is useful to see new replies at any point in the conversation, but makes it difficult to see the context.

The ranking algorithm is described in detail here.

Moderation

The internet is full of bots, trolls and other malicious actors. Sooner or later they will post unwanted content to any website that is open to the public. It is the task of administrators and moderators to remove such unwanted content. Lemmy provides many tools for this, from removing individual posts, over temporary bans, to removing all content from an offending user.

Moderation in Lemmy is divided between administrators and moderators. Admins are responsible for the entire instance, and can take action on any content. They are also the only ones who can completely ban users. In contrast, moderators are only responsible for a single community. Where admins can ban a user from the entire instance, mods can only ban them from their community.

The most important thing that normal users can do if they notice a rule breaking post is to use the report function. If you notice such a post, click the flag icon to notify mods and admins. This way they can take action quickly and remove the offending content. To find out about removals and other mod actions, you can use the mod log which is linked at the bottom of the page. In some cases there may be content that you personally dislike, but which doesn't violate any rules. For this exists a block function which hides all posts from a given user or community.

Each instance has a set of rules to let users know which content is allowed or not. These rules can be found in the sidebar and apply to all local communities on that instance. Some communities may have their own rules in the respective sidebar, which apply in addition to the instance rules.

Because Lemmy is decentralized, there is no single moderation team for the platform, nor any platform-wide rules. Instead each instance is responsible to create and enforce its own moderation policy. This means that two Lemmy instances can have rules that completely disagree or even contradict. This can lead to problems if they interact with each other, because by default federation is open to any instance that speaks the same protocol. To handle such cases, administrators can choose to block federation with specific instances. To be even safer, they can also choose to be federated only with instances that are allowed explicitly.

How to moderate

To get moderator powers, you either need to create a new community, or be appointed by an existing moderator. Similarly to become an admin, you need to create a new instance, or be appointed by an existing instance admin. Community moderation can be done over federation, you don't need to be registered on the same instance where the community is hosted. To be an instance administrator, you need an account on that specific instance. Admins and moderators are organized in a hierarchy, where the user who is listed first has the power to remove admins or mods who are listed later.

All moderation actions are taken on the context menu of posts or comments. Click the three dot button to expand available mod actions, as shown in the screenshot below. All actions can be reverted in the same way.

moderation_01.png moderation_02.png

ActionResultPermission level
LockPrevents making new comments under the postModerator
Sticky (Community)Pin the publication to the top of the community listingModerator
Sticky (Local)Pin the publication to the top of the frontpageAdmin
RemoveDelete the postModerator
Ban from communityBan user from interacting with the community, but can still use the rest of the site. There is also an option to remove all existing posts.Moderator
Appoint as modGives the user moderator statusModerator
Ban from siteCompletely bans the account, so it can't login or interact at all. There is also an option to remove all existing posts.Admin
Purge userCompletely delete the user, including all posts and uploaded media. Use with caution.Admin
Purge post/commentCompletely delete the post, including attached media.Admin
Appoint as adminGives the user administrator statusAdmin

Censorship resistance

Today's social media landscape is extremely centralized. The vast majority of users are concentrated on only a handful of platforms like Facebook, Reddit or Twitter. All of these are maintained by large corporations that are subject to profit motive and United States law. In recent years these platforms have increasingly censored users and entire communities, often with questionable justifications. It is only natural that those who are affected by this search for alternatives. This document is intended to help with the evaluation.

For this purpose we will consider as censorship anything that prevents a person from expressing their opinion, regardless of any moral considerations. All the options explained here also have legitimate uses, such as deleting spam. Nevertheless it is important for users to understand why their posts are getting removed and how to avoid it.

The first and most common source of censorship in this sense is the admin of a given Lemmy instance. Due to the way federation works, an admin has complete control over their instance, and can arbitrarily delete content or ban users. The moderation log helps to provide transparency into such actions.

The second source of censorship is through legal means. This often happens for copyright violation, but can also be used for other cases. What usually happens in this case is that the instance admin receives a takedown notice from the hosting provider or domain registrar. If the targeted content is not removed within a few days, the site gets taken down. The only way to avoid this is to choose the hosting company and country carefully, and avoid those which might consider the content as illegal.

Another way to censor is through social pressure on admins. This can range from spamming reports for unwanted content, to public posts from influential community members demanding to take certain content down. Such pressure can keep mounting for days or weeks, making it seem like everyone supports these demands. But in fact it is often nothing more than a vocal minority. It is the task of admins to gauge the true opinion of their community. Community members should also push back if a minority tries to impose its views on everyone else.

All of this shows that it is relatively easy to censor a single Lemmy instance. Even a group of instances can be censored if they share the same admin team, hosting infrastructure or country. Here it is important that an admin can only censor content on their own instance, or communities which are hosted on his instance. Other instances will be unaffected. So if there is a problem with censorship, it can always be solved by using a different Lemmy instance, or creating a new one.

But what if the goal was to censor the entire Lemmy network? This is inherently difficult because there is no single entity which has control over all instances. The closest thing to such an entity are the developers, because they can make changes to the code that all the instances run. For example, developers could decide to implement a hardcoded block for certain domains, so that they can't federate anymore. However, changes need to be released and then installed by instance admins. Those who are affected would have no reason to upgrade. And because the code is open source, they could publish a forked software version without these blocks. So the effect would be very limited, but it would split the project and result in loss of reputation for the developers. This is probably the reason why it has never happened on any Fediverse platform.

Lastly it might be possible to abuse software vulnerabilities for network-wide censorship. Imagine a bug in Lemmy or in the underlying software stack which allows the attacker to delete arbitrary content. This could remain undetected for a while if used sparingly, but would certainly be discovered after some time. And experience has shown that such critical flaws are fixed very quickly in open source software. It is also highly unlikely that critical vulnerabilities be present in multiple different Fediverse platforms at the same time.

In conclusion, the best way to avoid censorship on Lemmy is through the existence of many independent instances. These should have different admins, different hosting providers and be located in different countries. Additionally users should follow the development process to watch for changes that might create a centralized point of control for all instances. Based on this explanation it should be clear that censorship on Lemmy is difficult, and can always be circumvented. This is in contrast to centralized platforms like Facebook or Reddit. They are not open source and can't be self-hosted, so it is necessary to switch to an entirely different platform to avoid censorship. And due to lack of federation, such a switch means losing contact with users who decide to stay on the censored platform.

Theming

Users can choose between a number of built-in color themes. Admins can also provide additional themes and set them as default.

Easy to install, low hardware requirements

Lemmy is written in Rust, which is an extremely fast language. Thats why it has very low hardware requirements. It can easily run on a Raspberry Pi or similar low-powered hardware. This makes it easy to administrate and keeps costs low.

Language Tags

Lemmy instances and communities can specify which languages can be used for posting. Consider an instance aimed at Spanish users, it would limit the posting language to Spanish so that other languages can't be used. Or an international instance which only allows languages that the admin team understands. Community languages work in the same way, and are restricted to a subset of the instance languages. By default all languages are allowed (including undefined).

Users can also specify which languages they speak, and will only see content in those languages. Lemmy tries to smartly select a default language for new posts if possible. Otherwise you have to specify the language manually.

Lemmy as a blog

Lemmy can also function as a blogging platform. Doing this is as simple as creating a community and enabling the option "Only moderators can post to this community". Now only you and other people that you invite can create posts, while everyone else can comment. Like any Lemmy community, it is also possible to follow from other Fediverse platforms and over RSS. For advanced usage it is even possible to use the API and create a different frontend which looks more blog-like.

History of Lemmy

The idea to make Lemmy was a combination of factors.

Open source developers like myself have long watched the rise of the “Big Five”, the US tech giants that have managed to capture nearly all the world’s everyday communication into their hands. We’ve been asking ourselves why people have moved away from content-focused sites, and what we can do to subvert this trend, in a way that is easily accessible to a non-tech focused audience.

The barriers to entry on the web are much lower than say in the physical world: all it takes is a computer and some coding knowhow… yet the predominant social media firms have been able to stave off competition for at least two reasons: their sites are easy to use, and they have huge numbers of users already (the “first mover” advantage). The latter is more important; if you’ve ever tried to get someone to use a different chat app, you’ll know what I mean.

Now I loved early Reddit, not just for the way that it managed to put all the news for the communities and topics I wanted to see in a single place, but for the discussion trees behind every link posted. I still have many of these saved, and have gained so much more from the discussion behind the links, than I have from the links themselves. In my view, its the community-focused, tree-like discussions, as well as the ability to make, grow, and curate communities, that has made Reddit the 5th most popular site in the US, and where so many people around the world get their news.

But that ship sailed years ago; the early innovative spirit of Reddit left with Aaron Swartz: its libertarian founders have allowed some of the most racist and sexist online communities to fester on Reddit for years, only occasionally removing them when community outcry reaches a fever pitch. Reddit closed its source code years ago, and the Reddit redesign has become a bloated anti-privacy mess.

Its become absorbed into that silicon valley surveillance-capitalist machine that commodifies users to sell ads and paid flairs, and propagandizes pro-US interests above all. Software technology being one of the last monopoly exports the US has, it would be naive to think that one of the top 5 most popular social media sites, where so many people around the world get their news, would be anything other than a mouthpiece for the interests of those same US coastal tech firms.

Despite the conservative talking point that big tech is dominated by “leftist propaganda”, it is liberal, and pro-US, not left (leftism referring to the broad category of anti-capitalism). Reddit has banned its share of leftist users and communities, and the Reddit admins via announcement posts repeatedly vilify the US’s primary foreign-policy enemies as having “bot campaigns”, and “manipulating Reddit”, yet the default Reddit communities (/r/news, /r/pics, etc), who share a small number of moderators, push a line consistent with US foreign-policy interests. The aptly named /r/copaganda subreddit has exposed the pro-police propaganda that always seems to hit Reddit’s front page in the wake of every tragedy involving US police killing the innocent (or showing police kissing puppies, even though US police kill ~ 30 dogs every day, which researchers have called a “noted statistical phenomenon”).

We’ve also seen a rise in anti-China posts that have hit Reddit lately, and along with that comes anti-chinese racism, which Reddit tacitly encourages. That western countries are seeing a rise in attacks against Asian-Americans, just as some of the perpetrators of several hate-crimes against women were found to be Redditors active in mens-rights Reddit communities, is not lost on us, and we know where these tech companies really stand when it comes to violence and hate speech. Leftists know that our position on these platforms is tenuous at best; we’re currently tolerated, but that will not always be the case.

The idea for making a Reddit alternative seemed pointless, until Mastodon (a federated twitter alternative), started becoming popular. Using Activitypub (a protocol / common language that social media services can use to speak to each other), we finally have a solution to the “first mover” advantage: now someone can build or run a small site, but still be connected to a wider universe of users.

Nutomic and I originally made Lemmy to fill the role as a federated alternative to Reddit, but as it grows, it has the potential become a main source of news and discussion, existing outside of the US’s jurisdictional domain and control.

Administration info

Information for Lemmy instance admins, and those who want to run a server.

If you have any problems in the installation, you can ask for help in !lemmy_support. Do not use Github for support.

Install

Official/Supported methods

Lemmy has two primary installation methods:

We recommend using Ansible, because it simplifies the installation and also makes updating easier.

Lemmy uses roughly 150 MB of RAM in the default Docker installation. CPU usage is negligible.

Managed Hostings

Other installation methods

⚠️ Under your own risk.

In some cases, it might be necessary to use different installation methods.

You could use any other reverse proxy

An Example Caddy configuration.

Lemmy components

Lemmy-ui

Lemmy-ui is the main frontend for Lemmy. It consists of an expressjs based server-side process (necessary for SSR) and client code which run in the browser. It does not use a lot of resources and will happily run on quite low powered servers.

Lemmy_server

Lemmy_server is the backend process, which handles:

  • Incoming HTTP requests (both from Lemmy clients and incoming federation from other servers)
  • Outgoing federation
  • Scheduled tasks (most notably, constant hot rank calculations, which keep the front page fresh)

Pict-rs

Pict-rs is a service which does image processing. It handles user-uploaded images as well as downloading thumbnails for external images.

Docker Installation

Make sure you have both docker and docker-compose(>=2.0) installed. On Ubuntu, just run apt install docker-compose-v2 docker.io. On Debian, you need to install Docker using their official installation instructions and custom apt repo. Next,

# Create a folder for the lemmy files. the location doesnt matter, you can put this anywhere you want
mkdir lemmy
cd lemmy

Download default config files

These files contain {{ }} braces for variables, such as passwords and your domain.

Edit them before starting up lemmy for the first time.

The images will likely be: dessalines/lemmy:VERSION and dessalines/lemmy-ui:VERSION

Tracking Issue: For now, using latest is not advisable as changes can break your instance. Make sure you are using a specific version number. This is temporary and will be fixed.

wget https://raw.githubusercontent.com/LemmyNet/lemmy-ansible/main/templates/docker-compose.yml
wget https://raw.githubusercontent.com/LemmyNet/lemmy-ansible/main/examples/config.hjson -O lemmy.hjson
wget https://raw.githubusercontent.com/LemmyNet/lemmy-ansible/main/templates/nginx_internal.conf
wget https://raw.githubusercontent.com/LemmyNet/lemmy-ansible/main/files/proxy_params

If you'd like further customization, have a look at the config file named lemmy.hjson, and adjust it accordingly.

Database tweaks

To optimize your database, add this file.

You can input your system specs, using this tool: https://pgtune.leopard.in.ua/

wget https://raw.githubusercontent.com/LemmyNet/lemmy-ansible/main/examples/customPostgresql.conf

Folder permissions

Set the correct permissions for pictrs folder:

mkdir -p volumes/pictrs
sudo chown -R 991:991 volumes/pictrs

Finally, run:

docker compose up -d

lemmy-ui is accessible on the server at http://localhost:{{ lemmy_port }}

Reverse Proxy / Webserver

Here's an optional nginx reverse proxy template, which you can place in /etc/nginx/sites-enabled

Alternatively, you can use any other web server such as caddy as a simple reverse proxy.

Be sure to edit the {{ }} to match your domain and port.

If you're using this, you will need to set up Let's Encrypt. See those instructions below.

wget https://raw.githubusercontent.com/LemmyNet/lemmy-ansible/main/templates/nginx.conf

If you've set up Let's Encrypt and your reverse proxy, you can go to https://{{ domain }}

Let's Encrypt

You should also setup TLS, for example with Let's Encrypt. Here's a guide for setting up letsencrypt on Ubuntu.

For federation to work, it is important that you do not change any headers that form part of the signature. This includes the Host header - you may need to refer to the documentation for your proxy server to pass through the Host header unmodified.

Updating

To update to the newest version, you can manually change the version in docker-compose.yml. Alternatively, fetch the latest version from our lemmy-ansible repo:

wget https://raw.githubusercontent.com/LemmyNet/lemmy-ansible/main/templates/docker-compose.yml
# Then replace the {{ }} vars again
docker compose up -d

Ansible Installation

Follow the instructions on the Lemmy-Ansible repo.

From Scratch

These instructions are written for Ubuntu 20.04 / Ubuntu 22.04. They are particularly useful when you'd like to setup a Lemmy container (e.g. LXC on Proxmox) and cannot use Docker.

Lemmy is built from source in this guide, so this may take a while, especially on slow devices. For example, Lemmy v0.18.5 takes around 7 minutes to build on a quad core VPS.

Installing and configuring Lemmy using this guide takes about 60-90 minutes. You might need to make yourself a fresh cup of coffee before you start.

Installation

Database

For Ubuntu 20.04 the shipped PostgreSQL version is 12 which is not supported by Lemmy. So let's set up a newer one. The most recent stable version of PostgreSQL is 16 at the time of writing this guide.

Install dependencies

sudo apt install -y wget ca-certificates pkg-config
wget --quiet -O - https://www.postgresql.org/media/keys/ACCC4CF8.asc | sudo apt-key add -
sudo sh -c 'echo "deb http://apt.postgresql.org/pub/repos/apt/ $(lsb_release -cs)-pgdg main" >> /etc/apt/sources.list.d/pgdg.list'
sudo apt update
sudo apt install libssl-dev libpq-dev postgresql

Setup Lemmy database

Replace db-passwd with a unique password of your choice in the commands below.

sudo -iu postgres psql -c "CREATE USER lemmy WITH PASSWORD 'db-passwd';"
sudo -iu postgres psql -c "CREATE DATABASE lemmy WITH OWNER lemmy;"

If you're migrating from an older version of Lemmy, the following might be required.

sudo -iu postgres psql -c "ALTER USER lemmy WITH SUPERUSER;"

Tune your PostgreSQL settings to match your hardware via this guide

Setup md5 auth

Your Postgres config might need to be edited to allow password authentication instead of peer authentication. Simply add the following to your pg_hba.conf:

local   lemmy           lemmy                                   md5

Install Rust

For the Rust compiles, it is ideal to use a non-privileged Linux account on your system. Install Rust by following the instructions on Rustup (using a non-privileged Linux account, it will install file in that user's home folder for rustup and cargo).

protobuf-compiler may be required for Ubuntu 20.04 or 22.04 installs, please report testing in lemmy-docs issues.

sudo apt install protobuf-compiler gcc

Setup pict-rs (Optional)

You can skip this section if you don't require image hosting, but NOTE that Lemmy-ui will still allow users to attempt uploading images even if pict-rs is not configured. In this situation, the upload will fail and users will receive technical error messages.

Lemmy supports image hosting using pict-rs. We need to install a couple of dependencies for this.

Depending on preference, pict-rs can be installed as a standalone application, or it can be embedded within Lemmy itself. In both cases, pict-rs requires the magick command which comes with Imagemagick version 7, but Ubuntu 20.04 only comes with Imagemagick 6. So you need to install that command manually, eg from the official website.

NOTE: on standard LXC containers an AppImage-based ImageMagick installation will not work properly with both embedded and standalone pict-rs. It uses FUSE which will emit "permission denied" errors when trying to upload an image through pict-rs. You must use alternative installation methods, such as imei.sh.

AppImage-based installation of ImageMagick

sudo apt install ffmpeg exiftool libgexiv2-dev --no-install-recommends
# save the file to a working folder it can be verified before copying to /usr/bin/
wget https://download.imagemagick.org/ImageMagick/download/binaries/magick
# compare hash with the "message digest" on the official page linked above
sha256sum magick
sudo mv magick /usr/bin/
sudo chmod 755 /usr/bin/magick

imei.sh-based installation of ImageMagick

Follow the instructions from the official imei.sh page on GitHub

Standalone pict-rs installation

Since we're building stuff from source here, let's do the same for pict-rs. Follow the instructions here.

However, the embedded pict-rs installation should work just fine for you.

Lemmy Backend

Build the backend

Create user account on Linux for the lemmy_server application

sudo adduser lemmy --system --disabled-login --no-create-home --group

Compile and install Lemmy, given the from-scratch intention, this will be done via GitHub checkout. This can be done by a normal unprivledged user (using the same Linux account you used for rustup).

git clone https://github.com/LemmyNet/lemmy.git lemmy
cd lemmy
git checkout 0.18.5
git submodule init
git submodule update --recursive --remote
echo "pub const VERSION: &str = \"$(git describe --tag)\";" > "crates/utils/src/version.rs"

When using the embedded pict-rs, use the following build command:

cargo build --release --features embed-pictrs

Otherwise, just move forward with the following.

cargo build --release

Deployment

Because we should follow the Linux way, we should use the /opt directory to colocate the backend, frontend and pict-rs.

sudo mkdir /opt/lemmy
sudo mkdir /opt/lemmy/lemmy-server
sudo mkdir /opt/lemmy/pictrs
sudo mkdir /opt/lemmy/pictrs/files
sudo mkdir /opt/lemmy/pictrs/sled-repo
sudo mkdir /opt/lemmy/pictrs/old
sudo chown -R lemmy:lemmy /opt/lemmy

Note that it might not be the most obvious thing, but creating the pictrs directories is not optional.

Then copy the binary.

sudo cp target/release/lemmy_server /opt/lemmy/lemmy-server/lemmy_server

Configuration

This is the minimal Lemmy config, put this in /opt/lemmy/lemmy-server/lemmy.hjson (see here for more config options).

{
  database: {
    # put your db-passwd from above
    password: "db-passwd"
  }
  # replace with your domain
  hostname: example.com
  bind: "127.0.0.1"
  federation: {
    enabled: true
  }
  # remove this block if you don't require image hosting
  pictrs: {
    url: "http://localhost:8080/"
  }
}

Set the correct owner

chown -R lemmy:lemmy /opt/lemmy/

Server daemon

Add a systemd unit file, so that Lemmy automatically starts and stops, logs are handled via journalctl etc. Put this file into /etc/systemd/system/lemmy.service.

[Unit]
Description=Lemmy Server
After=network.target

[Service]
User=lemmy
ExecStart=/opt/lemmy/lemmy-server/lemmy_server
Environment=LEMMY_CONFIG_LOCATION=/opt/lemmy/lemmy-server/lemmy.hjson
Environment=PICTRS_ADDR=127.0.0.1:8080
Environment=RUST_LOG="info"
Restart=on-failure
WorkingDirectory=/opt/lemmy

# Hardening
ProtectSystem=yes
PrivateTmp=true
MemoryDenyWriteExecute=true
NoNewPrivileges=true

[Install]
WantedBy=multi-user.target

If you need debug output in the logs, change the RUST_LOG line in the file above to

Environment=RUST_LOG="debug,lemmy_server=debug,lemmy_api=debug,lemmy_api_common=debug,lemmy_api_crud=debug,lemmy_apub=debug,lemmy_db_schema=debug,lemmy_db_views=debug,lemmy_db_views_actor=debug,lemmy_db_views_moderator=debug,lemmy_routes=debug,lemmy_utils=debug,lemmy_websocket=debug"

Then run

sudo systemctl daemon-reload
sudo systemctl enable lemmy
sudo systemctl start lemmy

If you did everything right, the Lemmy logs from sudo journalctl -u lemmy should show "Starting http server at 127.0.0.1:8536". You can also run curl localhost:8536/api/v3/site which should give a successful response, looking like {"site_view":null,"admins":[],"banned":[],"online":0,"version":"unknown version","my_user":null,"federated_instances":null}. For pict-rs, run curl 127.0.0.1:8080 and ensure that it outputs nothing (particularly no errors).

Lemmy Front-end (lemmy-ui)

Install dependencies

Nodejs in Ubuntu 20.04 / Ubuntu 22.04 repos are too old, so let's install Node 20.

# nodejs
sudo apt install -y ca-certificates curl gnupg
sudo mkdir -p /etc/apt/keyrings
curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | sudo gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg
echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_20.x nodistro main" | sudo tee /etc/apt/sources.list.d/nodesource.list

sudo apt update
sudo apt install nodejs

# pnpm
npm i -g pnpm

Build the front-end

Clone the git repo, checkout the version you want (0.18.5 in this case), and compile it.

# dont compile as admin
cd /opt/lemmy
sudo -u lemmy bash
git clone https://github.com/LemmyNet/lemmy-ui.git --recursive
cd lemmy-ui
git checkout 0.18.5 # replace with the version you want to install
pnpm i
pnpm build:prod
exit

UI daemon

Add another systemd unit file, this time for lemmy-ui. You need to replace example.com with your actual domain. Put the file in /etc/systemd/system/lemmy-ui.service

[Unit]
Description=Lemmy UI
After=lemmy.service
Before=nginx.service

[Service]
User=lemmy
WorkingDirectory=/opt/lemmy/lemmy-ui
ExecStart=/usr/bin/node dist/js/server.js
Environment=LEMMY_UI_LEMMY_INTERNAL_HOST=localhost:8536
Environment=LEMMY_UI_LEMMY_EXTERNAL_HOST=example.com
Environment=LEMMY_UI_HTTPS=true
Restart=on-failure

# Hardening
ProtectSystem=full
PrivateTmp=true
NoNewPrivileges=true

[Install]
WantedBy=multi-user.target

More UI-related variables can be found here.

Then run.

sudo systemctl daemon-reload
sudo systemctl enable lemmy-ui
sudo systemctl start lemmy-ui

If everything went right, the command curl -I localhost:1234 should show 200 OK at the top.

Configure reverse proxy and TLS

Install dependencies

sudo apt install nginx certbot python3-certbot-nginx

Request Let's Encrypt TLS certificate (just follow the instructions)

sudo certbot certonly --nginx

Let's Encrypt certificates should be renewed automatically, so add the line below to your crontab, by running sudo crontab -e. Replace example.com with your actual domain.

@daily certbot certonly --nginx --cert-name example.com -d example.com --deploy-hook 'nginx -s reload'

Finally, add the Nginx virtual host config file. Copy paste the file below to /etc/nginx/sites-enabled/lemmy.conf

limit_req_zone $binary_remote_addr zone={{domain}}_ratelimit:10m rate=1r/s;

server {
    listen 80;
    listen [::]:80;
    server_name {{domain}};
    location /.well-known/acme-challenge/ {
        root /var/www/certbot;
    }
    location / {
        return 301 https://$host$request_uri;
    }
}

server {
    listen 443 ssl http2;
    listen [::]:443 ssl http2;
    server_name {{domain}};

    ssl_certificate /etc/letsencrypt/live/{{domain}}/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/{{domain}}/privkey.pem;

    # Various TLS hardening settings
    # https://raymii.org/s/tutorials/Strong_SSL_Security_On_nginx.html
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_prefer_server_ciphers on;
    ssl_ciphers 'ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256';
    ssl_session_timeout  10m;
    ssl_session_cache shared:SSL:10m;
    ssl_session_tickets on;
    ssl_stapling on;
    ssl_stapling_verify on;

    # Hide nginx version
    server_tokens off;

    # Enable compression for JS/CSS/HTML bundle, for improved client load times.
    # It might be nice to compress JSON, but leaving that out to protect against potential
    # compression+encryption information leak attacks like BREACH.
    gzip on;
    gzip_types text/css application/javascript image/svg+xml;
    gzip_vary on;

    # Only connect to this site via HTTPS for the two years
    add_header Strict-Transport-Security "max-age=63072000";

    # Various content security headers
    add_header Referrer-Policy "same-origin";
    add_header X-Content-Type-Options "nosniff";
    add_header X-Frame-Options "DENY";
    add_header X-XSS-Protection "1; mode=block";

    # Upload limit for pictrs
    client_max_body_size 20M;

    # frontend
    location / {
      # The default ports:

      set $proxpass "http://0.0.0.0:1234";
      if ($http_accept ~ "^application/.*$") {
        set $proxpass "http://0.0.0.0:8536";
      }
      if ($request_method = POST) {
        set $proxpass "http://0.0.0.0:8536";
      }
      proxy_pass $proxpass;

      rewrite ^(.+)/+$ $1 permanent;

      # Send actual client IP upstream
      proxy_set_header X-Real-IP $remote_addr;
      proxy_set_header Host $host;
      proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }

    # backend
    location ~ ^/(api|pictrs|feeds|nodeinfo|.well-known) {
      proxy_pass http://0.0.0.0:8536;
      proxy_http_version 1.1;
      proxy_set_header Upgrade $http_upgrade;
      proxy_set_header Connection "upgrade";

      # Rate limit
      limit_req zone={{domain}}_ratelimit burst=30 nodelay;

      # Add IP forwarding headers
      proxy_set_header X-Real-IP $remote_addr;
      proxy_set_header Host $host;
      proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }
}

access_log /var/log/nginx/access.log combined;

And then replace some variables in the file. Put your actual domain instead of example.com

sudo sed -i -e 's/{{domain}}/example.com/g' /etc/nginx/sites-enabled/lemmy.conf
sudo systemctl reload nginx

Now open your Lemmy domain in the browser, and it should show you a configuration screen. Use it to create the first admin user and the default community.

Upgrading

Lemmy

Compile and install lemmy_server changes. This compile can be done by a normal unprivledged user (using the same Linux account you used for rustup and first install of Lemmy).

rustup update
cd lemmy
git checkout main
git pull --tags
git checkout 0.18.5 # replace with version you are updating to
git submodule update --recursive --remote
echo "pub const VERSION: &str = \"$(git describe --tag)\";" > "crates/utils/src/version.rs"
# These instructions assume you build pictrs independent, but it is
# OPTIONAL on next command: --features embed-pictrs
cargo build --release
# copy compiled binary to destination
# the old file will be locked by the already running application, so this sequence is recommended:
sudo -- sh -c 'systemctl stop lemmy && cp target/release/lemmy_server /opt/lemmy/lemmy-server/lemmy_server && systemctl start lemmy'

Lemmy UI

cd /opt/lemmy/lemmy-ui
sudo -u lemmy bash
git checkout main
git pull --tags
git checkout 0.18.5 # replace with the version you are updating to
git submodule update
pnpm install
pnpm build:prod
exit
sudo systemctl restart lemmy-ui

Pict-rs

If you used the --features embed-pictrs flag, pict-rs should update with lemmy_server. Otherwise, refer to pict-rs documentation for instructions on upgrading.

Installing on AWS

⚠️ Disclaimer: this installation method is not recommended by the Lemmy developers. If you have any problems, you need to solve them yourself or ask the respective authors. If you notice any Lemmy bugs on an instance installed like this, please mention it in the bug report.

Lemmy AWS CDK

This contains the necessary infrastructure definitions to deploy Lemmy to AWS using their Cloud Development Kit.

Included:

  • ECS Fargate cluster
    • Lemmy-UI
    • Lemmy
    • Pictrs
  • CloudFront CDN
  • EFS storage for image uploads
  • Aurora Serverless Postgres DB
  • Bastion VPC host
  • Load balancers for Lemmy
  • DNS records for your site

Quickstart

Clone the Lemmy-CDK.

Clone Lemmy and Lemmy-UI to the directory above this.

cp example.env.local .env.local
# edit .env.local

You should edit .env.local with your site settings.

npm install -g aws-cdk
npm install
cdk bootstrap
cdk deploy

Cost

This is not the cheapest way to run Lemmy. The Serverless Aurora DB can run you ~$90/mo if it doesn't go to sleep.

Useful CDK commands

  • npm run build compile typescript to js
  • npm run watch watch for changes and compile
  • npm run test perform the jest unit tests
  • cdk deploy deploy this stack to your default AWS account/region
  • cdk diff compare deployed stack with current state
  • cdk synth emits the synthesized CloudFormation template

Administration First Steps

After you successfully installed Lemmy either manually with Docker or automatically with Ansible here are some recommendations for a new administrator of a Lemmy server.

Admin Settings

The first thing to do is to go to your admin panel, which can be found by clicking on the cog at the top right next to the search icon. Here you can define a description for your site, so that people know if it is about one specific topic or if all subjects are welcome. You can also add an icon and a banner that define your server, it can for example be the logo of your organization.

Take the time to browse through the entire page to discover the different options you have to customize your Lemmy instance, on the same page you can edit your configuration file, where you can find information about your database, the email used by the server, the federation options or who is the main administrator.

It is always good to define another administrator than yourself, in case it is necessary to take actions while you take your best nap. Take a look at the moderation guide for more information on how to do this.

Check that everything is working properly

Email

The easiest way to check that the email is set up correctly is to request a password renewal. You will need to set up an email in your settings if you have not already done so.

After that just log out, go to the Login page, enter your email in the Email or Username box and press forgot password. If everything is set up correctly, you should receive an email to renew your password. You can ignore this email.

Federation

Federation is disabled by default, and needs to be enabled either through the online admin panel or directly through the config.json file.

To test that your instance federation is working correctly execute curl -H 'Accept: application/activity+json' https://your-instance.com/u/your-username, it should return json data, and not an .html file. If that is unclear to you, it should look similar to the output of curl -H 'Accept: application/activity+json' https://lemmy.ml/u/nutomic.

Inclusion on join-lemmy.org instance list

To be included in the list of Lemmy instances on join-lemmy.org you must meet the following requirements:

  • Federate with at least one instance from the list
  • Have a site description and icon
  • Don't have closed sign-ups
  • Have at least 5 users who posted or commented at least once in the past month
  • Be on the latest major version of Lemmy
  • Be patient and wait the site to be updated, there's no fixed schedule for that

Recommended instances are defined in code here and the code that powers the crawler is visible here.

In the meantime you can always promote your server on other social networks like Mastodon using the hashtag #Lemmy.

Keeping up to date

You can subscribe to the Github RSS feeds to be informed of the latest releases:

There is also a Matrix chat for instance administrators that you can join. You'll find some really friendly people there who will help you (or at least try to) if you run into any issue.

Configuration

The configuration is based on the file config.hjson, which is located by default at config/config.hjson. To change the default location, you can set the environment variable LEMMY_CONFIG_LOCATION.

Additional environment variable are available:

  • LEMMY_DATABASE_URL, which can be used with a PostgreSQL connection string like postgres://lemmy:password@lemmy_db:5432/lemmy, passing all connection details at once,
  • LEMMY_SMTP_PASSWORD, which can be used to set the password to authenticate with the SMTP server.

Those environment variables will override values if specified in the config file.

If the Docker container is not used, manually create the database specified above by running the following commands:

cd server
./db-init.sh

Full config with default values

{
  # settings related to the postgresql database
  database: {
    # Configure the database by specifying a URI
    # 
    # This is the preferred method to specify database connection details since
    # it is the most flexible.
    # Connection URI pointing to a postgres instance
    # 
    # This example uses peer authentication to obviate the need for creating,
    # configuring, and managing passwords.
    # 
    # For an explanation of how to use connection URIs, see [here][0] in
    # PostgreSQL's documentation.
    # 
    # [0]: https://www.postgresql.org/docs/current/libpq-connect.html#id-1.7.3.8.3.6
    uri: "postgresql:///lemmy?user=lemmy&host=/var/run/postgresql"

    # or

    # Configure the database by specifying parts of a URI
    # 
    # Note that specifying the `uri` field should be preferred since it provides
    # greater control over how the connection is made. This merely exists for
    # backwards-compatibility.
    # Username to connect to postgres
    user: "string"
    # Password to connect to postgres
    password: "string"
    # Host where postgres is running
    host: "string"
    # Port where postgres can be accessed
    port: 123
    # Name of the postgres database for lemmy
    database: "string"
    # Maximum number of active sql connections
    pool_size: 5
  }
  # Settings related to activitypub federation
  # Pictrs image server configuration.
  pictrs: {
    # Address where pictrs is available (for image hosting)
    url: "http://localhost:8080/"
    # Set a custom pictrs API key. ( Required for deleting images )
    api_key: "string"
  }
  # Email sending configuration. All options except login/password are mandatory
  email: {
    # Hostname and port of the smtp server
    smtp_server: "localhost:25"
    # Login name for smtp server
    smtp_login: "string"
    # Password to login to the smtp server
    smtp_password: "string"
    # Address to send emails from, eg "noreply@your-instance.com"
    smtp_from_address: "noreply@example.com"
    # Whether or not smtp connections should use tls. Can be none, tls, or starttls
    tls_type: "none"
  }
  # Parameters for automatic configuration of new instance (only used at first start)
  setup: {
    # Username for the admin user
    admin_username: "admin"
    # Password for the admin user. It must be at least 10 characters.
    admin_password: "tf6HHDS4RolWfFhk4Rq9"
    # Name of the site (can be changed later)
    site_name: "My Lemmy Instance"
    # Email for the admin user (optional, can be omitted and set later through the website)
    admin_email: "user@example.com"
  }
  # the domain name of your instance (mandatory)
  hostname: "unset"
  # Address where lemmy should listen for incoming requests
  bind: "0.0.0.0"
  # Port where lemmy should listen for incoming requests
  port: 8536
  # Whether the site is available over TLS. Needs to be true for federation to work.
  tls_enabled: true
  # The number of activitypub federation workers that can be in-flight concurrently
  worker_count: 0
  # The number of activitypub federation retry workers that can be in-flight concurrently
  retry_count: 0
  prometheus: {
    bind: "127.0.0.1"
    port: 10002
  }
}

Lemmy-UI configuration

Lemmy-UI can be configured using environment variables, detailed in its README.

Theming Guide

Lemmy uses Bootstrap v4, and very few custom css classes, so any bootstrap v4 compatible theme should work fine. Use a tool like bootstrap.build to create a bootstrap v4 theme. Export the bootstrap.min.css once you're done, and save the _variables.scss too.

If you installed Lemmy with Docker, save your theme file to ./volumes/lemmy-ui/extra_themes. For native installation (without Docker), themes are loaded by lemmy-ui from ./extra_themes folder. A different path can be specified with LEMMY_UI_EXTRA_THEMES_FOLDER environment variable.

After a theme is added, users can select it under /settings. Admins can set a theme as site default under /admin.

Federation

Lemmy has three types of federation:

  • Allowlist: Explicitly list instances to connect to.
  • BlockList: Explicitly list instances to not connect to. Federation is open to all other instances.
  • Open: Federate with all potential instances.

Federation is enabled by default. You can add allowed and blocked instances, by adding a comma-delimited list in your instance admin panel. IE to only federate with these instances, add: enterprise.lemmy.ml,lemmy.ml to the allowed instances section.

Lemmy uses the ActivityPub protocol (a W3C standard) to enable federation between different servers (often called instances). This is very similar to the way email works. For example, if you use gmail.com, then you can not only send mails to other gmail.com users, but also to yahoo.com, yandex.ru and so on. Email uses the SMTP protocol to achieve this, so you can think of ActivityPub as "SMTP for social media". The amount of different actions possible on social media (post, comment, like, share, etc) means that ActivityPub is much more complicated than SMTP.

As with email, ActivityPub federation happens only between servers. So if you are registered on enterprise.lemmy.ml, you only connect to the API of enterprise.lemmy.ml, while the server takes care of sending and receiving data from other instances (eg voyager.lemmy.ml). The great advantage of this approach is that the average user doesn't have to do anything to use federation. In fact if you are using Lemmy, you are likely already using it. One way to confirm is by going to a community or user profile. If you are on enterprise.lemmy.ml and you see a user like @nutomic@voyager.lemmy.ml, or a community like !main@ds9.lemmy.ml, then those are federated, meaning they use a different instance from yours.

One way you can take advantage of federation is by opening a different instance, like ds9.lemmy.ml, and browsing it. If you see an interesting community, post or user that you want to interact with, just copy its URL and paste it into the search of your own instance. Your instance will connect to the other one (assuming the allowlist/blocklist allows it), and directly display the remote content to you, so that you can follow a community or comment on a post. Here are some examples of working searches:

  • !main@lemmy.ml (Community)
  • @nutomic@lemmy.ml (User)
  • https://lemmy.ml/c/programming (Community)
  • https://lemmy.ml/u/nutomic (User)
  • https://lemmy.ml/post/123 (Post)
  • https://lemmy.ml/comment/321 (Comment)

You can see the list of linked instances by following the "Instances" link at the bottom of any Lemmy page.

Fetching communities

If you search for a community first time, 20 posts are fetched initially. Only if a least one user on your instance subscribes to the remote community, will the community send updates to your instance. Updates include:

  • New posts, comments
  • Votes
  • Post, comment edits and deletions
  • Mod actions

You can copy the URL of the community from the address bar in your browser and insert it in your search field. Wait a few seconds, the post will appear below. At the moment there is no loading indicator for the search, so wait a few seconds if it shows "no results".

Fetching posts

Paste the URL of a post into your Lemmy instance's search field. Wait a few seconds until the post appears. This will also fetch the community profile, and the profile of the post creator.

Fetching comments

If you find an interesting comment under a posting on another instance, you can find below the comment in the 3-dot-menu the link-symbol. Copy this link. It looks like https://lemmy.ml/post/56382/comment/40796. Remove the post/XXX part and put it into your search-bar. For this example, search for https://lemmy.ml/comment/40796. This comment, all parent comments, users and community and the corresponding post are fetched from the remote instance, if they are not known locally.

Sibling comments are not fetched! If you want more comments from older posts, you have to search for each of them as described above.

Troubleshooting

Different problems that can occur on a new instance, and how to solve them.

Many Lemmy features depend on a correct reverse proxy configuration. Make sure yours is equivalent to our nginx config.

General

Logs

For frontend issues, check the browser console for any error messages.

For server logs, run docker compose logs -f lemmy in your installation folder. You can also do docker compose logs -f lemmy lemmy-ui pictrs to get logs from different services.

If that doesn't give enough info, try changing the line RUST_LOG=error in docker-compose.yml to RUST_LOG=info or RUST_LOG=verbose, then do docker compose restart lemmy.

Creating admin user doesn't work

Make sure that websocket is working correctly, by checking the browser console for errors. In nginx, the following headers are important for this:

proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";

Rate limit error when many users access the site

Check that the headers X-Real-IP and X-Forwarded-For are sent to Lemmy by the reverse proxy. Otherwise, it will count all actions towards the rate limit of the reverse proxy's IP. In nginx it should look like this:

proxy_set_header X-Real-IP $remote_addr;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

Federation

Other instances can't fetch local objects (community, post, etc)

Your reverse proxy (eg nginx) needs to forward requests with header Accept: application/activity+json to the backend. This is handled by the following lines:

set $proxpass "http://0.0.0.0:{{ lemmy_ui_port }}";
if ($http_accept = "application/activity+json") {
set $proxpass "http://0.0.0.0:{{ lemmy_port }}";
}
if ($http_accept = "application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\"") {
set $proxpass "http://0.0.0.0:{{ lemmy_port }}";
}
proxy_pass $proxpass;

You can test that it works correctly by running the following commands, all of them should return valid JSON:

curl -H "Accept: application/activity+json" https://your-instance.com/u/some-local-user
curl -H "Accept: application/activity+json" https://your-instance.com/c/some-local-community
curl -H "Accept: application/activity+json" https://your-instance.com/post/123 # the id of a local post
curl -H "Accept: application/activity+json" https://your-instance.com/comment/123 # the id of a local comment

Fetching remote objects works, but posting/commenting in remote communities fails

Check that federation is allowed on both instances.

Also ensure that the time is accurately set on your server. Activities are signed with a timestamp, and will be discarded if it is off by more than 10 seconds.

Other instances don't receive actions reliably

Lemmy uses one queue per federated instance to send out activities. Search the logs for "Federation state" for summaries. Errors will also be logged.

For details, execute this SQL query:

select domain,currval('sent_activity_id_seq') as latest_id, last_successful_id,fail_count,last_retry from federation_queue_state
join instance on instance_id = instance.id order by last_successful_id asc;

You will see a table like the following:

domainlatest_idlast_successful_idfail_countlast_retry
toad.work68371966832351142023-07-12 21:42:22.642379+00
lemmy.deltaa.xyz6837196683719601970-01-01 00:00:00+00
battleangels.net6837196683719601970-01-01 00:00:00+00
social.fbxl.net6837196683719601970-01-01 00:00:00+00
mastodon.coloradocrest.net6837196683719601970-01-01 00:00:00+00

This will show you exactly which instances are up to date or not.

You can also use this website which will help you monitor this without admin rights, and also let others see it

https://phiresky.github.io/lemmy-federation-state/site

You don't receive actions reliably

Due to the lemmy queue, remove lemmy instances will be sending apub sync actions serially to you. If your server rate of processing them is slower than the rate the origin server is sending them, when visiting the lemmy-federation-state for the remote server, you'll see your instance in the "lagging behind" section.

Typically the speed at which you process an incoming action should be less than 100ms. If this is higher, this might signify problems with your database performance or your networking setup.

Note that even apub action ingestion speed which seems sufficient for most other instances, might become insufficient if the origin server is receiving actions from their userbase faster than you can process them. I.e. if the origin server receives 10 actions per second, but you can only process 8 actions per second, you'll inevitably start falling behind that one server only.

These steps might help you diagnose this.

Check processing time on the loadbalancer

Check how long a request takes to process on the backend. In haproxy for example, the following command will show you the time it takes for apub actions to complete

tail -f /var/log/haproxy.log | grep  "POST \/inbox"

See here for nginx

If these actions take more than 100ms, you might want to investigate deeper.

Check your Database performance

Ensure that it's not very high in CPU or RAM utilization.

Afterwards check for slow queries. If you regularly see common queries with high max and mean exec time, it might signify your database is struggling. The below SQL query will show you all queries (you will need pg_stat_statements enabled)

\x auto
SELECT user,query,max_exec_time,mean_exec_time,calls FROM pg_stat_statements WHERE max_exec_time > 10 AND CALLS > 100 ORDER BY max_exec_time DESC;

If you see very high time on inserts, you might want to consider disabling synchronous_commit to see if this helps.

Check your backend performance.

Like the DB, if the server where your lemmy rust backend is running is overloaded, you might see such an impact

Check your Network Layout

If your backend and database appear to be in good condition, it might be that your issue is network based.

One problem can occur is your backend and your database are not in the same server and are too far from each other in geographic location. Due to the amount of DB queries performed for each apub sync request, even a small amount of latency can quickly add up.

Check the latency between your rust backend and your DB using ping

ping your_database_ip

if the time you see if above 1-2ms, this can start causing such delays. In that case, you might want to consider moving your backend closer to your DB geographically, so that your latency is below 2ms

Note that your external loadbalancer(s) (if any) do not necessarily need to be closer to the DB, as they do not do multiple small DB requests.

Downgrading

If you upgraded your instance to a newer version (by mistake or planned) and need to downgrade it. Often you need to reverse database changes as well.

First you need to figure out what SQL changes happened between your upgraded version, and the one you're downgrading. Then in that diff, check which files were added in the migrations dir.

Let's say that for the migration you're doing, the following were added

2023-10-24-131607_proxy_links
2023-10-27-142514_post_url_content_type
2023-12-19-210053_tolerable-batch-insert-speed
2023-12-22-040137_make-mixed-sorting-directions-work-with-tuple-comparison
2024-01-05-213000_community_aggregates_add_local_subscribers
2024-01-15-100133_local-only-community
2024-01-22-105746_lemmynsfw-changes
2024-01-25-151400_remove_auto_resolve_report_trigger
2024-02-15-171358_default_instance_sort_type
2024-02-27-204628_add_post_alt_text
2024-02-28-144211_hide_posts

Each of these folders contains a down.sql file. We need to run that against our postgresql DB to rollback those DB changes.

  1. Stop your lemmy backend, and take a backup of your DB.
  2. Copy the migrations folder to your DB container or server
  3. Acquire a shell in your postgresql container or server and switch to the postgres user
  4. Run each relevant script with this command
    downfolder=2024-02-28-144211_hide_posts
    psql -d lemmy -a -f /path/to/migrations/${downfolder}/down.sql
    
    Alternatively, copy the content of the file and paste into a psql session
  5. You now need to clean the __diesel_schema_migrations table from the migration records, so that they will be correctly applied the next time you upgrade. You can use this command to sort them
    select * from __diesel_schema_migrations ORDER BY run_on ASC;
    
    You have to delete the entries in that table which match the current timestamp you applied them (This should typically be any time in the past few minutes)
    delete from __diesel_schema_migrations where version='20240228144211';
    
  6. You should now be able to start your lemmy in the previous version

Backup and Restore Guide

Docker and Ansible

When using docker or ansible, there should be a volumes folder, which contains both the database, and all the pictures. Copy this folder to the new instance to restore your data.

Full Database backup

To take a complete backup of the DB to a .sql.gz file, you can run:

docker compose exec postgres pg_dumpall -c -U lemmy | gzip > lemmy_dump_`date +%Y-%m-%d"_"%H_%M_%S`.sql.gz

For compression, you can use either gzip and gunzip, or xz and unxz.

A Sample backup script

#!/bin/sh
# DB Backup
ssh MY_USER@MY_IP "docker compose exec postgres pg_dumpall -c -U lemmy" | gzip > ~/BACKUP_LOCATION/INSTANCE_NAME_dump_`date +%Y-%m-%d"_"%H_%M_%S`.sql.gz

# Volumes folder Backup
rsync -avP -zz --rsync-path="sudo rsync" MY_USER@MY_IP:/LEMMY_LOCATION/volumes ~/BACKUP_LOCATION/FOLDERNAME

Restoring the DB

To restore, run:

docker compose up -d postgres

# Restore from the .sql.gz backup
gunzip < db_dump.sql  |  docker compose exec -T postgres psql -U lemmy

# Note: You may need to change the permissions on the postgres directory, depending on your system.
chown -R $USER volumes
docker compose restart postgres

# Continue with the startup
docker compose up -d

If you've accidentally already started the lemmy service, you need to clear out your existing database:

# Drop the existing DB
docker exec -i FOLDERNAME_postgres_1 psql -U lemmy -c "DROP SCHEMA public CASCADE; CREATE SCHEMA public;"

# This also might be necessary when doing a db import with a different password.
docker exec -i FOLDERNAME_postgres_1 psql -U lemmy -c "alter user lemmy with password 'bleh'"

Then run the restore commands above.

Changing your domain name

If you haven't federated yet, you can change your domain name in the DB. Warning: do not do this after you've federated, or it will break federation.

Get into psql for your docker:

docker compose exec postgres psql -U lemmy

-- Instance
update instance set domain = replace (domain, 'old_domain', 'new_domain');

-- Site
update site set actor_id = replace (actor_id, 'old_domain', 'new_domain');
update site set inbox_url = replace (inbox_url, 'old_domain', 'new_domain');
update site set sidebar = replace (sidebar, 'old_domain', 'new_domain');

-- Post
update post set ap_id = replace (ap_id, 'old_domain', 'new_domain');
update post set url = replace (url, 'old_domain', 'new_domain');
update post set body = replace (body, 'old_domain', 'new_domain');
update post set thumbnail_url = replace (thumbnail_url, 'old_domain', 'new_domain');

-- Comments
update comment set ap_id = replace (ap_id, 'old_domain', 'new_domain');
update comment set content = replace (content, 'old_domain', 'new_domain');

-- Private messages
update private_message set ap_id = replace (ap_id, 'old_domain', 'new_domain');
update private_message set content = replace (content, 'old_domain', 'new_domain');

-- User
update person set actor_id = replace (actor_id, 'old_domain', 'new_domain');
update person set inbox_url = replace (inbox_url, 'old_domain', 'new_domain');
update person set shared_inbox_url = replace (shared_inbox_url, 'old_domain', 'new_domain');
update person set bio = replace (bio, 'old_domain', 'new_domain');
update person set avatar = replace (avatar, 'old_domain', 'new_domain');
update person set banner = replace (banner, 'old_domain', 'new_domain');

-- Community
update community set actor_id = replace (actor_id, 'old_domain', 'new_domain');
update community set followers_url = replace (followers_url, 'old_domain', 'new_domain');
update community set inbox_url = replace (inbox_url, 'old_domain', 'new_domain');
update community set shared_inbox_url = replace (shared_inbox_url, 'old_domain', 'new_domain');
update community set moderators_url = replace (moderators_url, 'old_domain', 'new_domain');
update community set featured_url = replace (featured_url, 'old_domain', 'new_domain');
update community set description = replace (description, 'old_domain', 'new_domain');
update community set icon = replace (icon, 'old_domain', 'new_domain');
update community set banner = replace (banner, 'old_domain', 'new_domain');

--- Custom Emoji
update custom_emoji set image_url = replace (image_url, 'old_domain', 'new_domain');

More resources

  • https://stackoverflow.com/questions/24718706/backup-restore-a-dockerized-postgresql-database

Using Caddy as reverse proxy example

If you prefer to use Caddy instead of Nginx - you could use this template to fit into your needs:

(caddy-common) {
    encode gzip
    header {
        -Server
        Strict-Transport-Security "max-age=31536000; include-subdomains;"
        X-XSS-Protection "1; mode=block"
        X-Frame-Options "DENY"
        X-Content-Type-Options nosniff
        Referrer-Policy  no-referrer-when-downgrade
        X-Robots-Tag "none"
    }
}

lemmy-site.com {
        import caddy-common
        reverse_proxy   http://lemmy_lemmy-ui_1:1234
}

@lemmy {
        path    /api/*
        path    /pictrs/*
        path    /feeds/*
        path    /nodeinfo/*
        path    /.well-known/*
}

@lemmy-hdr {
        header Accept application/*
}

handle @lemmy {
        reverse_proxy   http://lemmy_lemmy_1:8536
}

handle @lemmy-hdr {
        reverse_proxy   http://lemmy_lemmy_1:8536
}

@lemmy-post {
        method POST
}

handle @lemmy-post {
        reverse_proxy   http://lemmy_lemmy_1:8536
}

Tor Hidden Services

This guide assumes Lemmy has been installed using the official Docker Compose method.

Note that federation is not currently supported over the Tor network. An existing Lemmy instance is required. This procedure will proxy Lemmy though Tor, but federation tasks are still handled by HTTPS on the open internet.

Tor ("The Onion Router") is software designed to circumvent censorship and prevent bad actors from monitoring your activity on the internet by encrypting and distributing network traffic through a decentralized pool of relay servers run by volunteers all over the world.

A Tor hidden service is only accessible through the Tor network using the .onion top-level domain with the official Tor Browser, or any client capable of communicating over a SOCKS5 proxy. Hosting a service on the Tor network is a good way to promote digital privacy and internet freedom.

Installing Tor

The official documentation suggests Ubuntu and Debian users install Tor from the deb.torproject.org repository because it always provides the latest stable release of the software.

Administrative Access is Required

Commands below are expected to be executed as the root user. To become root use sudo -i or su -.

With sudo:

sudo -i
Password: [authenticate with current user password]

With su:

su -
Password: [authenticate with root's password]

Note: To return to your account run: exit.

Verify your architecture is supported

The package repository only supports amd64, arm64, and i386 architectures.

dpkg --print-architecture

If your architecture is not supported you may want to consider installing Tor from source.

Install prerequisite packages

apt install -y apt-transport-https ca-certificates gpg lsb-release wget

Enable the deb.torproject.org repository

Configure apt to pull packages from deb.torproject.org.

bash -c 'dist=$(lsb_release -s -c); /bin/echo -e \
"deb [signed-by=/usr/share/keyrings/tor-archive-keyring.gpg] \
https://deb.torproject.org/torproject.org $dist main\n\
deb-src [signed-by=/usr/share/keyrings/tor-archive-keyring.gpg] \
https://deb.torproject.org/torproject.org $dist main" \
> /etc/apt/sources.list.d/tor.list'

Import deb.torproject.org's GPG signing key

wget -qO- https://deb.torproject.org/torproject.org/A3C4F0F979CAA22CDBA8F512EE8CBC9E886DDD89.asc \
    | gpg --dearmor \
    | tee /usr/share/keyrings/tor-archive-keyring.gpg >/dev/null

The signing key ensures the package retrieved from the server was created by deb.torproject.org.

Install tor

apt update && apt install -y tor

Creating a Tor hidden service

Create a new hidden service directory:

mkdir /var/lib/tor/hidden_lemmy_service

Append the following to /etc/tor/torrc to tie the hidden service directory to the tor daemon:

HiddenServiceDir /var/lib/tor/hidden_lemmy_service/
HiddenServicePort 80 127.0.0.1:10080

HiddenServiceDir [path] is where tor will store data related to the hidden service, and HiddenServicePort [hidden_service_port] [host_ip:port] binds a port on the host to a hidden service port on the Tor network.

Enable and start the Tor daemon

systemctl enable --now tor

At startup tor daemon will automatically populate /var/lib/tor/hidden_lemmy_service/ with encryption keys, certificates, and assign a hostname for the new service.

Determine your hidden service's hostname

cat /var/lib/tor/hidden_lemmy_service/hostname

The .onion address contained in this file will be referred to as HIDDEN_SERVICE_ADDR from here on.

Configure your existing Lemmy instance

Docker compose

Forward port 10080 from the proxy container to the hidden service port 127.0.0.1:10080. This exposes 10080/tcp to the local host, and will not be directly accessible from the internet. For context "80:80" binds port 80/tcp (HTTP) to 0.0.0.0:80 on the host. Unless a firewall is configured to block incoming traffic to 80 this will be exposed to other hosts on the local area network (LAN) and/or the open internet.

docker-compose.yml

services:
  # ...
  proxy:
    # ...
    ports:
      - "80:80"
      - "443:443"
      - "127.0.0.1:10080:10080"

Configure NGINX

Append a new server {...} block to handle tor traffic, and add the Onion-Location header to the SSL encrypted server exposed to the internet. This header informs Tor Browser users that an equivalent .onion site exists on the Tor network by displaying an icon next to the address bar.

nginx.conf

worker_processes 1;
events {
    worker_connections 1024;
}

http {
    # Original configuration listening on port 80
    server {
        listen 80;
        # ...
    }

    # Original configuration listening on port 443
    server {
        listen 443;
        # ...
        location / {
            # Handle Tor Browser's ".onion" link detection
            add_header Onion-Location "http://HIDDEN_SERVICE_ADDR$request_uri" always;
            # ...
        }
    }

    # Establish a rate limit for the hidden service address
    limit_req_zone $binary_remote_addr zone=HIDDEN_SERVICE_ADDR_ratelimit:10m rate=1r/s;

    # Add tor-specific upstream aliases as a visual aid to
    # avoid editing the incorrect server block in the future
    upstream lemmy-tor {
        server "lemmy:8536";
    }
    upstream lemmy-ui-tor {
        server "lemmy-ui:1234";
    }

    # Add a copy of your current internet-facing configuration with
    # "listen" and "server_listen" modified to send all traffic
    # over the Tor network, incorporating the visual upstream aliases
    # above.
    server {
        # Tell nginx to listen on the hidden service port
        listen 10080;

        # Set server_name to the contents of the file:
        # /var/lib/tor/hidden_lemmy_service/hostname
        server_name HIDDEN_SERVICE_ADDR;

        # Hide nginx version
        server_tokens off;

        # Enable compression for JS/CSS/HTML bundle, for improved client load times.
        # It might be nice to compress JSON, but leaving that out to protect against potential
        # compression+encryption information leak attacks like BREACH.
        gzip on;
        gzip_types text/css application/javascript image/svg+xml;
        gzip_vary on;

        # Various content security headers
        add_header Referrer-Policy "same-origin";
        add_header X-Content-Type-Options "nosniff";
        add_header X-Frame-Options "DENY";
        add_header X-XSS-Protection "1; mode=block";

        # Upload limit for pictrs
        client_max_body_size 20M;

        # frontend
        location / {
            # distinguish between ui requests and backend
            # don't change lemmy-ui or lemmy here, they refer to the upstream definitions on top
            set $proxpass "http://lemmy-ui-tor";

            if ($http_accept = "application/activity+json") {
                set $proxpass "http://lemmy-tor";
            }
            if ($http_accept = "application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\"") {
                set $proxpass "http://lemmy-tor";
            }
            if ($request_method = POST) {
                set $proxpass "http://lemmy-tor";
            }
            proxy_pass $proxpass;

            rewrite ^(.+)/+$ $1 permanent;

            # Send actual client IP upstream
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header Host $host;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        }

        # backend
        location ~ ^/(api|feeds|nodeinfo|.well-known) {
            proxy_pass "http://lemmy-tor";
            proxy_http_version 1.1;
            proxy_set_header Upgrade $http_upgrade;
            proxy_set_header Connection "upgrade";

            # Rate limit
            limit_req zone=HIDDEN_SERVICE_ADDR_ratelimit burst=30 nodelay;

            # Add IP forwarding headers
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header Host $host;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        }

        # pictrs only - for adding browser cache control.
        location ~ ^/(pictrs) {
            # allow browser cache, images never update, we can apply long term cache
            expires 120d;
            add_header Pragma "public";
            add_header Cache-Control "public";

            proxy_pass "http://lemmy-tor";
            proxy_http_version 1.1;
            proxy_set_header Upgrade $http_upgrade;
            proxy_set_header Connection "upgrade";

            # Rate limit
            limit_req zone=HIDDEN_SERVICE_ADDR_ratelimit burst=30 nodelay;

            # Add IP forwarding headers
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header Host $host;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        }

        # Redirect pictshare images to pictrs
        location ~ /pictshare/(.*)$ {
            return 301 /pictrs/image/$1;
        }
    }

    # ...
}

Apply the configuration(s)

Restart all services associated with your Lemmy instance:

docker compose down
docker compose up -d

Test connectivity over Tor

Using torsocks, verify your hidden service is available on the Tor network.

torsocks curl -vI http://HIDDEN_SERVICE_ADDR
*   Trying 127.*.*.*:80...
* Connected to HIDDEN_SERVICE_ADDR (127.*.*.*) port 80 (#0)
> HEAD / HTTP/1.1
> Host: HIDDEN_SERVICE_ADDR
> User-Agent: curl/7.76.1
> Accept: */*
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 200 OK
HTTP/1.1 200 OK
< Server: nginx
Server: nginx
< Date: Wed, 07 Jun 2023 17:06:00 GMT
Date: Wed, 07 Jun 2023 17:06:00 GMT
< Content-Type: text/html; charset=utf-8
Content-Type: text/html; charset=utf-8
< Content-Length: 98487
Content-Length: 98487
< Connection: keep-alive
Connection: keep-alive
< Vary: Accept-Encoding
Vary: Accept-Encoding
< X-Powered-By: Express
X-Powered-By: Express
< Content-Security-Policy: default-src 'self'; manifest-src *; connect-src *; img-src * data:; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; form-action 'self'; base-uri 'self'; frame-src *
Content-Security-Policy: default-src 'self'; manifest-src *; connect-src *; img-src * data:; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; form-action 'self'; base-uri 'self'; frame-src *
< ETag: W/"180b7-EC9iFYAIlbnN8zHCayBwL3wAm64"
ETag: W/"180b7-EC9iFYAIlbnN8zHCayBwL3wAm64"
< Referrer-Policy: same-origin
Referrer-Policy: same-origin
< X-Content-Type-Options: nosniff
X-Content-Type-Options: nosniff
< X-Frame-Options: DENY
X-Frame-Options: DENY
< X-XSS-Protection: 1; mode=block
X-XSS-Protection: 1; mode=block

<
* Connection #0 to host HIDDEN_SERVICE_ADDR left intact

Logging behavior

Hidden service traffic will appear to originate from the lemmyexternalproxy docker network instead of an internet IP. Docker's default network address pool is 172.17.0.0/16.

docker compose logs -f proxy
lemmy-proxy-1  | 172.*.0.1 - -  # ...
lemmy-proxy-1  | 172.*.0.1 - -  # ...
lemmy-proxy-1  | 172.*.0.1 - -  # ...

Prometheus

Lemmy supports the export of metrics in the Prometheus format. This document outlines how to enable the feature and begin collecting metrics.

Configuration

Configuration of the Prometheus endpoint is contained under the optional prometheus object in the lemmy.hjson file. In this configuration block, the bind address and port of the Prometheus metrics server can be configured.

By default, it will serve on 127.0.0.1:10002. If running inside a container, it should instead bind on all addresses as below.

prometheus: {
  bind: "0.0.0.0"
  port: 10002
}

Scrape

After Lemmy has been deployed, test that it is serving properly (substitute the correct hostname if not running locally).

curl localhost:10002/metrics

Prometheus Configuration

Below is a minimal configuration on the Prometheus server's side that will scrape the metrics from Lemmy.

global:
scrape_configs:
  - job_name: lemmy
    scrape_interval: 1m
    static_configs:
      - targets:
          - localhost:10002

Then run a Prometheus server to scrape the metrics.

docker run -p 9090:9090 -v prometheus.yml:/etc/prometheus/prometheus.yml prom/prometheus

If Lemmy was deployed using the docker-compose.yml, then use the following configurations instead.

global:
scrape_configs:
  - job_name: lemmy
    scrape_interval: 1m
    static_configs:
      - targets:
          - lemmy:10002

Run the Prometheus server inside the same docker network that Lemmy is running in. This can be discovered by running docker inspect on the lemmy container.

docker run -p 9090:9090 -v prometheus.yml:/etc/prometheus/prometheus.yml --network $LEMMY_NETWORK prom/prometheus

Example

Below is an example of what is returned from the /metrics endpoint. Each combination of endpoint, method, and status will produce a histogram (lemmy_api_http_requests_duration_seconds_*), so for brevity the output was reduced to a single endpoint.

For the HTTP metrics, this is saying that /api/v3/post/list received 12 GET requests that returned a 200 HTTP code. Cumulatively, these requests took 0.383 seconds. 5 requests completed between 0.01 and 0.025 seconds. 5 more completed between 0.025 and 0.05 seconds. And the remaining 2 requests completed between 0.05 and 0.1 seconds.

lemmy_api_http_requests_duration_seconds_bucket{endpoint="/api/v3/post/list",method="GET",status="200",le="0.005"} 0
lemmy_api_http_requests_duration_seconds_bucket{endpoint="/api/v3/post/list",method="GET",status="200",le="0.01"} 0
lemmy_api_http_requests_duration_seconds_bucket{endpoint="/api/v3/post/list",method="GET",status="200",le="0.025"} 5
lemmy_api_http_requests_duration_seconds_bucket{endpoint="/api/v3/post/list",method="GET",status="200",le="0.05"} 10
lemmy_api_http_requests_duration_seconds_bucket{endpoint="/api/v3/post/list",method="GET",status="200",le="0.1"} 12
lemmy_api_http_requests_duration_seconds_bucket{endpoint="/api/v3/post/list",method="GET",status="200",le="0.25"} 12
lemmy_api_http_requests_duration_seconds_bucket{endpoint="/api/v3/post/list",method="GET",status="200",le="0.5"} 12
lemmy_api_http_requests_duration_seconds_bucket{endpoint="/api/v3/post/list",method="GET",status="200",le="1"} 12
lemmy_api_http_requests_duration_seconds_bucket{endpoint="/api/v3/post/list",method="GET",status="200",le="2.5"} 12
lemmy_api_http_requests_duration_seconds_bucket{endpoint="/api/v3/post/list",method="GET",status="200",le="5"} 12
lemmy_api_http_requests_duration_seconds_bucket{endpoint="/api/v3/post/list",method="GET",status="200",le="10"} 12
lemmy_api_http_requests_duration_seconds_bucket{endpoint="/api/v3/post/list",method="GET",status="200",le="+Inf"} 12
lemmy_api_http_requests_duration_seconds_sum{endpoint="/api/v3/post/list",method="GET",status="200"} 0.3834808429999999
lemmy_api_http_requests_duration_seconds_count{endpoint="/api/v3/post/list",method="GET",status="200"} 12

lemmy_api_http_requests_total{endpoint="/api/v3/post/list",method="GET",status="200"} 12

# HELP lemmy_db_pool_available_connections Number of available connections in the pool
# TYPE lemmy_db_pool_available_connections gauge
lemmy_db_pool_available_connections 2
# HELP lemmy_db_pool_connections Current number of connections in the pool
# TYPE lemmy_db_pool_connections gauge
lemmy_db_pool_connections 2
# HELP lemmy_db_pool_max_connections Maximum number of connections in the pool
# TYPE lemmy_db_pool_max_connections gauge
lemmy_db_pool_max_connections 5

# HELP process_cpu_seconds_total Total user and system CPU time spent in seconds.
# TYPE process_cpu_seconds_total counter
process_cpu_seconds_total 14
# HELP process_max_fds Maximum number of open file descriptors.
# TYPE process_max_fds gauge
process_max_fds 1073741816
# HELP process_open_fds Number of open file descriptors.
# TYPE process_open_fds gauge
process_open_fds 91
# HELP process_resident_memory_bytes Resident memory size in bytes.
# TYPE process_resident_memory_bytes gauge
process_resident_memory_bytes 75603968
# HELP process_start_time_seconds Start time of the process since unix epoch in seconds.
# TYPE process_start_time_seconds gauge
process_start_time_seconds 1688487611
# HELP process_threads Number of OS threads in the process.
# TYPE process_threads gauge
process_threads 37
# HELP process_virtual_memory_bytes Virtual memory size in bytes.
# TYPE process_virtual_memory_bytes gauge
process_virtual_memory_bytes 206000128

Scaling Lemmy horizontally

This is a collection of notes on scaling different Lemmy components horizontally. This is not meant as a step-by-step guide, rather as general tips and knowledge that might be useful to somebody who is already familiar with and horizontal scaling concepts and best practices. If you don't already know about concepts like load balancing, object storage, and reverse proxies, then this document will probably be hard (or impossible) to understand.

Note: scaling Lemmy this way is not necessary, and for small instances, the added overhead most likely outweighs any benefits.

Why scale horizontally?

  • If one of your Lemmy servers dies for any reason, your instance can still remain operational
  • Having multiple Lemmy servers allows you to do rolling upgrades (no downtime needed to upgrade Lemmy!)
  • Horizontal scaling provides additional flexibility in how you upgrade your infrastructure
  • As of Lemmy version 0.18.2, Lemmy appears to be able to use the same resources more efficiently if they are split between multiple Lemmy processes

Breakdown of Lemmy components

See Lemmy components for a high level overview of what each component does.

Lemmy-ui

Lemmy-ui can be horizontally scaled, but make sure you read the following section for information about rolling upgrades.

Rolling upgrades

In general, there is nothing preventing you from running multiple load balanced Lemmy-ui servers in parallel, but without some custom configuration, rolling upgrades will break Lemmy for your users! This is because by default, Lemmy-ui will serve static files through the express process.

Consider the scenario where you have 2 Lemmy-ui servers, one is running on version 1, and the other has just come online running version 2. A user opening your Lemmy in their browser might have their front page html request routed to version 1. This html will contain a reference to many other static files which are specific to version 1. Most likely, some of these requests will get routed to the server running version 2. The files will not exist on this server, the server will respond with 404, and the UI will remain in a broken state.

There are a few ways to work around this issue:

  1. The safest option is to ensure that all servers are able to serve static files for all currently active Lemmy-ui versions.

    • There are many ways to achieve this, the most obvious one being to upload your static files to object storage, and have reverse proxies route static file requests to your static files bucket.
    • Here's one possible example of how to do this with nginx:
    location /static {
       expires 1y;
       add_header Cache-Control "public";
       proxy_pass https://${bucket_fqdn};
    
       limit_except GET {
           deny  all;
       }
    
       proxy_intercept_errors on;
       # invalid keys result in 403
       error_page 403 =404 /@404static;
    }
    

    Replace ${bucket_fqdn} with the URL to your bucket. With this setup, you should upload the contents of the lemmy-ui dist/ directory to a "directory" named static/$LEMMY_UI_HASH in your bucket, where the hash is calculated from the commit you are deploying: export LEMMY_UI_HASH=$(git rev-parse --short HEAD). Note that this setup requires public acl on uploaded files, so it's recommended to limit access to your bucket with an IP allowlist policy.

  2. An alternative option is to configure sticky sessions in your load balancer. This will ensure that subsequent requests all end up on the same Lemmy-ui server, and as long as your sticky sessions last longer than your upgrade process, most clients should remain functional.

  3. Similarly to sticky sessions, another possibility is to configure ip hash based load balancing.

  4. There is always the option to just employ downtime and upgrade all servers at once (but that's not very fun!)

Lemmy_server

Lemmy_server can be horizontally scaled, with a few caveats.

Here's a quick example on how you could start 3 web servers, 3 federation servers and one scheduled task process:

# scheduled tasks
lemmy_server --disable-http-server --disable-activity-sending

# 3 http servers
lemmy_server --disable-activity-sending --disable-scheduled-tasks
lemmy_server --disable-activity-sending --disable-scheduled-tasks
lemmy_server --disable-activity-sending --disable-scheduled-tasks

# 3 servers for sending out federation activities
lemmy_server --disable-http-server --disable-scheduled-tasks --federate-process-index=1 --federate-process-count=3
lemmy_server --disable-http-server --disable-scheduled-tasks --federate-process-index=2 --federate-process-count=3
lemmy_server --disable-http-server --disable-scheduled-tasks --federate-process-index=3 --federate-process-count=3

Scheduled tasks

By default, a Lemmy_server process will run background scheduled tasks, which must be run only on one server. Launching multiple processes with the default configuration will result in multiple duplicated scheduled tasks all starting at the same moment and trying to do the same thing at once.

To solve this, Lemmy must be started with the --disable-scheduled-tasks flag on all but one instance. In general, there are two approaches:

  1. Run all your load balanced Lemmy servers with the --disable-scheduled-tasks flag, and run one additional Lemmy server without this flag which is not in your load balancer and does not accept any HTTP traffic.
  2. Run one load balanced Lemmy server without the flag, and all other load balanced servers with the flag.

Federation queue

The persistent federation queue (since 0.19) is split by federated domain and can be processed in equal-size parts run in separate processes. To split the queue up into N processes numbered 1...N, use the arguments --federate-process-index=i --federate-process-count=N on each. It is important that each index is is given to exactly one process, otherwise you will get undefined behaviour (missing, dupe federation, crashes).

Federation processes can be started and stopped at will. They will restart federation to each instance from the last transmitted activity regardless of downtime.

Rolling upgrades

For most versions, rolling releases have been completely OK, but there have been some cases where a database migration slips in which is NOT backwards compatible (causing any servers running the older version of Lemmy to potentially start generating errors). To be safe, you should always first take a look at what database migrations are included in a new version, and evaluate whether the changes are for sure safe to apply with running servers.

Note that even if there are no backwards incompatible migrations between immediate version upgrades (version 1 -> 2 is safe and 2 -> 3 is safe), doing bigger upgrades might cause these same migrations to become backwards incompatible (a server running version 1 might not be compatible with migrations from version 3 in this scenario). It is generally best to only upgrade one version at a time!.

For the scheduled task process, it shouldn't cause any major issues to have two servers running in parallel for a short duration during an upgrade, but shutting the old one down before starting the new one will always be safer.

Pict-rs

Pict-rs does not scale horizontally as of writing this document. This is due to a dependency on a Sled database, which is a disk based database file.

The developer of pict-rs has mentioned plans to eventually add Postgres support (which should in theory enable horizontal scaling), but work on this has not yet started.

Note that by caching pict-rs images (for example, with nginx, or with a CDN), you can really minimize load on the pict-rs server!

Other tips

  1. Your lemmy-ui servers need to access your backend as well. If you don't want a complicated setup with multiple load balancers, you could just have each of your servers contain nginx, lemmy-ui, and lemmy_server, and just load balance to an nginx on each of your servers

  2. Make sure you're passing the real client ip address to Lemmy, otherwise Lemmy's built in rate limiter will trigger very often (if it rate limits based on your load balancer's IP address, for example). With nginx, you should use something like this in your nginx.conf:

    real_ip_recursive on;
    real_ip_header X-Forwarded-For;
    set_real_ip_from <your load balancer ip here>;
    
  3. The internal Lemmy load balancer works based on an in-memory storage of ip addresses. That means that by adding more Lemmy servers, you are effectively making your rate limits less strict. If you rely on the built in limiter, make sure to adjust the limits accordingly!

Contributing

Lemmy is an open source project and relies on community contributions to get better. Development happens mainly in the Lemmy Github repositories. Communication is done over Matrix.

These are the main repositories which are relevant for contributors:

  • lemmy: The backend which is the core of Lemmy. It implements SQL queries, provides the API and handles ActivityPub federation. Additionally it sends emails and provides RSS feeds. Written in Rust with actix-web and diesel. The issue tracker is also used for general enhancements which affect multiple repositories.
  • lemmy-ui: The main frontend for Lemmy. It provides the user interface that you see when viewing a Lemmy instance. Written in Typescript and CSS with the Inferno framework.
  • lemmy-ansible: Automated installation method which is recommended for users without technical knowledge.
  • joinlemmy-site: Source code for the official project website join-lemmy.org. Landing page for new users which includes general information and a list of instances.
  • lemmy-js-client: Client for the Lemmy API which is used by lemmy-ui. Can also be used by other projects to get started more easily.
  • activitypub-federation-rust: High-level framework for ActivityPub federation in Rust. Was originally part of the Lemmy code, but was extracted and made more generic so that other projects can use it too.

There are many different ways to contribute, depending on your skills and interests:

Reporting issues

You may notice different problems or potential improvements when using Lemmy. You can report them in the relevant repository. Make sure to first search the issue tracker to avoid duplicates. When creating the issue, fill in the template and include as much relevant information as possible.

Translating

Use Weblate to help translate Lemmy into different languages.

Programming and Design

You can open a pull request to make changes to Lemmy. In case of bigger contributions it is recommended to discuss it in an issue first. See the next sections for instructions to compile and develop the projects.

Donating

Besides contributing with your time, you can also consider making a donation to support the developers.

Local Development

Install build requirements

Install the latest Rust version using rustup.

Debian-based distro:

sudo apt install git cargo libssl-dev pkg-config libpq-dev curl

Arch-based distro:

sudo pacman -S git cargo openssl pkg-config postgresql-libs curl

macOS:

Install Homebrew if you don't already have it installed. Then install Node and pnpm.

brew install node

Install pnpm

The install instructions are here, or you can run

npm install -g pnpm

Setup PostgreSQL database

Debian-based distro:

sudo apt install postgresql
sudo systemctl start postgresql

Arch-based distro:

sudo pacman -S postgresql
sudo systemctl start postgresql
# If the systemctl command errors, then run the following
sudo mkdir /var/lib/postgres/data
sudo chown postgres:postgres /var/lib/postgres/data
sudo -u postgres initdb -D /var/lib/postgres/data
sudo systemctl start postgresql

macOS:

brew install postgresql
brew services start postgresql
$(brew --prefix postgresql)/bin/createuser -s postgres

Either execute scripts/db-init.sh, or manually initialize the postgres database:

psql -c "create user lemmy with password 'password' superuser;" -U postgres
psql -c 'create database lemmy with owner lemmy;' -U postgres
export LEMMY_DATABASE_URL=postgres://lemmy:password@localhost:5432/lemmy

Get the code

Clone frontend and backend code to local folders. Be sure to include --recursive to initialize git submodules.

git clone https://github.com/LemmyNet/lemmy.git --recursive
git clone https://github.com/LemmyNet/lemmy-ui.git --recursive

Backend development

Use cargo check to find compilation errors. To start the Lemmy backend, run cargo run. It will bind to 0.0.0.0:8536.

After making changes, you need to format the code with cargo +nightly fmt --all and run the linter with ./scripts/lint.sh.

Frontend development

Install dependencies by running pnpm i. Then run pnpm dev to launch lemmy-ui locally. It automatically connects to the Lemmy backend on localhost:8536. Open localhost:1234 in your browser. The project is rebuilt automatically when you change any files.

Note that this setup doesn't support image uploads. If you want to test those, you need to use the Docker development.

Tests

Run Rust unit tests:

./scripts/test.sh

To run federation/API tests, first add the following lines to /etc/hosts:

127.0.0.1       lemmy-alpha
127.0.0.1       lemmy-beta
127.0.0.1       lemmy-gamma
127.0.0.1       lemmy-delta
127.0.0.1       lemmy-epsilon

Then run the following script:

cd api_tests
./run-federation-test.sh

Docker Development

Dependencies

Debian-based distro:

sudo apt install git docker-compose
sudo systemctl start docker
git clone https://github.com/LemmyNet/lemmy --recursive

Arch-based distro:

sudo pacman -S git docker-compose
sudo systemctl start docker

Get the code with submodules:

git clone https://github.com/LemmyNet/lemmy --recursive

Building

Use these commands to create a custom container based on your local branch and tagged accordingly.

This is useful if you want to modify the source code of your instance to add some extra functionalities which are not available in the main release.

sudo docker build . -f docker/Dockerfile --build-arg RUST_RELEASE_MODE=release -t "lemmy:${git rev-parse --abbrev-ref HEAD}"

Build Troubleshooting

In case the build fails, the following might help resolve it

Translations missing

If you see an error like this

Error: FileRead { file: "translations/email/en.json", source: Os { code: 2, kind: NotFound, message: "No such file or directory" } }

Try these commands

git submodule init && git submodule update

Then try building again

Running custom build on your server

If you want a custom docker build to run on your instance via docker, you don't need to upload to a container repository, you can upload directly from your PC through ssh.

The following commands will copy the file to your instance and then load it onto your server's container registry

LEMMY_SRV=lemmy.example.com # Add the FQDN, IP or hostname of your lemmy server here
# We store in /tmp to avoid putting it in our local branch and committing it by mistake
sudo docker save -o /tmp/customlemmy.tar lemmy:${git rev-parse --abbrev-ref HEAD}
# We change permissios to allow our normal user to read the file as root might not have ssh keys
sudo chown ${whoami} /tmp/${git rev-parse --abbrev-ref HEAD}
scp /tmp/customlemmy.tar ${LEMMY_SRV}:
ssh ${LEMMY_SRV}
# This command should be run while in your lemmy server as the user you uploaded
sudo docker load -i ${HOME}/customlemmy.tar

After the container is in your registry, simply change the docker-compose to have your own tag in the image key

image: lemmy:your_branch_name

Finally, reinitiate the container

docker-compose up -d

You should now be running your custom docker container.

Running

cd docker
./docker_update.sh

Then open localhost:1236 in your browser.

Building with Docker is relatively slow. To get faster builds you need to use local development instead.

Federation Development

The federation setup allows starting a few local Lemmy instances, to test federation functionality. You can start it with the following commands:

cd docker/federation
./start-local-instances.bash

The federation test sets up 5 instances:

InstanceAdmin usernameLocationNotes
lemmy-alphalemmy_alpha127.0.0.1:8540federated with all other instances
lemmy-betalemmy_beta127.0.0.1:8550federated with all other instances
lemmy-gammalemmy_gamma127.0.0.1:8560federated with all other instances
lemmy-deltalemmy_delta127.0.0.1:8570only allows federation with lemmy-beta
lemmy-epsilonlemmy_epsilon127.0.0.1:8580uses blocklist, has lemmy-alpha blocked

You can log into each using the instance name, and lemmylemmy as the password, IE (lemmy_alpha, lemmylemmy).

To start federation between instances, visit one of them and search for a user, community or post, like this. Note that the Lemmy backend runs on a different port than the frontend, so you have to increment the port number from the URL bar by one.

  • http://lemmy-gamma:8561/u/lemmy-gamma
  • http://lemmy-alpha:8541/c/main
  • http://lemmy-beta:8551/post/3

API

Lemmy has an HTTP API for clients and frontends. See the API documentation for more information. Instead of using the API directly you can use one of the existing libraries. You can either interact with a local development instance via http://localhost:8536, or connect to a production instance. The following instances are available for testing purposes:

  • https://enterprise.lemmy.ml/
  • https://ds9.lemmy.ml/
  • https://voyager.lemmy.ml/

More clearly arranged, unofficial API documentation is available at:

Curl Examples

GET example

The current API {version} is here.

curl "https://lemmy.ml/api/{version}/community/list?sort=Hot"`

POST example

curl -i -H \
"Content-Type: application/json" \
-X POST \
-d '{
  "comment_id": 374,
  "score": 1,
  "auth": eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpZCI6MiwiaXNzIjoidGVzdC5sZW1teS5tbCJ9.P77RX_kpz1a_geY5eCp29sl_5mAm-k27Cwnk8JcIZJk
}' \
https://lemmy.ml/api/{version}/comment/like

RSS/Atom feeds

  • All - /feeds/all.xml?sort=Hot
  • Community - /feeds/c/community-name.xml?sort=Hot
  • User - /feeds/u/user-name.xml?sort=Hot

Images

Lemmy forwards image requests to a locally running Pictrs.

GET /pictrs/image/{filename}?format={webp, jpg, ...}&thumbnail={96}

Format and thumbnail are optional.

Create (request)

Uploaded content must be valid multipart/form-data with an image array located within the images[] key.

POST /pictrs/image

Create (response)

{
  "files": [
    {
      "delete_token": "{token}",
      "file": "{file}.jpg"
    }
  ],
  "msg": "ok"
}

Delete

GET /pictrs/image/delete/{delete_token}/{file}

Rust API

If you want to develop a Rust application which interacts with Lemmy, you can directly pull in the relevant API structs. This uses the exact API structs used in Lemmy, but with most heavyweight dependencies disabled. API paths or HTTP client are not included, so you need to handle those aspects manually. To get started, run cargo add lemmy_api_common in your repository. You can then use the following code to make an API request:

#![allow(unused)]
fn main() {
use lemmy_api_common::post::{GetPosts, GetPostsResponse};
use lemmy_db_schema::{ListingType, SortType};
use ureq::Agent;

pub fn list_posts() -> GetPostsResponse {
    let params = GetPosts {
        type_: Some(ListingType::Local),
        sort: Some(SortType::New),
        ..Default::default()
    };
    Agent::new()
        .get("https://lemmy.ml/api/v3/post/list")
        .send_json(&params).unwrap()
        .into_json().unwrap()
}
}

You can also look at the following real-world projects as examples:

Creating a Custom Frontend

The Lemmy backend and frontend are completely separate projects. This creates a lot of potential for alternative frontends which can change much of the design and user experience of Lemmy. For example, it is possible to create a frontend in the style of a traditional forum like phpBB, a question-and-answer site like Stack Overflow, a blogging platform or an image gallery. This way you don't have to write any SQL queries, federation logic, API code and so on, but can use the proven implementation from Lemmy. It is also possible to run multiple frontends for a single Lemmy instance.

Development

The easiest way to get started is by forking one of the existing frontends. But you can also create a new frontend from scratch. In any case the principle is the same: bind to a port to serve user requests, and connect to the API of a Lemmy instance as described above to fetch data.

Translations

You can add the lemmy-translations repository to your project as a git submodule. That way you can take advantage of same translations used in the official frontend, and you will also receive new translations contributed via Weblate.

Rate limiting

Lemmy does rate limiting for many actions based on the client IP. But if you make any API calls on the server side (e.g. in the case of server-side rendering, or javascript pre-rendering), Lemmy will take the IP of the Docker container. Meaning that all requests come from the same IP, and get rate limited much earlier. To avoid this problem, you need to pass the actual client IP via Forwarded or X-Forwarded-For HTTP header.

Federation

Lemmy uses the ActivityPub protocol for communication between servers. If you are unfamiliar with the protocol, you can start by reading the resource links. This document explains how to interact with it from other projects.

In Lemmy we use some specific terms to refer to ActivityPub items. They are essentially our specific implementations of well-known ActivityPub concepts:

  • Community: Group
  • User: Person
  • Post: Page
  • Comment: Note

Almost every action in Lemmy happens inside a group. The Federation Enhancement Proposal Group Federation gives a high-level overview how this works. The generic federation logic is implemented in the activitypub-federation library. It can also be used by other Rust projects.

Sometimes you will see a notation like Create/Note. This refers to a Create activity with a Note as object.

Below are explanations and examples for all actors, objects and activities from Lemmy. These include many optional fields which you can safely ignore.

If you have trouble to make your project federate with Lemmy, feel free to open an issue. Make sure to include the specific federation messages you are sending, and any errors returned by Lemmy.

Context

[
  "https://www.w3.org/ns/activitystreams",
  "https://w3id.org/security/v1",
  {
    "lemmy": "https://join-lemmy.org/ns#",
    "litepub": "http://litepub.social/ns#",
    "pt": "https://joinpeertube.org/ns#",
    "sc": "http://schema.org/",
    "ChatMessage": "litepub:ChatMessage",
    "commentsEnabled": "pt:commentsEnabled",
    "sensitive": "as:sensitive",
    "matrixUserId": "lemmy:matrixUserId",
    "postingRestrictedToMods": "lemmy:postingRestrictedToMods",
    "removeData": "lemmy:removeData",
    "stickied": "lemmy:stickied",
    "moderators": {
      "@type": "@id",
      "@id": "lemmy:moderators"
    },
    "expires": "as:endTime",
    "distinguished": "lemmy:distinguished",
    "language": "sc:inLanguage",
    "identifier": "sc:identifier"
  }
]

The context is identical for all activities and objects.

Actors

Community

An automated actor. Users can send posts or comments to it, which the community forwards to its followers in the form of Announce.

{
  "id": "https://enterprise.lemmy.ml/c/tenforward",
  "type": "Group",
  "preferredUsername": "main",
  "name": "Ten Forward",
  "summary": "<p>Lounge and recreation facility</p>\n<hr />\n<p>Welcome to the <a href=\"https://memory-alpha.fandom.com/wiki/USS_Enterprise_(NCC-1701-D)\">Enterprise</a>!.</p>\n",
  "source": {
    "content": "Lounge and recreation facility\n\n---\n\nWelcome to the [Enterprise](https://memory-alpha.fandom.com/wiki/USS_Enterprise_(NCC-1701-D))!.",
    "mediaType": "text/markdown"
  },
  "sensitive": false,
  "icon": {
    "type": "Image",
    "url": "https://enterprise.lemmy.ml/pictrs/image/waqyZwLAy4.webp"
  },
  "image": {
    "type": "Image",
    "url": "https://enterprise.lemmy.ml/pictrs/image/Wt8zoMcCmE.jpg"
  },
  "inbox": "https://enterprise.lemmy.ml/c/tenforward/inbox",
  "followers": "https://enterprise.lemmy.ml/c/tenforward/followers",
  "attributedTo": "https://enterprise.lemmy.ml/c/tenforward/moderators",
  "featured": "https://enterprise.lemmy.ml/c/tenforward//featured",
  "postingRestrictedToMods": false,
  "endpoints": {
    "sharedInbox": "https://enterprise.lemmy.ml/inbox"
  },
  "outbox": "https://enterprise.lemmy.ml/c/tenforward/outbox",
  "publicKey": {
    "id": "https://enterprise.lemmy.ml/c/tenforward#main-key",
    "owner": "https://enterprise.lemmy.ml/c/tenforward",
    "publicKeyPem": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAzRjKTNtvDCmugplwEh+g\nx1bhKm6BHUZfXfpscgMMm7tXFswSDzUQirMgfkxa9ubfr1PDFKffA2vQ9x6CyuO/\n70xTafdOHyV1tSqzgKz0ZvFZ/VCOo6qy1mYWVkrtBm/fKzM+87MdkKYB/zI4VyEJ\nLfLQgjwxBAEYUH3CBG71U0gO0TwbimWNN0vqlfp0QfThNe1WYObF88ZVzMLgFbr7\nRHBItZjlZ/d8foPDidlIR3l2dJjy0EsD8F9JM340jtX7LXqFmU4j1AQKNHTDLnUF\nwYVhzuQGNJ504l5LZkFG54XfIFT7dx2QwuuM9bSnfPv/98RYrq1Si6tCkxEt1cVe\n4wIDAQAB\n-----END PUBLIC KEY-----\n"
  },
  "language": [
    {
      "identifier": "fr",
      "name": "Français"
    },
    {
      "identifier": "de",
      "name": "Deutsch"
    }
  ],
  "published": "2019-06-02T16:43:50.799554+00:00",
  "updated": "2021-03-10T17:18:10.498868+00:00"
}
Field NameDescription
preferredUsernameName of the actor
nameTitle of the community
sensitiveTrue indicates that all posts in the community are nsfw
attributedToFirst the community creator, then all the remaining moderators
summaryText for the community sidebar, usually containing a description and rules
iconIcon, shown next to the community name
imageBanner image, shown on top of the community page
inboxActivityPub inbox URL
outboxActivityPub outbox URL, only contains up to 20 latest posts, no comments, votes or other activities
followersFollower collection URL, only contains the number of followers, no references to individual followers
endpointsContains URL of shared inbox
publishedDatetime when the community was first created
updatedDatetime when the community was last changed
publicKeyThe public key used to verify signatures from this actor

User

A person, interacts primarily with the community where it sends and receives posts/comments. Can also create and moderate communities, and send private messages to other users. Can be followed from other platforms.

{
  "id": "https://enterprise.lemmy.ml/u/picard",
  "type": "Person",
  "preferredUsername": "picard",
  "name": "Jean-Luc Picard",
  "summary": "<p>Captain of the starship <strong>Enterprise</strong>.</p>\n",
  "source": {
    "content": "Captain of the starship **Enterprise**.",
    "mediaType": "text/markdown"
  },
  "icon": {
    "type": "Image",
    "url": "https://enterprise.lemmy.ml/pictrs/image/ed9ej7.jpg"
  },
  "image": {
    "type": "Image",
    "url": "https://enterprise.lemmy.ml/pictrs/image/XenaYI5hTn.png"
  },
  "matrixUserId": "@picard:matrix.org",
  "inbox": "https://enterprise.lemmy.ml/u/picard/inbox",
  "outbox": "https://enterprise.lemmy.ml/u/picard/outbox",
  "endpoints": {
    "sharedInbox": "https://enterprise.lemmy.ml/inbox"
  },
  "published": "2020-01-17T01:38:22.348392+00:00",
  "updated": "2021-08-13T00:11:15.941990+00:00",
  "publicKey": {
    "id": "https://enterprise.lemmy.ml/u/picard#main-key",
    "owner": "https://enterprise.lemmy.ml/u/picard",
    "publicKeyPem": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0lP99/s5Vv+XbPdkeqIJ\nwoD4GFnHmBnBHdEKChEUWfWj1TtioC/rGNoXFQeXQA3Amhy4nxSceiDnUgwkkuQY\nv0MtIW58NzgknEavtllxL+LSds5pg3gANaDIk8UiWTkqXTg0GnlJMpCK1Chen0l/\nszL6DEvUyTSuS5ZYDXFgewF89Pe7U0S15V5U2Harv7AgJYDyxmUL0D1pGuUCRqcE\nl5MTHJjrXeNnH1w2g8aly8YlO/Cr0L51rFg/lBF23vni7ZLv8HbmWh6YpaAf1R8h\nE45zKR7OHqymdjzrg1ITBwovefpwMkVgnJ+Wdr4HPnFlBSkXPoZeM11+Z8L0anzA\nXwIDAQAB\n-----END PUBLIC KEY-----\n"
  }
}
Field NameDescription
preferredUsernameName of the actor
nameThe user's displayname
summaryUser bio
iconThe user's avatar, shown next to the username
imageThe user's banner, shown on top of the profile
inboxActivityPub inbox URL
endpointsContains URL of shared inbox
publishedDatetime when the user signed up
updatedDatetime when the user profile was last changed
publicKeyThe public key used to verify signatures from this actor

Instance

Represents a Lemmy instance, and is used to federate global data like the instance description or site bans. It can be fetched from the root path.

{
  "type": "Application",
  "id": "https://enterprise.lemmy.ml/",
  "name": "Enterprise",
  "summary": "A test instance",
  "content": "<p>Enterprise sidebar</p>\\n",
  "mediaType": "text/html",
  "source": {
    "content": "Enterprise sidebar",
    "mediaType": "text/markdown"
  },
  "inbox": "https://enterprise.lemmy.ml/inbox",
  "outbox": "https://enterprise.lemmy.ml/outbox",
  "publicKey": {
    "id": "https://enterprise.lemmy.ml/#main-key",
    "owner": "https://enterprise.lemmy.ml/",
    "publicKeyPem": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAupcK0xTw5yQb/fnztAmb\n9LfPbhJJP1+1GwUaOXGYiDJD6uYJhl9CLmgztLl3RyV9ltOYoN8/NLNDfOMmgOjd\nrsNWEjDI9IcVPmiZnhU7hsi6KgQvJzzv8O5/xYjAGhDfrGmtdpL+lyG0B5fQod8J\n/V5VWvTQ0B0qFrLSBBuhOrp8/fTtDskdtElDPtnNfH2jn6FgtLOijidWwf9ekFo4\n0I1JeuEw6LuD/CzKVJTPoztzabUV1DQF/DnFJm+8y7SCJa9jEO56Uf9eVfa1jF6f\ndH6ZvNJMiafstVuLMAw7C/eNJy3ufXgtZ4403oOKA0aRSYf1cc9pHSZ9gDE/mevH\nLwIDAQAB\n-----END PUBLIC KEY-----\n"
  },
  "language": [
    {
      "identifier": "fr",
      "name": "Français"
    },
    {
      "identifier": "es",
      "name": "Español"
    }
  ],
  "published": "2022-01-19T21:52:11.110741+00:00"
}
Field NameDescription
nameInstance name
summaryShort description
contentLong description (sidebar)
iconInstance icon
imageInstance banner
inboxActivityPub inbox URL
endpointsContains URL of shared inbox
publishedDatetime when the instance was created
updatedDatetime when the instance metadata
publicKeyThe public key used to verify signatures from this actor

Objects

Post

A page with title, and optional URL and text content. The attachment URL often leads to an image, in which case a thumbnail is included. Each post belongs to exactly one community. Sent out as Page, but for receiving the types Article, Note, Video and Event are also accepted.

{
  "id": "https://enterprise.lemmy.ml/post/55143",
  "type": "Page",
  "attributedTo": "https://enterprise.lemmy.ml/u/picard",
  "to": [
    "https://enterprise.lemmy.ml/c/tenforward",
    "https://www.w3.org/ns/activitystreams#Public"
  ],
  "audience": "https://enterprise.lemmy.ml/c/tenforward",
  "name": "Post title",
  "content": "<p>This is a post in the /c/tenforward community</p>\n",
  "mediaType": "text/html",
  "source": {
    "content": "This is a post in the /c/tenforward community",
    "mediaType": "text/markdown"
  },
  "attachment": [
    {
      "type": "Link",
      "href": "https://enterprise.lemmy.ml/pictrs/image/eOtYb9iEiB.png"
    }
  ],
  "image": {
    "type": "Image",
    "url": "https://enterprise.lemmy.ml/pictrs/image/eOtYb9iEiB.png"
  },
  "sensitive": false,
  "commentsEnabled": true,
  "language": {
    "identifier": "fr",
    "name": "Français"
  },
  "published": "2021-02-26T12:35:34.292626+00:00"
}
Field NameDescription
attributedToID of the user which created this post
toID of the community where it was posted to
nameTitle of the post (mandatory)
contentBody of the post
attachmentA single website or image link
imageThumbnail for url, only present if it is an image link
commentsEnabledFalse indicates that the post is locked, and no comments can be added
sensitiveTrue marks the post as NSFW, blurs the thumbnail and hides it from users with NSFW setting disabled
stickiedTrue means that it is shown on top of the community
publishedDatetime when the post was created
updatedDatetime when the post was edited (not present if it was never edited)

Comment

A reply to a post, or reply to another comment. Contains only text (including references to other users or communities). Lemmy displays comments in a tree structure.

{
  "id": "https://enterprise.lemmy.ml/comment/38741",
  "type": "Note",
  "attributedTo": "https://enterprise.lemmy.ml/u/picard",
  "to": ["https://www.w3.org/ns/activitystreams#Public"],
  "cc": [
    "https://enterprise.lemmy.ml/c/tenforward",
    "https://enterprise.lemmy.ml/u/picard"
  ],
  "audience": "https://enterprise.lemmy.ml/c/tenforward",
  "inReplyTo": "https://enterprise.lemmy.ml/post/55143",
  "content": "<p>first comment!</p>\n",
  "mediaType": "text/html",
  "source": {
    "content": "first comment!",
    "mediaType": "text/markdown"
  },
  "tag": [
    {
      "href": "https://enterprise.lemmy.ml/u/picard",
      "type": "Mention",
      "name": "@picard@enterprise.lemmy.ml"
    }
  ],
  "distinguished": false,
  "language": {
    "identifier": "fr",
    "name": "Français"
  },
  "published": "2021-03-01T13:42:43.966208+00:00",
  "updated": "2021-03-01T13:43:03.955787+00:00"
}
Field NameDescription
attributedToID of the user who created the comment
toCommunity where the comment was made
contentThe comment text
inReplyToID of the parent object. In case of a top-level comment this is the post ID, in case of a nested comment it is the parent comment ID.
publishedDatetime when the comment was created
updatedDatetime when the comment was edited (not present if it was never edited)

Private Message

A direct message from one user to another. Can not include additional users. Threading is not implemented yet, so the inReplyTo field is missing.

{
  "id": "https://enterprise.lemmy.ml/private_message/1621",
  "type": "ChatMessage",
  "attributedTo": "https://enterprise.lemmy.ml/u/picard",
  "to": ["https://queer.hacktivis.me/users/lanodan"],
  "content": "<p>Hello hello, testing</p>\n",
  "mediaType": "text/html",
  "source": {
    "content": "Hello hello, testing",
    "mediaType": "text/markdown"
  },
  "published": "2021-10-21T10:13:14.597721+00:00"
}
Field NameDescription
attributedToID of the user who created this private message
toID of the recipient
contentThe text of the private message
publishedDatetime when the message was created
updatedDatetime when the message was edited (not present if it was never edited)

Collections

Community Outbox

{
  "type": "OrderedCollection",
  "id": "https://ds9.lemmy.ml/c/testcom/outbox",
  "totalItems": 2,
  "orderedItems": [
    {
      "actor": "https://ds9.lemmy.ml/c/testcom",
      "to": ["https://www.w3.org/ns/activitystreams#Public"],
      "object": {
        "actor": "https://ds9.lemmy.ml/u/nutomic",
        "to": ["https://www.w3.org/ns/activitystreams#Public"],
        "cc": ["https://ds9.lemmy.ml/c/testcom"],
        "type": "Create",
        "id": "http://ds9.lemmy.ml/activities/create/eee6a57a-622f-464d-b560-73ae1fcd3ddf",
        "object": {
          "type": "Page",
          "id": "https://ds9.lemmy.ml/post/2328",
          "attributedTo": "https://ds9.lemmy.ml/u/nutomic",
          "to": [
            "https://ds9.lemmy.ml/c/testcom",
            "https://www.w3.org/ns/activitystreams#Public"
          ],
          "name": "another outbox test",
          "mediaType": "text/html",
          "commentsEnabled": true,
          "sensitive": false,
          "stickied": false,
          "published": "2021-11-18T17:19:45.895163+00:00"
        }
      },
      "cc": ["https://ds9.lemmy.ml/c/testcom/followers"],
      "type": "Announce",
      "id": "https://ds9.lemmy.ml/activities/announce/b204fe9f-b13d-4af2-9d22-239ac2d892e6"
    },
    {
      "actor": "https://ds9.lemmy.ml/c/testcom",
      "to": ["https://www.w3.org/ns/activitystreams#Public"],
      "object": {
        "actor": "https://ds9.lemmy.ml/u/nutomic",
        "to": ["https://www.w3.org/ns/activitystreams#Public"],
        "cc": ["https://ds9.lemmy.ml/c/testcom"],
        "type": "Create",
        "id": "http://ds9.lemmy.ml/activities/create/eee6a57a-622f-464d-b560-73ae1fcd3ddf",
        "object": {
          "type": "Page",
          "id": "https://ds9.lemmy.ml/post/2327",
          "attributedTo": "https://ds9.lemmy.ml/u/nutomic",
          "to": [
            "https://ds9.lemmy.ml/c/testcom",
            "https://www.w3.org/ns/activitystreams#Public"
          ],
          "name": "outbox test",
          "mediaType": "text/html",
          "commentsEnabled": true,
          "sensitive": false,
          "stickied": false,
          "published": "2021-11-18T17:19:05.763109+00:00"
        }
      },
      "cc": ["https://ds9.lemmy.ml/c/testcom/followers"],
      "type": "Announce",
      "id": "https://ds9.lemmy.ml/activities/announce/c6c960ce-c8d8-4231-925e-3ba367468f18"
    }
  ]
}

The outbox only contains Create/Post activities for now.

Community Followers

{
  "id": "http://enterprise.lemmy.ml/c/main/followers",
  "type": "Collection",
  "totalItems": 3,
  "items": []
}

The followers collection is only used to expose the number of followers. Actor IDs are not included, to protect user privacy.

Community Moderators

List of moderators who can perform actions like removing posts or banning users.

{
  "type": "OrderedCollection",
  "id": "https://enterprise.lemmy.ml/c/tenforward/moderators",
  "orderedItems": ["https://enterprise.lemmy.ml/u/picard"]
}

List of posts which are stickied in the community.

{
  "type": "OrderedCollection",
  "id": "https://ds9.lemmy.ml/c/main/featured",
  "totalItems": 2,
  "orderedItems": [
    {
      "type": "Page",
      "id": "https://ds9.lemmy.ml/post/2",
      "attributedTo": "https://ds9.lemmy.ml/u/lemmy_alpha",
      "to": [
        "https://ds9.lemmy.ml/c/main",
        "https://www.w3.org/ns/activitystreams#Public"
      ],
      "name": "test 2",
      "cc": [],
      "mediaType": "text/html",
      "attachment": [],
      "commentsEnabled": true,
      "sensitive": false,
      "published": "2023-02-06T06:42:41.939437+00:00",
      "language": {
        "identifier": "de",
        "name": "Deutsch"
      },
      "audience": "https://ds9.lemmy.ml/c/main"
    },
    {
      "type": "Page",
      "id": "https://ds9.lemmy.ml/post/1",
      "attributedTo": "https://ds9.lemmy.ml/u/lemmy_alpha",
      "to": [
        "https://ds9.lemmy.ml/c/main",
        "https://www.w3.org/ns/activitystreams#Public"
      ],
      "name": "test 1",
      "cc": [],
      "mediaType": "text/html",
      "attachment": [],
      "commentsEnabled": true,
      "sensitive": false,
      "published": "2023-02-06T06:42:37.119567+00:00",
      "language": {
        "identifier": "de",
        "name": "Deutsch"
      },
      "audience": "https://ds9.lemmy.ml/c/main"
    }
  ]
}

User Outbox

Only contains totalItems count, but no actual items for privacy reasons.

{
  "type": "OrderedCollection",
  "id": "http://ds9.lemmy.ml/u/lemmy_alpha/outbox",
  "orderedItems": [],
  "totalItems": 0
}

Activities

User to Community

Follow

Each Community page has a "Follow" button. Clicking this triggers a Follow activity to be sent from the user to the Community inbox. The Community will automatically respond with an Accept/Follow activity to the user inbox. It will also add the user to its list of followers, and deliver any activities about Posts/Comments in the Community to the user.

{
  "actor": "http://ds9.lemmy.ml/u/lemmy_alpha",
  "to": ["http://enterprise.lemmy.ml/c/main"],
  "object": "http://enterprise.lemmy.ml/c/main",
  "type": "Follow",
  "id": "http://ds9.lemmy.ml/activities/follow/6abcd50b-b8ca-4952-86b0-a6dd8cc12866"
}

Unfollow

After following a Community, the "Follow" button is replaced by "Unfollow". Clicking this sends an Undo/Follow activity to the Community inbox. The Community removes the User from its followers list and doesn't send any activities to it anymore.

{
  "actor": "http://ds9.lemmy.ml/u/lemmy_alpha",
  "to": ["http://enterprise.lemmy.ml/c/main"],
  "object": {
    "actor": "http://ds9.lemmy.ml/u/lemmy_alpha",
    "to": ["http://enterprise.lemmy.ml/c/main"],
    "object": "http://enterprise.lemmy.ml/c/main",
    "type": "Follow",
    "id": "http://ds9.lemmy.ml/activities/follow/dc2f1bc5-f3a0-4daa-a46b-428cbfbd023c"
  },
  "type": "Undo",
  "id": "http://ds9.lemmy.ml/activities/undo/dd83c482-8ebd-4b6c-9008-c8373bd1a86a"
}

Create or Update Post

When a user creates a new post, it is sent to the respective community as Create/Page. Editing a previously created post sends an almost identical activity, except the type being Update.

{
  "actor": "http://ds9.lemmy.ml/u/lemmy_alpha",
  "to": ["https://www.w3.org/ns/activitystreams#Public"],
  "object": {
    "type": "Page",
    "id": "http://ds9.lemmy.ml/post/1",
    "attributedTo": "http://ds9.lemmy.ml/u/lemmy_alpha",
    "to": [
      "http://enterprise.lemmy.ml/c/main",
      "https://www.w3.org/ns/activitystreams#Public"
    ],
    "audience": "https://enterprise.lemmy.ml/c/main",
    "name": "test post",
    "content": "<p>test body</p>\n",
    "mediaType": "text/html",
    "source": {
      "content": "test body",
      "mediaType": "text/markdown"
    },
    "attachment": [
      {
        "type": "Link",
        "href": "https://lemmy.ml/pictrs/image/xl8W7FZfk9.jpg"
      }
    ],
    "commentsEnabled": true,
    "sensitive": false,
    "language": {
      "identifier": "ko",
      "name": "한국어"
    },
    "published": "2021-10-29T15:10:51.557399+00:00"
  },
  "cc": ["http://enterprise.lemmy.ml/c/main"],
  "audience": "https://enterprise.lemmy.ml/c/main",
  "type": "Create",
  "id": "http://ds9.lemmy.ml/activities/create/eee6a57a-622f-464d-b560-73ae1fcd3ddf"
}

Create or Update Comment

A reply to a post, or to another comment as Create/Note. Can contain mentions of other users. Editing a previously created post sends an almost identical activity, except the type being Update.

The origin instance also scans the Comment for any User mentions, and sends the Create/Note to those Users as well.

{
  "actor": "http://ds9.lemmy.ml/u/lemmy_alpha",
  "to": ["https://www.w3.org/ns/activitystreams#Public"],
  "object": {
    "type": "Note",
    "id": "http://ds9.lemmy.ml/comment/1",
    "attributedTo": "http://ds9.lemmy.ml/u/lemmy_alpha",
    "to": ["https://www.w3.org/ns/activitystreams#Public"],
    "cc": [
      "http://enterprise.lemmy.ml/c/main",
      "http://ds9.lemmy.ml/u/lemmy_alpha"
    ],
    "audience": "http://ds9.lemmy.ml/u/lemmy_alpha",
    "content": "hello",
    "mediaType": "text/html",
    "source": {
      "content": "hello",
      "mediaType": "text/markdown"
    },
    "inReplyTo": "http://ds9.lemmy.ml/post/1",
    "published": "2021-11-01T11:45:49.794920+00:00"
  },
  "cc": [
    "http://enterprise.lemmy.ml/c/main",
    "http://ds9.lemmy.ml/u/lemmy_alpha"
  ],
  "audience": "http://ds9.lemmy.ml/u/lemmy_alpha",
  "tag": [
    {
      "href": "http://ds9.lemmy.ml/u/lemmy_alpha",
      "type": "Mention",
      "name": "@lemmy_alpha@ds9.lemmy.ml"
    }
  ],
  "type": "Create",
  "id": "http://ds9.lemmy.ml/activities/create/1e77d67c-44ac-45ed-bf2a-460e21f60236"
}

Like Post or Comment

An upvote for a post or comment.

{
  "actor": "http://ds9.lemmy.ml/u/lemmy_alpha",
  "object": "http://ds9.lemmy.ml/comment/1",
  "audience": "https://enterprise.lemmy.ml/c/tenforward",
  "type": "Like",
  "id": "http://ds9.lemmy.ml/activities/like/fd61d070-7382-46a9-b2b7-6bb253732877"
}

Dislike Post or Comment

A downvote for a post or comment.

{
  "actor": "http://enterprise.lemmy.ml/u/lemmy_beta",
  "object": "http://ds9.lemmy.ml/post/1",
  "audience": "https://enterprise.lemmy.ml/c/tenforward",
  "type": "Dislike",
  "id": "http://enterprise.lemmy.ml/activities/dislike/64d40d40-a829-43a5-8247-1fb595b3ca1c"
}

Undo Like or Dislike Post or Comment

Revert a vote that was previously done by the same user.

{
  "actor": "http://ds9.lemmy.ml/u/lemmy_alpha",
  "object": {
    "actor": "http://ds9.lemmy.ml/u/lemmy_alpha",
    "object": "http://ds9.lemmy.ml/comment/1",
    "audience": "https://enterprise.lemmy.ml/c/tenforward",
    "type": "Like",
    "id": "http://ds9.lemmy.ml/activities/like/efcf7ae2-dfcc-4ff4-9ce4-6adf251ff004"
  },
  "audience": "https://enterprise.lemmy.ml/c/tenforward",
  "type": "Undo",
  "id": "http://ds9.lemmy.ml/activities/undo/3518565c-24a7-4d9e-8e0a-f7a2f45ac618"
}

Delete Post or Comment

Mods can remove Posts and Comments from their Communities. Admins can remove any Posts or Comments on the entire site. Communities can also be removed by admins. The item is then hidden from all users.

Removals are sent to all followers of the Community, so that they also take effect there. The exception is if an admin removes an item from a Community which is hosted on a different instance. In this case, the removal only takes effect locally.

{
  "actor": "http://ds9.lemmy.ml/u/lemmy_alpha",
  "to": ["https://www.w3.org/ns/activitystreams#Public"],
  "object": "http://ds9.lemmy.ml/post/1",
  "cc": ["http://enterprise.lemmy.ml/c/main"],
  "audience": "http://enterprise.lemmy.ml/u/main",
  "type": "Delete",
  "id": "http://ds9.lemmy.ml/activities/delete/f2abee48-c7bb-41d5-9e27-8775ff32db12"
}

Undo Delete

Post or comment deletions can be reverted by the same user.

{
  "actor": "http://ds9.lemmy.ml/u/lemmy_alpha",
  "to": ["https://www.w3.org/ns/activitystreams#Public"],
  "object": {
    "actor": "http://ds9.lemmy.ml/u/lemmy_alpha",
    "to": ["https://www.w3.org/ns/activitystreams#Public"],
    "object": "http://ds9.lemmy.ml/post/1",
    "cc": ["http://enterprise.lemmy.ml/c/main"],
    "audience": "http://enterprise.lemmy.ml/u/main",
    "type": "Delete",
    "id": "http://ds9.lemmy.ml/activities/delete/b13cca96-7737-41e1-9769-8fbf972b3509"
  },
  "cc": ["http://enterprise.lemmy.ml/c/main"],
  "audience": "http://enterprise.lemmy.ml/u/main",
  "type": "Undo",
  "id": "http://ds9.lemmy.ml/activities/undo/5e939cfb-b8a1-4de8-950f-9d684e9162b9"
}

Report Post, comment or private message

Reports content for rule violation, so that mods/admins can review it.

{
  "actor": "http://ds9.lemmy.ml/u/lemmy_alpha",
  "to": ["http://enterprise.lemmy.ml/c/main"],
  "audience": "http://enterprise.lemmy.ml/u/main",
  "object": "http://enterprise.lemmy.ml/post/7",
  "summary": "report this post",
  "type": "Flag",
  "id": "http://ds9.lemmy.ml/activities/flag/98b0933f-5e45-4a95-a15f-e0dc86361ba4"
}

Delete User

Sent when a user deletes his own account.

{
  "actor": "http://ds9.lemmy.ml/u/lemmy_alpha",
  "to": ["https://www.w3.org/ns/activitystreams#Public"],
  "object": "http://ds9.lemmy.ml/u/lemmy_alpha",
  "type": "Delete",
  "id": "http://ds9.lemmy.ml/activities/delete/f2abee48-c7bb-41d5-9e27-8775ff32db12"
}

Community to User

Accept Follow

Automatically sent by the community in response to a Follow. At the same time, the community adds this user to its followers list.

{
  "actor": "http://enterprise.lemmy.ml/c/main",
  "to": ["http://ds9.lemmy.ml/u/lemmy_alpha"],
  "object": {
    "actor": "http://ds9.lemmy.ml/u/lemmy_alpha",
    "to": ["http://enterprise.lemmy.ml/c/main"],
    "object": "http://enterprise.lemmy.ml/c/main",
    "type": "Follow",
    "id": "http://ds9.lemmy.ml/activities/follow/6abcd50b-b8ca-4952-86b0-a6dd8cc12866"
  },
  "type": "Accept",
  "id": "http://enterprise.lemmy.ml/activities/accept/75f080cc-3d45-4654-8186-8f3bb853fa27"
}

Announce

If the Community receives any Post or Comment related activity (Create, Update, Like, Dislike, Remove, Delete, Undo etc.), it will forward this to its followers. For this, an Announce is created with the Community as actor, and the received activity as object. This is sent to all followers, so they get updated in real time.

{
  "actor": "http://enterprise.lemmy.ml/c/main",
  "to": ["https://www.w3.org/ns/activitystreams#Public"],
  "object": {
    "actor": "http://enterprise.lemmy.ml/u/lemmy_beta",
    "to": ["https://www.w3.org/ns/activitystreams#Public"],
    "object": {
      "type": "Page",
      "id": "http://enterprise.lemmy.ml/post/7",
      "attributedTo": "http://enterprise.lemmy.ml/u/lemmy_beta",
      "to": [
        "http://enterprise.lemmy.ml/c/main",
        "https://www.w3.org/ns/activitystreams#Public"
      ],
      "name": "post 4",
      "mediaType": "text/html",
      "commentsEnabled": true,
      "sensitive": false,
      "stickied": false,
      "published": "2021-11-01T12:11:22.871846+00:00"
    },
    "cc": ["http://enterprise.lemmy.ml/c/main"],
    "type": "Create",
    "id": "http://enterprise.lemmy.ml/activities/create/2807c9ec-3ad8-4859-a9e0-28b59b6e499f"
  },
  "cc": ["http://enterprise.lemmy.ml/c/main/followers"],
  "type": "Announce",
  "id": "http://enterprise.lemmy.ml/activities/announce/8030b171-803a-4108-94b1-342688f375cf"
}

Moderation

These actions can only be done by instance admins or community moderators. They are sent to the community and announced by it. See for a general overview how moderation works in Lemmy. Communities can only be created on the same instance where a user is registered. After that, mods from other instances can be added with Add/User activity.

Remove Post or Comment

Removes a post or comment. The difference to delete is that remove activities have a summary field, which contains the reason for removal, as provided by the mod/admin.

{
  "actor": "http://enterprise.lemmy.ml/u/lemmy_beta",
  "to": ["https://www.w3.org/ns/activitystreams#Public"],
  "object": "http://ds9.lemmy.ml/comment/1",
  "cc": ["http://enterprise.lemmy.ml/c/main"],
  "audience": "http://enterprise.lemmy.ml/u/main",
  "type": "Delete",
  "summary": "bad comment",
  "id": "http://enterprise.lemmy.ml/activities/delete/42ca1a79-f99e-4518-a2ca-ba2df221eb5e"
}

Block User

Blocks a user so he can't participate anymore. The scope is determined by the target field: either a community, or a whole instance. The removeData field can optionally be set to indicate that all previous posts of the user should be deleted.

{
  "actor": "http://enterprise.lemmy.ml/u/lemmy_beta",
  "to": ["https://www.w3.org/ns/activitystreams#Public"],
  "object": "http://ds9.lemmy.ml/u/lemmy_alpha",
  "cc": ["http://enterprise.lemmy.ml/c/main"],
  "audience": "http://enterprise.lemmy.ml/u/main",
  "target": "http://enterprise.lemmy.ml/c/main",
  "type": "Block",
  "removeData": true,
  "summary": "spam post",
  "expires": "2021-11-01T12:23:50.151874+00:00",
  "id": "http://enterprise.lemmy.ml/activities/block/5d42fffb-0903-4625-86d4-0b39bb344fc2"
}

Lock post

Posts can be locked so that no new comments can be created.

{
  "id": "http://lemmy-alpha:8541/activities/lock/cb48761d-9e8c-42ce-aacb-b4bbe6408db2",
  "actor": "http://lemmy-alpha:8541/u/lemmy_alpha",
  "to": ["https://www.w3.org/ns/activitystreams#Public"],
  "object": "http://lemmy-alpha:8541/post/2",
  "cc": ["http://lemmy-alpha:8541/c/main"],
  "type": "Lock",
  "audience": "http://lemmy-alpha:8541/c/main"
}

Undo mod actions

All previously listed mod actions can be reverted by wrapping the original activity in Undo. Note that Lemmy regenerates the inner activity with a new ID.

{
  "actor": "http://enterprise.lemmy.ml/u/lemmy_beta",
  "to": ["https://www.w3.org/ns/activitystreams#Public"],
  "object": {
    "actor": "http://enterprise.lemmy.ml/u/lemmy_beta",
    "to": ["https://www.w3.org/ns/activitystreams#Public"],
    "object": "http://ds9.lemmy.ml/comment/1",
    "cc": ["http://enterprise.lemmy.ml/c/main"],
    "audience": "http://enterprise.lemmy.ml/u/main",
    "type": "Delete",
    "summary": "bad comment",
    "id": "http://enterprise.lemmy.ml/activities/delete/2598435c-87a3-49cd-81f3-a44b03b7af9d"
  },
  "cc": ["http://enterprise.lemmy.ml/c/main"],
  "audience": "http://enterprise.lemmy.ml/u/main",
  "type": "Undo",
  "id": "http://enterprise.lemmy.ml/activities/undo/a850cf21-3866-4b3a-b80b-56aa00997fee"
}
{
  "actor": "http://enterprise.lemmy.ml/u/lemmy_beta",
  "to": ["https://www.w3.org/ns/activitystreams#Public"],
  "object": {
    "actor": "http://enterprise.lemmy.ml/u/lemmy_beta",
    "to": ["https://www.w3.org/ns/activitystreams#Public"],
    "object": "http://ds9.lemmy.ml/u/lemmy_alpha",
    "cc": ["http://enterprise.lemmy.ml/c/main"],
    "audience": "http://enterprise.lemmy.ml/u/main",
    "target": "http://enterprise.lemmy.ml/c/main",
    "type": "Block",
    "removeData": true,
    "summary": "spam post",
    "expires": "2021-11-01T12:23:50.151874+00:00",
    "id": "http://enterprise.lemmy.ml/activities/block/726f43ab-bd0e-4ab3-89c8-627e976f553c"
  },
  "cc": ["http://enterprise.lemmy.ml/c/main"],
  "audience": "http://enterprise.lemmy.ml/u/main",
  "type": "Undo",
  "id": "http://enterprise.lemmy.ml/activities/undo/06a20ffb-3e32-42fb-8f4c-674b36d7c557"
}

Add or remove featured post

Posts can be pinned so that they are always shown on top of the community. This is federated with the Community featured posts collection.

{
  "cc": ["https://ds9.lemmy.ml/c/main"],
  "id": "https://ds9.lemmy.ml/activities/add/47d911f5-52c5-4659-b2fd-0e58c451a427",
  "to": ["https://www.w3.org/ns/activitystreams#Public"],
  "type": "Add",
  "actor": "https://ds9.lemmy.ml/u/lemmy_alpha",
  "object": "https://ds9.lemmy.ml/post/2",
  "target": "https://ds9.lemmy.ml/c/main/featured",
  "audience": "https://ds9.lemmy.ml/c/main"
}
{
  "cc": ["https://ds9.lemmy.ml/c/main"],
  "id": "https://ds9.lemmy.ml/activities/add/47d911f5-52c5-4659-b2fd-0e58c451a427",
  "to": ["https://www.w3.org/ns/activitystreams#Public"],
  "type": "Remove",
  "actor": "https://ds9.lemmy.ml/u/lemmy_alpha",
  "object": "https://ds9.lemmy.ml/post/2",
  "target": "https://ds9.lemmy.ml/c/main/featured",
  "audience": "https://ds9.lemmy.ml/c/main"
}

Add or remove mod

Add a new mod to the community. Has to be sent by an existing community mod, or an admin of the community's instance.

{
  "actor": "http://enterprise.lemmy.ml/u/lemmy_beta",
  "to": ["https://www.w3.org/ns/activitystreams#Public"],
  "object": "http://ds9.lemmy.ml/u/lemmy_alpha",
  "target": "http://enterprise.lemmy.ml/c/main/moderators",
  "cc": ["http://enterprise.lemmy.ml/c/main"],
  "audience": "http://enterprise.lemmy.ml/u/main",
  "type": "Add",
  "id": "http://enterprise.lemmy.ml/activities/add/ec069147-77c3-447f-88c8-0ef1df10403f"
}

An existing mod can be removed in the same way.

{
  "actor": "http://enterprise.lemmy.ml/u/lemmy_beta",
  "to": ["https://www.w3.org/ns/activitystreams#Public"],
  "object": "http://ds9.lemmy.ml/u/lemmy_alpha",
  "cc": ["http://enterprise.lemmy.ml/c/main"],
  "type": "Remove",
  "target": "http://enterprise.lemmy.ml/c/main/moderators",
  "audience": "http://enterprise.lemmy.ml/u/main",
  "id": "http://enterprise.lemmy.ml/activities/remove/aab114f8-cfbd-4935-a5b7-e1a64603650d"
}

User to User

Follow a user

Users from other platforms can follow Lemmy users and receive all of their posts to the inbox. Note that users who are registered on Lemmy can only follow groups, not other users.

{
  "@context": [
    "https://www.w3.org/ns/activitystreams",
    "https://mycrowd.ca/schemas/litepub-0.1.jsonld",
    {
      "@language": "und"
    }
  ],
  "actor": "https://mycrowd.ca/users/kinetix",
  "cc": [],
  "id": "https://mycrowd.ca/activities/dab6a4d3-0db0-41ee-8aab-7bfa4929b4fd",
  "object": "https://lemmy.ca/u/kinetix",
  "state": "pending",
  "to": ["https://lemmy.ca/u/kinetix"],
  "type": "Follow"
}

Create or Update Private message

User profiles have a "Send Message" button, which opens a dialog permitting to send a private message to this user. It is sent as a Create/ChatMessage to the user inbox. Private messages can only be directed at a single User. They can also be edited with Update/ChatMessage.

{
  "id": "http://enterprise.lemmy.ml/activities/create/987d05fa-f637-46d7-85be-13d112bc269f",
  "actor": "http://enterprise.lemmy.ml/u/lemmy_beta",
  "to": ["http://ds9.lemmy.ml/u/lemmy_alpha"],
  "object": {
    "type": "ChatMessage",
    "id": "http://enterprise.lemmy.ml/private_message/1",
    "attributedTo": "http://enterprise.lemmy.ml/u/lemmy_beta",
    "to": ["http://ds9.lemmy.ml/u/lemmy_alpha"],
    "content": "hello",
    "mediaType": "text/html",
    "source": {
      "content": "hello",
      "mediaType": "text/markdown"
    },
    "published": "2021-10-29T15:31:56.058289+00:00"
  },
  "type": "Create"
}

Delete Private Message

Deletes a previous private message.

{
  "actor": "http://enterprise.lemmy.ml/u/lemmy_beta",
  "to": ["http://enterprise.lemmy.ml/u/lemmy_beta"],
  "object": "http://enterprise.lemmy.ml/private_message/1",
  "type": "Delete",
  "id": "http://enterprise.lemmy.ml/activities/delete/041d9858-5eef-4ad9-84ae-7455b4d87ed9"
}

Undo Delete Private Message

Restores a previously deleted private message. The object is regenerated from scratch, as such the activity ID and other fields are different.

{
  "actor": "http://enterprise.lemmy.ml/u/lemmy_beta",
  "to": ["http://ds9.lemmy.ml/u/lemmy_alpha"],
  "object": {
    "actor": "http://enterprise.lemmy.ml/u/lemmy_beta",
    "to": ["http://enterprise.lemmy.ml/u/lemmy_beta"],
    "object": "http://enterprise.lemmy.ml/private_message/1",
    "type": "Delete",
    "id": "http://enterprise.lemmy.ml/activities/delete/616c41be-04ed-4bd4-b865-30712186b122"
  },
  "type": "Undo",
  "id": "http://enterprise.lemmy.ml/activities/undo/35e5b337-014c-4bbe-8d63-6fac96f51409"
}

Resources / Libraries

Rust

Typescript

Trees

Sorting

SQL

ActivityPub Resources

Official Documents

Explanations

Examples and Libraries

Trending / Hot / Best Sorting algorithm

Goals

  • During the day, new posts and comments should be near the top, so they can be voted on.
  • After a day or so, the time factor should go away.
  • Use a log scale, since votes tend to snowball, and so the first 10 votes are just as important as the next hundred.

Implementations

Reddit

Does not take the lifetime of the thread into account, giving early comments an overwhelming advantage over later ones, with the effect being even worse in small communities. New comments pool at the bottom of the thread, effectively killing off discussion and making each thread a race to comment early. This lowers the quality of conversation and rewards comments that are repetitive and spammy.

Hacker News

While far superior to Reddit's implementation for its decay of scores over time, Hacker News' ranking algorithm does not use a logarithmic scale for scores.

Lemmy

Counterbalances the snowballing effect of votes over time with a logarithmic scale. Negates the inherent advantage of early comments while still ensuring that votes still matter in the long-term, not nuking older popular comments.

Rank = ScaleFactor * log(Max(1, 3 + Score)) / (Time + 2)^Gravity
Scaled_Rank = Rank / log(2 + Users_Active_Month)

Score = Upvotes - Downvotes
Time = time since submission (in hours)
Gravity = Decay gravity, 1.8 is default
Users_Active_Month = The number of users in a given community who have posted / commented / voted in the last month.
  • Lemmy uses the same Rank algorithm above, in three sorts: Active, Hot, and Scaled.
    • Active uses the post votes, and latest comment time (limited to two days).
    • Hot uses the post votes, and the post published time.
    • Scaled is similar to Hot, but gives a boost to smaller / less active communities.
  • Use Max(1, score) to make sure all comments are affected by time decay.
  • Add 3 to the score, so that everything that has less than 3 downvotes will seem new. Otherwise all new comments would stay at zero, near the bottom.
  • The sign and abs of the score are necessary for dealing with the log of negative scores.
  • A scale factor of 10k gets the rank in integer form.

A plot of rank over 24 hours, of scores of 1, 5, 10, 100, 1000, with a scale factor of 10k.

rank_algorithm.png

Active User counts

Lemmy also shows counts of active users for your site, and its communities. These are counted within the last day, week, month, and half year, and are cached on starting up lemmy, and every hour.

An active user is someone who has posted, commented, or voted on our instance or community within the last given time frame. For site counts, only local users are counted. For community counts, federated users are included.

Code of Conduct

  • We are committed to providing a friendly, safe and welcoming environment for all, regardless of level of experience, gender identity and expression, sexual orientation, disability, personal appearance, body size, race, ethnicity, age, religion, nationality, or other similar characteristic.
  • Please avoid using overtly sexual aliases or other nicknames that might detract from a friendly, safe and welcoming environment for all.
  • Please be kind and courteous. There’s no need to be mean or rude.
  • Respect that people have differences of opinion and that every design or implementation choice carries a trade-off and numerous costs. There is seldom a right answer.
  • Please keep unstructured critique to a minimum. If you have solid ideas you want to experiment with, make a fork and see how it works.
  • We will exclude you from interaction if you insult, demean or harass anyone. That is not welcome behavior. We interpret the term “harassment” as including the definition in the Citizen Code of Conduct; if you have any lack of clarity about what might be included in that concept, please read their definition. In particular, we don’t tolerate behavior that excludes people in socially marginalized groups.
  • Private harassment is also unacceptable. No matter who you are, if you feel you have been or are being harassed or made uncomfortable by a community member, please contact one of the channel ops or any of the Lemmy moderation team immediately. Whether you’re a regular contributor or a newcomer, we care about making this community a safe place for you and we’ve got your back.
  • Likewise any spamming, trolling, flaming, baiting or other attention-stealing behavior is not welcome.
  • Entitlement, ie demanding or insulting behavior towards someone for not doing what you want them to do, or not doing it fast enough, will also not be tolerated.

Message the Moderation Team on Mastodon

Moderation

These are the policies for upholding our community’s standards of conduct. If you feel that a thread needs moderation, please contact the Lemmy moderation team .

  1. Remarks that violate the Lemmy standards of conduct, including hateful, hurtful, oppressive, or exclusionary remarks, are not allowed. (Cursing is allowed, but never targeting another user, and never in a hateful manner.)
  2. Remarks that moderators find inappropriate, whether listed in the code of conduct or not, are also not allowed.
  3. Moderators will first respond to such remarks with a warning, at the same time the offending content will likely be removed whenever possible.
  4. If the warning is unheeded, the user will be “kicked,” i.e., kicked out of the communication channel to cool off.
  5. If the user comes back and continues to make trouble, they will be banned, i.e., indefinitely excluded.
  6. Moderators may choose at their discretion to un-ban the user if it was a first offense and they offer the offended party a genuine apology.
  7. If a moderator bans someone and you think it was unjustified, please take it up with that moderator, or with a different moderator, in private. Complaints about bans in-channel are not allowed.
  8. Moderators are held to a higher standard than other community members. If a moderator creates an inappropriate situation, they should expect less leeway than others.

In the Lemmy community we strive to go the extra step to look out for each other. Don’t just aim to be technically unimpeachable, try to be your best self. In particular, avoid flirting with offensive or sensitive issues, particularly if they’re off-topic; this all too often leads to unnecessary fights, hurt feelings, and damaged trust; worse, it can drive people away from the community entirely.

And if someone takes issue with something you said or did, resist the urge to be defensive. Just stop doing what it was they complained about and apologize. Even if you feel you were misinterpreted or unfairly accused, chances are good there was something you could’ve communicated better — remember that it’s your responsibility to make others comfortable. Everyone wants to get along and we are all here first and foremost because we want to talk about cool technology. You will find that people will be eager to assume good intent and forgive as long as you earn their trust.

The enforcement policies listed above apply to all official Lemmy venues; including git repositories under github.com/LemmyNet and git.join-lemmy.org/LemmyNet, the Matrix chat; lemmy.ml and other instances under that domain. For other projects adopting the Lemmy Code of Conduct, please contact the maintainers of those projects for enforcement. If you wish to use this code of conduct for your own project, consider explicitly mentioning your moderation policy or making a copy with your own moderation policy so as to avoid confusion.

Adapted from the Rust Code of Conduct, which is based on the Node.js Policy on Trolling as well as the Contributor Covenant v1.3.0.