Update system packages​

apt update && apt upgrade -y

Install fail2ban so it blocks repeated login attempts​

apt install fail2ban

Edit with your favorite editor vi or nano /etc/fail2ban/jail.local and put this inside:

[DEFAULT]
destemail = your@email.here
sendername = Fail2Ban

[sshd]
enabled = true
port = 22

[sshd-ddos]
enabled = true
port = 22 

Restart fail2ban:

systemctl restart fail2ban

Install a firewall and only whitelist SSH, HTTP and HTTPS ports​

First, install iptables-persistent. During installation, it will ask you if you want to keep current rules–decline.

apt install -y iptables-persistent

Edit with your favorite editor vi or nano /etc/iptables/rules.v4 and put this inside:

👇

spoiler
*filter

# Allow all loopback (lo0) traffic and drop all traffic to 127/8 that doesn't use lo0
-A INPUT -i lo -j ACCEPT
-A INPUT ! -i lo -d 127.0.0.0/8 -j REJECT

# Accept all established inbound connections
-A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT

# Allow all outbound traffic - you can modify this to only allow certain traffic
-A OUTPUT -j ACCEPT

# Allow HTTP and HTTPS connections from anywhere (the normal ports for websites and SSL).
-A INPUT -p tcp --dport 80 -j ACCEPT
-A INPUT -p tcp --dport 443 -j ACCEPT

# Allow SSH connections
# The -dport number should be the same port number you set in sshd_config
-A INPUT -p tcp -m state --state NEW --dport 22 -j ACCEPT

# Allow ping
-A INPUT -p icmp -m icmp --icmp-type 8 -j ACCEPT

# Log iptables denied calls
-A INPUT -m limit --limit 5/min -j LOG --log-prefix "iptables denied: " --log-level 7

# Reject all other inbound - default deny unless explicitly allowed policy
-A INPUT -j REJECT
-A FORWARD -j REJECT

COMMIT

With iptables-persistent, that configuration will be loaded at boot time. But since we are not rebooting right now, we need to load it manually for the first time:

iptables-restore < /etc/iptables/rules.v4

Installing from source​

Pre-requisites

A machine running Ubuntu 20.04 (22.04) or Debian 11 that you have root access to A domain name (or a subdomain) for the Mastodon server, e.g. example.com

An e-mail delivery service or other SMTP server You will be running the commands as root. If you aren’t already root, switch to root:

System repositories

Make sure curl, wget, gnupg, apt-transport-https, lsb-release and ca-certificates is installed first:

apt install -y curl wget gnupg apt-transport-https lsb-release ca-certificates

Node.js​

curl -sL https://deb.nodesource.com/setup_16.x | bash -

PostgreSQL​

wget -O /usr/share/keyrings/postgresql.asc https://www.postgresql.org/media/keys/ACCC4CF8.asc

echo “deb [signed-by=/usr/share/keyrings/postgresql.asc] http://apt.postgresql.org/pub/repos/apt $(lsb_release -cs)-pgdg main” > /etc/apt/sources.list.d/postgresql.list

System packages​

apt update
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 libgdbm-dev \
nginx redis-server redis-tools postgresql postgresql-contrib \
certbot python3-certbot-nginx libidn11-dev libicu-dev libjemalloc-dev 

Yarn​

corepack enable
yarn set version stable

Installing Ruby​

We will be using rbenv to manage Ruby versions, because it’s easier to get the right versions and to update once a newer release comes out. rbenv must be installed for a single Linux user, therefore, first we must create the user Mastodon will be running as:

adduser --disabled-login mastodon

We can then switch to the user:

su - mastodon

And proceed to install rbenv and rbenv-build:

👇

spoiler

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 exec bash git clone https://github.com/rbenv/ruby-build.git ~/.rbenv/plugins/ruby-build

Once this is done, we can install the correct Ruby version:

RUBY_CONFIGURE_OPTS=–with-jemalloc rbenv install 3.2.2

rbenv global 3.2.2

We’ll also need to install bundler:

gem install bundler --no-document

Return to the root user:

exit

Setup

Setting up PostgreSQL

Performance configuration (optional)

For optimal performance, you may use pgTune to generate an appropriate configuration and edit values in /etc/postgresql/14/main/postgresql.conf before restarting PostgreSQL with systemctl restart postgresql

Creating a user

You will need to create a PostgreSQL user that Mastodon could use. It is easiest to go with “ident” authentication in a simple setup, i.e. the PostgreSQL user does not have a separate password and can be used by the Linux user with the same username.

Open the prompt:

sudo -u postgres psql

In the prompt, execute:

CREATE USER mastodon CREATEDB;

