I contributed to the official Mastodon installation guide back in 2022. It has drifted since then, as server guides tend to do. This version walks through installing and configuring a Mastodon instance on your own server.
#1. What is Mastodon?
Mastodon is a free, open-source, decentralized social network. It feels familiar if you have used Twitter or X, but one company does not own the whole thing.
Instead of everyone joining one central site, users create or join independently operated servers called instances that communicate across the wider Fediverse.
Running your own Mastodon instance gives you full control over your community, data, and content policies.
#2. Prerequisites
Have these ready before you start:
-
A domain name with DNS A/AAAA records pointed to your server's IP address. This tutorial uses
example.comas the placeholder. -
A Linux server with Debian Stable or Ubuntu LTS installed and a user with
sudoprivileges. -
An email delivery service such as Mailgun or Amazon Simple Email Service.
The commands below use sudo. If you prefer a root shell, run sudo -i first and drop sudo from the commands.
Log in to your server and update the system:
sudo apt update
sudo apt upgrade -y
sudo apt full-upgrade -y
Install the required software and dependencies:
sudo apt install curl vim wget gnupg dpkg apt-transport-https lsb-release ca-certificates -y
#3. Add package repositories
Nginx
curl -sS https://n.wtf/public.key | sudo gpg --dearmor -o /usr/share/keyrings/n.wtf.gpg
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/n.wtf.gpg] https://mirror-cdn.xtom.com/sb/nginx/ $(lsb_release -sc) main" | sudo tee /etc/apt/sources.list.d/n.wtf.list
On Debian, you can also enable it with extrepo:
sudo apt install extrepo -y
sudo extrepo enable n.wtf
Node.js
curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | sudo gpg --dearmor -o /usr/share/keyrings/nodesource.gpg
echo "deb [signed-by=/usr/share/keyrings/nodesource.gpg] https://deb.nodesource.com/node_24.x nodistro main" | sudo tee /etc/apt/sources.list.d/nodesource.list
Or use extrepo:
sudo apt install extrepo -y
sudo extrepo enable node_24.x
PostgreSQL
curl -sSL https://www.postgresql.org/media/keys/ACCC4CF8.asc | sudo gpg --dearmor -o /usr/share/keyrings/postgresql.gpg
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/postgresql.gpg] http://apt.postgresql.org/pub/repos/apt $(lsb_release -cs)-pgdg main" | sudo tee /etc/apt/sources.list.d/postgresql.list
Or use extrepo:
sudo apt install extrepo -y
sudo extrepo enable postgresql
Redis
curl -fsSL https://packages.redis.io/gpg | sudo gpg --dearmor -o /usr/share/keyrings/redis-archive-keyring.gpg
echo "deb [signed-by=/usr/share/keyrings/redis-archive-keyring.gpg] https://packages.redis.io/deb $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/redis.list
Or use extrepo:
sudo apt install extrepo -y
sudo extrepo enable redis
Elasticsearch 8 (optional)
curl -sS https://artifacts.elastic.co/GPG-KEY-elasticsearch | sudo gpg --dearmor -o /usr/share/keyrings/elasticsearch.gpg
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/elasticsearch.gpg] https://artifacts.elastic.co/packages/8.x/apt stable main" | sudo tee /etc/apt/sources.list.d/elasticsearch.list
Or use extrepo:
sudo apt install extrepo -y
sudo extrepo enable elastic_8
Update the package lists:
sudo apt update
#4. Install system packages
Install the system packages Mastodon needs:
sudo apt install -y \
imagemagick ffmpeg libpq-dev libxml2-dev libxslt1-dev file git \
g++ libprotobuf-dev protobuf-compiler pkg-config nodejs gcc autoconf \
bison build-essential libssl-dev libyaml-dev libreadline-dev \
zlib1g-dev libncurses-dev libffi-dev libgdbm-dev \
redis-server redis-tools postgresql postgresql-contrib \
libidn11-dev libicu-dev libjemalloc-dev libvips nginx-extras
Enable Yarn through Corepack:
sudo corepack enable
Enable the redis-server service:
sudo systemctl enable --now redis-server
#5. Create the Mastodon user
Create a dedicated mastodon user:
sudo adduser --disabled-password --gecos "" --home /home/mastodon mastodon
Then grant the right permissions for the /home/mastodon directory:
sudo chmod 0755 /home/mastodon
#6. Set up PostgreSQL
Mastodon uses PostgreSQL as its database. Create a user named mastodon and allow it to create Mastodon's database:
sudo -u postgres psql
CREATE USER mastodon CREATEDB;
\q
You can use PGTune to tune PostgreSQL for your server.
PGTune generates recommended settings from your RAM and CPU. Treat them as a starting point, not sacred text.
#7. Install Mastodon
Switch to the mastodon user and move into /home/mastodon. From here until you exit this shell, all commands run as the mastodon user without sudo:
sudo -i -u mastodon
cd ~
Install rbenv, a Ruby version manager:
git clone https://github.com/rbenv/rbenv.git ~/.rbenv
cd ~/.rbenv && src/configure && make -C src
echo 'export PATH="$HOME/.rbenv/bin:$PATH"' >> ~/.bashrc
echo 'eval "$(rbenv init -)"' >> ~/.bashrc
source ~/.bashrc
git clone https://github.com/rbenv/ruby-build.git ~/.rbenv/plugins/ruby-build
Clone the Mastodon Git repository and switch to the latest released version:
cd ~
git clone https://github.com/mastodon/mastodon.git live && cd live
git checkout $(git tag -l | grep '^v[0-9.]*$' | sort -V | tail -n 1)
After checkout, install the Ruby version required by Mastodon:
RUBY_CONFIGURE_OPTS=--with-jemalloc rbenv install
Install the Ruby and JavaScript dependencies:
bundle config deployment 'true'
bundle config without 'development test'
bundle install -j$(getconf _NPROCESSORS_ONLN)
yarn install --immutable
Wait for the install to finish before running the setup wizard.
#8. Generate the Mastodon configuration file
Run the interactive setup wizard:
RAILS_ENV=production bin/rails mastodon:setup
This process will:
- Create a configuration file
- Run asset precompilation
- Create the database schema
The configuration file is saved as .env.production. You can review and edit it as needed. For detailed options, see the official documentation.
Leave the mastodon shell for now:
exit
#9. Configure Nginx and SSL certificates
Use acme.sh to obtain free TLS certificates from Let's Encrypt. acme.sh is installed per user and its commands run without sudo; running this section as root (sudo -i) is recommended for reloading Nginx after certificate issuance:
cd ~
git clone https://github.com/acmesh-official/acme.sh.git
cd ./acme.sh
./acme.sh --install -m user@example.com
source ~/.bashrc
acme.sh --upgrade --auto-upgrade
acme.sh --set-default-ca --server letsencrypt
Replace user@example.com with your actual email address. If you leave the example address, Let's Encrypt will reject it with an error like this:
[Mon Apr 28 06:28:13 PM UTC 2025] Registering account: https://acme-v02.api.letsencrypt.org/directory
[Mon Apr 28 06:28:14 PM UTC 2025] Account registration error: {
"type": "urn:ietf:params:acme:error:invalidContact",
"detail": "Error creating new account :: contact email has forbidden domain \"example.com\"",
"status": 400
}
Create a directory for Let's Encrypt HTTP-01 verification and another directory for the SSL certificates:
sudo mkdir -p /var/www/letsencrypt/.well-known/{acme-challenge,pki-validation}
sudo chown www-data:www-data /var/www/letsencrypt -R
sudo mkdir -p /etc/nginx/ssl
Modify the default Nginx configuration file.
sudo bash -c 'cat > /etc/nginx/sites-available/default << "EOF"
server {
listen 80 default_server;
listen [::]:80 default_server;
server_name _;
location ~ ^/.well-known/(acme-challenge|pki-validation)/ {
root /var/www/letsencrypt;
}
location / {
return 301 https://$host$request_uri;
}
}
EOF'
This configuration redirects all requests on HTTP port 80 to HTTPS port 443, except for requests to /.well-known/acme-challenge/ and /.well-known/pki-validation/, which are served from the /var/www/letsencrypt directory.
The acme-challenge path is what Let's Encrypt uses for HTTP-01 validation. The pki-validation path is kept for other certificate workflows.
Reload Nginx:
sudo nginx -t
sudo nginx -s reload
Issue SSL certificates for your domain name:
acme.sh --issue -d example.com -w /var/www/letsencrypt
Replace
example.comwith your own domain name.
Install the certificates to /etc/nginx/ssl:
acme.sh --install-cert -d example.com \
--key-file /etc/nginx/ssl/example.com.key \
--fullchain-file /etc/nginx/ssl/example.com.crt \
--ca-file /etc/nginx/ssl/example.com.ca.crt \
--reloadcmd "sudo systemctl restart nginx"
You may need to add your current user to the sudoers file so acme.sh can restart Nginx without a password prompt. To do this, run:
sudo visudo
Add the following line at the end of the file:
your_username ALL=(ALL) NOPASSWD: /bin/systemctl restart nginx
Copy the Nginx configuration template from the Mastodon directory:
sudo cp /home/mastodon/live/dist/nginx.conf /etc/nginx/sites-available/mastodon
sudo ln -s /etc/nginx/sites-available/mastodon /etc/nginx/sites-enabled/mastodon
The template needs three edits: your domain, your certificate paths, and one deleted block.
First, set your domain in a shell variable and let sed handle the renaming. The match side stays literal (that is exactly what the template says), and the replacement side carries your domain, so this works for any real domain:
DOMAIN=example.com
sudo sed -i \
-e "s/server_name example.com;/server_name $DOMAIN;/g" \
-e "s|# ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;|ssl_certificate /etc/nginx/ssl/$DOMAIN.crt;|" \
-e "s|# ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;|ssl_certificate_key /etc/nginx/ssl/$DOMAIN.key;|" \
/etc/nginx/sites-available/mastodon
This sets server_name in both server blocks, and uncomments the two ssl_certificate lines while pointing them at the certificates acme.sh installed to /etc/nginx/ssl earlier.
Then open the file and delete the server block that starts with listen 80. Our default site from section 9 already redirects HTTP to HTTPS and serves the ACME challenges, so this block would only get in the way of renewals:
server {
listen 80;
listen [::]:80;
server_name example.com;
root /home/mastodon/live/public;
location /.well-known/acme-challenge/ { allow all; }
location / { return 301 https://$host$request_uri; }
}
After the edits, the top of the remaining server block should look like this:
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name example.com;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers on;
ssl_session_cache shared:SSL:10m;
ssl_session_tickets off;
ssl_certificate /etc/nginx/ssl/example.com.crt;
ssl_certificate_key /etc/nginx/ssl/example.com.key;
Create a cache folder for Nginx and reload it:
sudo mkdir -p /var/cache/nginx
sudo chown www-data:www-data /var/cache/nginx -R
sudo nginx -t
sudo nginx -s reload
#10. Set up systemd services
Copy the systemd service templates from the Mastodon directory.
sudo cp /home/mastodon/live/dist/mastodon-*.service /etc/systemd/system/
Start and enable the new systemd services:
sudo systemctl daemon-reload
sudo systemctl enable --now mastodon-web mastodon-sidekiq mastodon-streaming
Visit https://example.com/ in your browser to open your Mastodon instance.
#11. Enable Elasticsearch (optional)
Mastodon supports full-text search when Elasticsearch is available. Enable it only if your server has enough RAM and CPU; otherwise, keep the instance lighter and skip this section.
If you have already added the official Elasticsearch 8.x repository, install it with apt:
sudo apt install elasticsearch
Create a jvm.options file to configure Elasticsearch memory usage:
sudo bash -c 'echo "-Xms2g
-Xmx2g" > /etc/elasticsearch/jvm.options.d/jvm.options'
Elasticsearch 8 ships with security enabled but serves HTTPS with a self-signed certificate by default. For a localhost-only node it is simpler to keep authentication and turn off TLS on the HTTP layer:
echo "xpack.security.http.ssl.enabled: false" | sudo tee -a /etc/elasticsearch/elasticsearch.yml
Enable and start the systemd service:
sudo systemctl daemon-reload
sudo systemctl enable --now elasticsearch
Set a password for the built-in elastic user and note it down:
sudo /usr/share/elasticsearch/bin/elasticsearch-reset-password -u elastic
Edit the /home/mastodon/live/.env.production file and add the following lines, replacing password with the one you just generated:
# Elasticsearch (optional)
# ------------------------
ES_ENABLED=true
ES_HOST=localhost
ES_PORT=9200
# Authentication for ES (optional)
ES_USER=elastic
ES_PASS=password
Restart the Mastodon services so they read the new settings:
sudo systemctl restart mastodon-{web,sidekiq}
Run the following command to create the Elasticsearch indices and populate them with searchable data:
sudo -i -u mastodon
RAILS_ENV=production /home/mastodon/live/bin/tootctl search deploy