Last weekend, I setup since my previous account at offered by a friend is retired after an year of excellent service. After I set it up, there was many questions from friends in fediverse asking me to write a #HowTo to simply  my installation.

Mastodon is a free and open-source self-hosted social networking service. It allows anyone to host their own server node in the network, and its various separately operated user bases are federated across many different servers. Pleroma is another code base which can do same functionality of Mastodon with more light weight environment, but I decided to choose Mastodon  because of its single user mode functionality and more easy installation possibility.

I am running this on a VPS300 worth 3.99  per month Contabo a German Webhosting company . Their server configuration powerful enough to run multiple services.

I am not mentioning how to use do initial setup. I hope you will do following

  • You need a VPS  running  Ubuntu Focal Fossa 20.04 LTS and you have configured your SSH Key to user instance, created a sudo user and logged into the sudo user via SSH. These steps will be usually covered under Initial setup Howtos in Digital Ocean
  • You have a domain  or subdomain pointing to the instance IP address via A/AAAA

Pre Installation

After logging in the instance, do following and install Nodejs , current LTS version and install yarn as package manager

sudo apt update && apt upgrade -y
sudo apt install curl -y
sudo curl -sL | sudo bash -
sudo curl -sS | sudo apt-key add -
sudo echo "deb stable main" | sudo tee /etc/apt/sources.list.d/yarn.list
sudo apt update

Mastodon dependencies are

  • Imagemagick for image related operations
  • FFmpeg for conversion video and gifs
  • Protobuf with libprotobuf-dev and protobuf-compiler, used for language detection
  • Nginx as frontend web server
  • Redis for its in-memory data structure store
  • PostgreSQL is used as SQL database for Mastodon
  • Node.js is used for Mastodon’s streaming API
  • Yarn for node package management
  • Certbot for SSL/TSL

Install Certbot first as mentioned in my previous post.

sudo apt update
sudo apt install snapd
sudo snap install core; sudo snap refresh core
sudo apt-get remove certbot
sudo snap install --classic certbot
sudo ln -s /snap/bin/certbot /usr/bin/certbot
sudo snap set certbot trust-plugin-with-root=ok

Setup DNS plugin if needed as in previous post.

Some extra packages are required for the compilation of Ruby using ruby-build.

sudo apt install -y imagemagick ffmpeg libpq-dev libxml2-dev libxslt1-dev file git-core g++ libprotobuf-dev protobuf-compiler pkg-config nodejs gcc autoconf bison build-essential libssl-dev libyaml-dev libreadline6-dev zlib1g-dev libncurses5-dev libffi-dev libgdbm6 libgdbm-dev nginx redis-server redis-tools postgresql postgresql-contrib nginx yarn libidn11-dev libicu-dev libjemalloc-dev

We are going to use rbenv to manage Ruby versions. We need to create a user under which Mastodon will run. The --disabled-login flag disables direct login to the user account for increased security. log into the mastodon user account and enter into the home directory of the user:

sudo adduser --disabled-login mastodon
sudo su mastodon

Setting up ruby-build under rbenv

git clone ~/.rbenv
cd ~/.rbenv && src/configure && make -C src
echo 'export PATH="$HOME/.rbenv/bin:$PATH"' >> ~/.bashrc
echo 'eval "$(rbenv init -)"' >> ~/.bashrc
# Restart the users shell
exec bash
# Check if rbenv is correctly installed
type rbenv
# Install ruby-build as a rbenv plugin
git clone ~/.rbenv/plugins/ruby-build

Mastodon uses 2.6.6 as ruby version. After Compilation Install bundler as well and exit to base user.

RUBY_CONFIGURE_OPTS=--with-jemalloc rbenv install 2.6.6
rbenv global 2.6.6
gem install bundler --no-document

Next step is setting up postgreSQL. I am not addressing optimization, you can configure your postgres optimized for performance as per sever capacities , if needed

sudo -u postgres psql

Installing Mastodon

Download mastodon under new user account and checkout latest stable branch

sudo su - mastodon
git clone live && cd live
git checkout $(git tag -l | grep -v 'rc[0-9]*$' | sort -V | tail -n 1)

Install Ruby dependencies and nodejs dependencies

bundle config deployment 'true'
bundle config without 'development test'
bundle install -j$(getconf _NPROCESSORS_ONLN)
yarn install --pure-lockfile

Setting up Let's Encrypt
on sudo user you can get Let's encrypt certificates using certbot installed earlier either via default method(TLS SNI validation) or DNS method . I used DNS method as in previous post and not the step below

sudo systemctl stop nginx 
sudo certbot certonly --standalone -d
sudo certbot renew --dry-run

Setting up NGINX

sudo cp /home/mastodon/live/dist/nginx.conf /etc/nginx/sites-available/
sudo ln -s /etc/nginx/sites-available/ /etc/nginx/sites-enabled/

Open and edit the file.

nano /etc/nginx/sites-available/

