Home PageSocialsBlog MetaPoetryTechThoughts/RantsProjectsVPS SetupGit ReposFriends of STUTS

VPS Setup


Mastodon Setup


Private mastodon server for family, private by design with option to go public. That should be fine, right?


Ref - https://www.vultr.com/docs/installing-mastodon-on-centos-7

Install NodeJS

curl --silent --location https://rpm.nodesource.com/setup_8.x |bash
yum -y install nodejs

Install yarn

wget https://dl.yarnpkg.com/rpm/yarn.repo -O /etc/yum.repos.d/yarn.repo
yum -y install yarn

Install redis

yum -y install redis

systemctl start redis
systemctl enable redis

Compilation deps

yum -y install ImageMagick git libxml2-devel libxslt-devel gcc bzip2 openssl-devel zlib-devel gdbm-devel ncurses-devel autoconf automake bison gcc-c++ libffi-devel libtool patch readline-devel sqlite-devel glibc-headers glibc-devel libyaml-devel libicu-devel libidn-devel
yum -y groupinstall 'Development Tools'


yum install https://download.postgresql.org/pub/repos/yum/reporpms/EL-7-x86_64/pgdg-redhat-repo-latest.noarch.rpm
yum -y install centos-release-scl-rh

yum -y install postgresql11-server postgresql11-contrib postgresql11-devel
/usr/pgsql-11/bin/postgresql-11-setup initdb

vim /var/lib/pgsql/11/data/pg_hba.conf
    local from `peer` to `trust`
    2 x `ident` to `md5`

systemctl start postgresql-11
systemctl enable postgresql-11

passwd postgres

su - postgres
    createuser mastodon
        ALTER USER mastodon WITH ENCRYPTED password 'DBPassword' CREATEDB;

yum -y install libpqxx-devel protobuf-devel

Mastodon user & Ruby

adduser mastodon -d /opt/mastodon
usermod -aG wheel mastodon
passwd mastodon
su - mastodon

# Everything below done as mastodon user
gpg --keyserver hkp://keys.gnupg.net --recv-keys 409B6B1796C275462A1703113804BB82D39DC0E3 7D2BAF1CF37B13E2069D6956105BD0E739499BDB
curl -sSL https://get.rvm.io | bash -s stable
source /opt/mastodon/.rvm/scripts/rvm

rvm install 2.6
rvm use 2.6

gem install bundler

cd ~
git clone https://github.com/tootsuite/mastodon.git app
cd ~/app

git checkout v3.1.5

bundle config build.pg --with-pg-config=/usr/pgsql-11/bin/pg_config
bundle install --deployment --without development test

yarn install --pure-lockfile

# Use output of this for matching values in .env.production
RAILS_ENV=production bundle exec rake mastodon:webpush:generate_vapid_key

cp .env.production.sample .env.production
    Set LOCAL_DOMAIN to domain name (thefranks.club)
    Set database information
    Set Secrets
    Set Vapid keys
    Set SMTP server (Zoho TLS)

SAFETY_ASSURED=1 RAILS_ENV=production bundle exec rails db:setup
RAILS_ENV=production bundle exec rails assets:precompile

SSL (as root)

# Do as in SERVER.md