\q

Done!

Setting up Mastodon​

It is time to download the Mastodon code. Switch to the mastodon user:

su - mastodon

git clone https://github.com/mastodon/mastodon.git live && cd live

Check out the latest stable branch.

git checkout $(git tag -l | grep -v ‘rc[0-9]*$’ | sort -V | tail -n 1)

Install the remaining Ruby dependencies:

bundle config deployment 'true'

bundle config without 'development test'

bundle install -j$(getconf _NPROCESSORS_ONLN)

Use yarn to install the node.js dependencies:

yarn install --pure-lockfile

Type and enter exit to return to the root account.

Requesting a Let’s Encrypt Certificate​

Stop Nginx before requesting the certificate:

systemctl stop nginx

Use certbot to request a certificate with TLS SNI validation in standalone mode. Replace example.com with your domain name:

certbot certonly --standalone -d example.com

As Let’s Encrypt certificates have a validity of 90 days, a cron-job can be used to renew them and restart nginx automatically.

Create a new file and open it in a text editor like nano:

nano /etc/cron.daily/letsencrypt-renew

Copy the following content into the file, save it and exit nano:

#!/usr/bin/env bash
certbot renew

systemctl reload nginx

Allow execution of the script and restart the cron daemon. It will run the script daily:

chmod +x /etc/cron.daily/letsencrypt-renew systemctl restart cron

Configuring Nginx​

Copy the example configuration file shipped with Mastodon in your Nginx sites-available directory and create a symlink to it in the sites-enabled directory:

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

Open the configuration file in a text editor, such as nano:

nano /etc/nginx/sites-available/mastodon

Replace example.com in the configuration file with your domain or subdomain. Replace with your domain in all following occurrences as well.

👇

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

upstream backend {
server 127.0.0.1:3000 fail_timeout=0;
}

upstream streaming {
server 127.0.0.1:4000 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;
server_name example.com; &lt;- /!\ Replace example.com with your domain name /!\
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;
server_name example.com;

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/example.com/fullchain.pem; &lt;- /!\ Replace example.com with your domain name and uncomment this line /!\
# ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem; &lt;- /!\ Replace example.com with your domain name and uncomment this line /!\

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";

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;

tcp_nodelay on;
}

error_page 500 501 502 503 504 /500.html;
} 

Save and exit.

Configuring the Mastodon Application​

Enter the mastodon user account:

su - mastodon

Change into the /home/mastodon/live directory and run the Mastodon installer:

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

The interactive installer guides you through the setup process.

  1. Enter the domain name or subdomain of the instance. 2 .Select No when asked if you want to use Docker.

Most of the other values are already pre-filled with the correct settings. Edit them if required for your setup.

  1. Optional - Select Amazon S3 as a service provider to set up Mastodon with Object Storage. Valid API keys are required in this step.

Enter the details as follows:

Provider Amazon S3

S3 bucket name: [scaleway_bucket_name]

S3 region: fr-par

S3 hostname: s3.fr-par.scw.cloud

S3 access key: [scaleway_access_key]

S3 secret key: [scaleway_secret_key]

Note:

If your bucket is located in Amsterdam, use nl-ams as region and s3.nl-ams.scw.cloud as S3 hostname If it is located in Warsaw, use pl-waw and s3.pl-waw.scw.cloud.

Once the configuration is complete, the installer will start to compile the application. This may take some time and consume a lot of RAM.

Once the application is installed, you will be asked if you want to create an Administrator account for your Mastodon instance.

Type Y to create the account. 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 need it to connect to your Instance.

All done! You can now power on the Mastodon server ?

Do you want to create an admin user straight away? Yes

Username: admin <– Enter the username for your Mastodon admin account

E-mail: me@myemail.com <– Enter your email address

You can login with the password: 9dc4d92d93a26e9b6c021bb75b4a3ce2

Type exit to switch back into the root account.

Setting-up systemd Services​

Systemd scripts are used to manage services on Ubuntu systems. Three different scripts are required for Mastodon. These scripts come with the Mastodon package, you need to copy them to their final destination, then activate the services.

Copy the Mastodon systemd scripts into their final destination:

cp /home/mastodon/live/dist/mastodon-*.service /etc/systemd/system/

Reload the systemd daemon:

systemctl daemon-reload

Start the services and enable them, so they will start automatically upon the next system reboot:

systemctl start mastodon-web mastodon-sidekiq mastodon-streaming systemctl enable mastodon-web mastodon-sidekiq mastodon-streaming

Verify if all services are running:

systemctl status mastodon-*.service

If everything is running, open a web browser and go to your domain name. You will see the home page of your Mastodon instance. 👍