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.



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 - | sudo apt-key add -
sudo sh -c 'echo "deb $(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

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/
# 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 installation of ImageMagick

Follow the instructions from the official 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 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/"

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


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


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
  bind: ""
  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.

Description=Lemmy Server


# Hardening


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


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". 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 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 | sudo gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg
echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] 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 --recursive
cd lemmy-ui
git checkout 0.18.5 # replace with the version you want to install
pnpm i
pnpm build:prod

UI daemon

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

Description=Lemmy UI

ExecStart=/usr/bin/node dist/js/server.js

# Hardening


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 with your actual domain.

@daily certbot certonly --nginx --cert-name -d --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
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_prefer_server_ciphers on;
    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 "";
      if ($http_accept ~ "^application/.*$") {
        set $proxpass "";
      if ($request_method = POST) {
        set $proxpass "";
      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_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

sudo sed -i -e 's/{{domain}}/' /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.



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/"
# 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
sudo systemctl restart lemmy-ui


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.