cat << 'EOF' > /etc/nginx/http.d/thefranks.club.conf
server {
  listen 443 ssl;
  listen [::]:443 ssl;
  server_name thefranks.club;

  ssl_certificate     /etc/ssl/nginx/fullchain-thefranks-club.pem;
  ssl_certificate_key /etc/ssl/nginx/key-thefranks-club.pem;

  keepalive_timeout    70;
  sendfile             on;
  client_max_body_size 0;

  root /opt/mastodon/app/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";
    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_buffering off;
    proxy_redirect off;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;

    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_buffering off;
    proxy_redirect off;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;

    tcp_nodelay on;

  error_page 500 501 502 503 504 /500.html;


cat << EOF > /etc/systemd/system/mastodon-web.service
Description=Mastodon Web Service

ExecStart=/bin/bash -lc 'bundle exec puma -C config/puma.rb'


cat << EOF > /etc/systemd/system/mastodon-queue.service
Description=Mastodon Queue Service

ExecStart=/bin/bash -lc 'bundle exec sidekiq -c 5 -q default -q mailers -q pull -q push'


cat << EOF > /etc/systemd/system/mastodon-api.service
Description=Mastodon Streaming

ExecStart=/bin/npm run start


chmod +x /opt/mastodon

firewall-cmd --permanent --zone=public --add-port=4000/tcp
firewall-cmd --reload

systemctl enable mastodon-web mastodon-queue mastodon-api 
systemctl start mastodon-web mastodon-queue mastodon-api

Fixing issues

# Redis < 4 in repos so Sidekiq was failing
yum install epel-release
yum remove redis
yum install rh-redis5.x86_64
systemctl start rh-redis5-redis

# Mail still not working but queue is

## Change all details of SMTP to stu@stuts.uk, no difference
## Looked into ActionMailer delivery errors
## Tried without TLS (587 -> 465)
## Enabled IMAP for my email account (through admin gui and mail settings gui)
## Linode restriction of SMTP ports

## Manually confirmed account through rails console

# Images are publicly available
## Enabled "Authorized Fetch" in .env.production
## Enabled "Whitelist Mode" as this may do the restrictions needed
## Note: perhaps it's the nginx config using public cache-control for images?
## A good ref for similar things: https://discourse.joinmastodon.org/t/completely-private-instance/1574/4
## Discussion on confidential hosting: https://github.com/tootsuite/mastodon/issues/9785
## Attempted commenting out the entire caching section for images in nginx (shrug)
## It should autheticate user for media (`before_action :authenticate_user!, if: :whitelist_mode?` in MediaController and MediaProxyController)
## Tried restarting everything (stop masto services, restart nginx, start masto services)
## Posted another image...
## No difference
## Asked Mark for help

New User Privacy

  • Click “Edit Profile” from the timeline
  • On the “Appearane” page select “Lock Account” (this gives you the power to decide who can follow you)
    • Click Save Changes
  • Go to Preferences -> Other in the left-hand menu
    • Select “Hide Your Network” (this prevents anyone from seeing who you follow and who follows you)
    • Set “Posting Privacy” to “Followers Only” (this allows only those you’ve accepted to follow you to see your content)
    • Click Save Changes

Mark Notes:

paperclip configuration can be tweaked to store in S3, or to store in a different place in the filesystem, but the S3 support (by mastodon) still only works with the public-read ACL — you can configure it differently, but you’ll get the usual S3 “permission denied” stuff so it won’t really work
i think the only solution is to “hack” on an attachment serving controller — i’d probably go:
configure mastodon/paperclip to serve attachments from a different URL base (i.e. change PAPERCLIP_ROOT_URL hereish: https://github.com/tootsuite/mastodon/blob/44f88a334b1630fc236bdd6fbd4cde42f2882e21/.env.nanobox#L86
implement a route & controller that listens for GET requests to the new URL, does the appropriate auth stuff and then does one of:
- rails serves file statically (probably fine on low-volume system)
- some kind of “sign upstream URL” and redirect (i.e. that might be the approach if you were storing objects in S3)
- redirect to a still-publicly-available URL served by nginx — not sure this is much of a win tho. you could poss do a “copy to temporary area and periodically sweep older files”. seems complex tho.probs the first one. afaics, that approach is the only fix for your requirement
if you can get at the mastodon auth stack, you might be able to mount a rack app at an appropriate URL that does the file serving

Further Mark Talk

  • Remove first 2 things from Nginx as they do public serving off stuff
  • Allow serving of static files in rails
  • Create a controller for the paths of the private files
    • This should check authentication in the show method
    • On unauthenticated, show an image that says “this image is on a private server” (this will help to address potential federation issues)
  • Profit?