Replace  with your servername  and save. Uncomment cert lines since you already created it with certbot

map $http_upgrade $connection_upgrade {
  default upgrade;
  ''      close;

upstream backend {
    server fail_timeout=0;
upstream streaming {
    server fail_timeout=0;

proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=CACHE:10m inactive=7d max_size=1g;

server {
  listen 80;
  listen [::]:80;
  root /home/mastodon/live/public;
  location /.well-known/acme-challenge/ { allow all; }
  location / { return 301 https://$host$request_uri; }

server {
  listen 443 ssl http2;
  listen [::]:443 ssl http2;

  ssl_protocols TLSv1.2 TLSv1.3;
  ssl_ciphers HIGH:!MEDIUM:!LOW:!aNULL:!NULL:!SHA;
  ssl_prefer_server_ciphers on;
  ssl_session_cache shared:SSL:10m;

  # Uncomment these lines once you acquire a certificate:
  ssl_certificate     /etc/letsencrypt/live/;
  ssl_certificate_key /etc/letsencrypt/live/;
  keepalive_timeout    70;
  sendfile             on;
  client_max_body_size 80m;

  root /home/mastodon/live/public;

  gzip on;
  gzip_disable "msie6";
  gzip_vary on;
  gzip_proxied any;
  gzip_comp_level 6;
  gzip_buffers 16 8k;
  gzip_http_version 1.1;
  gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;

  add_header Strict-Transport-Security "max-age=31536000";

  location / {
    try_files $uri @proxy;

  location ~ ^/(emoji|packs|system/accounts/avatars|system/media_attachments/files) {
    add_header Cache-Control "public, max-age=31536000, immutable";
    add_header Strict-Transport-Security "max-age=31536000";
    try_files $uri @proxy;
 location /sw.js {
    add_header Cache-Control "public, max-age=0";
    add_header Strict-Transport-Security "max-age=31536000";
    try_files $uri @proxy;
  location @proxy {
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto https;
    proxy_set_header Proxy "";
    proxy_pass_header Server;
    proxy_pass http://backend;
    proxy_buffering on;
    proxy_redirect off;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection $connection_upgrade;
    proxy_cache CACHE;
    proxy_cache_valid 200 7d;
    proxy_cache_valid 410 24h;
    proxy_cache_use_stale error timeout updating http_500 http_502 http_503 http_504;
    add_header X-Cached $upstream_cache_status;
    add_header Strict-Transport-Security "max-age=31536000";
#    proxy_max_temp_file_size 0;
    tcp_nodelay on;
  location /api/v1/streaming {
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto https;
    proxy_set_header Proxy "";
    proxy_pass http://streaming;
    proxy_buffering off;
    proxy_redirect off;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection $connection_upgrade;
 #   proxy_max_temp_file_size 0;

    tcp_nodelay on;

  error_page 500 501 502 503 504 /500.html;

Restart Nginx

sudo nginx -t
sudo service nginx restart

Before Final Configuration, you need an SMTP provider.

You have multiple ways to create it

  1. Once you configured 2FA in gmail, it allows you to create app passwords. Gmail with app password effectively works as a SMTP server.  It is a popular method.
  2. Get one via mailgun (paid) or Sendinblue. I used sendinblue, since they provide a free SMTP service with upto 300 messages free per day, which is good enough for a single user notifications.

Also if you are using ufw,  openup , 'Nginx Full'  or 'Nginx HTTPS' along with OpenSSH.
I think SMTP ports also need to be open to send out email notifications

sudo ufw app list  
sudo ufw allow OpenSSH
sudo ufw allow 'Nginx Full'
sudo ufw allow 587/tcp
sudo ufw status

Now enter mastodon user and lets start configuring the application

sudo su - mastodon
cd /home/mastodon/live
RAILS_ENV=production bundle exec rake mastodon:setup

This interactive installer guides through the setup process.  Make sure to select single user mod, if you are setting up instance for a single user. then it will redirect the base domain to user profile. When asked to use Docker choose No. Most of the other values are already pre-filled with the correct settings. Edit them if required for your setup.
For Object Storage, i selected No. If you like you can use an object storage, you need to give API credentials. At the end of installation, it will prompt you to create a administrator username.  Enter the username for the admin user, followed by your email address. A random password will be generated. Take a note of it, as you will require it to connect to your instance.

Setting-up systemd Services

Copy the Mastodon systemd scripts into the needed location and start all three  mastodon services

sudo cp /home/mastodon/live/dist/mastodon-*.service /etc/systemd/system/
sudo systemctl daemon-reload
systemctl start mastodon-web mastodon-sidekiq mastodon-streaming
systemctl enable mastodon-web mastodon-sidekiq mastodon-streaming

Make sure all are running

systemctl status mastodon-*.service

You can go to your browser and run mastodon, if everything running correctly

This is based on official documentation, modified for Ubuntu 20.04