Suppose you are running a (bare metal) webserver managed by ISPConfig and apache, and you want to run mastodon on it, it seems you are in trouble. But, there is a solution!

First, check if you are able to run a virtual machine on it. If so, continue reading. If not, consider continue reading to find out how to. Maybe it is possible to setup a vps as described in the vm part  below, and consider proxy pass the ISPConfig virtual host to that particular vps like described in the ISPConfig part later on. Untill now I did not test that, maybe a skilled reader can check, test and confirm or deny.

What happened?

So, the other day I decided that my amount of cloud servers at hetzner was getting out of control. I decided to setup a bare metal server from hetzner auction, and migrate al content, applications, websites from all of those cloudservers to that bare metal one and write a nice python app to automate some 80 to 90% of the maintenance, so I am able to have a beer with my friends and watch my sons soccer games.

Upon my research to migrate mastodon, and pretesting that on a local machine, I came across only one working solution. I stunbled on this blog.vyvojari.dev post (so, ALL CREDITS go there!), which finally brought me to my solution. Read that blog, it also gives a good idea how mastodon works and how the workaround works.

Lets go! I assume here, you have a working server with ISPConfig set up on it. This post was written using ubuntu 22.04, so maybe some commands need some love if you are on another distro.

Step 1. Create a VM and setup / migrate your mastodon server.

Donot execute the nginx setup as described. Instead of copying the file in the installation manual. Here comes the trick, at least, for the VM part:

Excecute

sudo nano /etc/nginx/sites-available/mastodon

and paste below code (make sure you replace <your_fqdn> for your qualified domain name!):

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 only on plain HTTP
  listen 80;
  listen [::]:80;
  server_name <your_fqdn>;

  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 image/svg+xml image/x-icon;

  location / {
    try_files $uri @proxy;
  }

  # If Docker is used for deployment and Rails serves static files,
  # then needed must replace line `try_files $uri =404;` with `try_files $uri @proxy;`.
  location = /sw.js {
    add_header Cache-Control "public, max-age=604800, must-revalidate";
    add_header Strict-Transport-Security "max-age=63072000; includeSubDomains";
    try_files $uri =404;
  }

  location ~ ^/assets/ {
    add_header Cache-Control "public, max-age=2419200, must-revalidate";
    add_header Strict-Transport-Security "max-age=63072000; includeSubDomains";
    try_files $uri =404;
  }

  location ~ ^/avatars/ {
    add_header Cache-Control "public, max-age=2419200, must-revalidate";
    add_header Strict-Transport-Security "max-age=63072000; includeSubDomains";
    try_files $uri =404;
  }

  location ~ ^/emoji/ {
    add_header Cache-Control "public, max-age=2419200, must-revalidate";
    add_header Strict-Transport-Security "max-age=63072000; includeSubDomains";
    try_files $uri =404;
  }

  location ~ ^/headers/ {
    add_header Cache-Control "public, max-age=2419200, must-revalidate";
    add_header Strict-Transport-Security "max-age=63072000; includeSubDomains";
    try_files $uri =404;
  }

  location ~ ^/packs/ {
    add_header Cache-Control "public, max-age=2419200, must-revalidate";
    add_header Strict-Transport-Security "max-age=63072000; includeSubDomains";
    try_files $uri =404;
  }

  location ~ ^/shortcuts/ {
    add_header Cache-Control "public, max-age=2419200, must-revalidate";
    add_header Strict-Transport-Security "max-age=63072000; includeSubDomains";
    try_files $uri =404;
  }

  location ~ ^/sounds/ {
    add_header Cache-Control "public, max-age=2419200, must-revalidate";
    add_header Strict-Transport-Security "max-age=63072000; includeSubDomains";
    try_files $uri =404;
  }

  location ~ ^/system/ {
    add_header Cache-Control "public, max-age=2419200, immutable";
    add_header Strict-Transport-Security "max-age=63072000; includeSubDomains";
    try_files $uri =404;
  }

  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 $scheme;
    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;

    add_header Strict-Transport-Security "max-age=63072000; includeSubDomains";

    tcp_nodelay on;
  }

  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 $scheme;
    proxy_set_header X-Forwarded-Proto https; # the lie is right here
    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;

    tcp_nodelay on;
  }

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

To prevent 500 error codes in the end, I also had to change permissions to the /home/mastodon directory. I am not sure it is required, but nginx needs permissions for the static files which are not sufficient out of the box appearently. Maybe you can test it (please comment if it works for you without):

chmod a+X /home/mastodon

Now restart nginx (the official guide tells to just reload it, I prefer to restart the service):

sudo systemctl restart nginx

And for the vm part, we are done! Congrats!

Time to finalize this in ISPConfig.

Step 2: Create domain in ISPConfig and proxy it

Head over to the ISPConfig control panel. Create a website with letsencrypt SSL as you are used to do.

(Free tip! Https is default nowadays. In the "Redirect" tab of the website editor, I always check "Rewrite HTTP to HTTPS")

Now, in ISPConfig, edit the website. In the options tab, Apache Directives, paste the code below, and be sure to replace <your_vm_ip> with the ip address of your vm:

    ProxyPreserveHost On

    ProxyPass / http://<your_vm_ip>:80/
    ProxyPassReverse / http://<your_vm_ip>:80/

 

Donot forget to hit "save"(clicking it will also do).

 

And we are done! Happy fediversing!