Esta entrada es parte de una charla/taller para la #Fedifiesta del 1 de Febrero de 2025 en La EKO de Karabanchel.
Tenemos varias maneras de instalar Mastodon, una configurando todo lo necesario en nuestro servidor y otra instalando Docker y creando nuestro contenedor a partir del código fuente.
El primer paso es disponer de un servidor Linux, Debian o Ubuntu preferentemente. En este caso voy a usar la última versión LTS disponible de Ubuntu, la 24.04.
Empezamos instalando estos paquetes que son necesarios para lo que haremos a continuación.
sudo apt install -y curl wget gnupg apt-transport-https lsb-release ca-certificates
A continuación vamos a añadir los repositorios oficiales de NodeJS y PostgreSQL para tener las últimas versiones de ambos aplicativos.
sudo curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | sudo gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg
echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_20.x nodistro main" | sudo tee /etc/apt/sources.list.d/nodesource.list
sudo 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" | sudo tee /etc/apt/sources.list.d/postgresql.list
sudo apt update
Ahora que tenemos los nuevos repositorios listos, viene la instalación de todas las dependencias que necesita Mastodon:
sudo apt install -y imagemagick ffmpeg libvips-tools libpq-dev libxml2-dev libxslt1-dev file git-core g++ libprotobuf-dev protobuf-compiler pkg-config gcc autoconf bison build-essential libssl-dev libyaml-dev libreadline6-dev zlib1g-dev libncurses5-dev libffi-dev libgdbm-dev nginx nodejs redis-server redis-tools postgresql postgresql-contrib certbot python3-certbot-nginx libidn11-dev libicu-dev libjemalloc-dev
Activamos corepack para que se instale la versión correcta de yarn
sudo corepack enable
Y ahora vamos a crear el usuario mastodon, un usuario normal sin privilegios para vivir más tranquilos.
sudo adduser --disabled-password mastodon
Toca crear un usuario y una base de datos en PostgreSQL, vamos a entrar usando el usuario postgres
sudo -u postgres psql
Ejecutaremos esto ahora
CREATE USER mastodon CREATEDB;
\q
Ahora vamos ya a instalar Mastodon, lo primero es convertirnos en el usuario mastodon que hemos creado
sudo su - mastodon
Clonamos en repositorio de Mastodon
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)
En el momento de escribir esto la última versión es la 4.3.3

Ahora vamos a usar rbenv para gestionar la version de Ruby que queremos usar aquí. Ruby es el lenguaje de programación en el que están desarrolladas las tripas de Mastodon.
git clone https://github.com/rbenv/rbenv.git ~/.rbenv
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 root)"/plugins/ruby-build
Y ahora instalamos Ruby con
RUBY_CONFIGURE_OPTS=--with-jemalloc rbenv install
Cuando este paso termina, la duración depende de lo potente que sea nuestra máquina, instalamos las dependencias de Ruby y Javascript
bundle config deployment 'true'
bundle config without 'development test'
bundle install -j$(getconf _NPROCESSORS_ONLN)
yarn install
El sistema nos preguntará si queremos descargar yarn.js, le decimos que sí.

Y llegamos al punto en que ejecutamos la instalación interactiva de Mastodon!
RAILS_ENV=production bin/rails mastodon:setup
Nos hará varias preguntas como el dominio, si es una instancia para un solo usuario, si usamos Docker, base de datos, como vamos a enviar emails, etc.
![Your instance is identified by its domain name. Changing it afterward will break things.
Domain name: fedifiesta.louzao.network
Single user mode disables registrations and redirects the landing page to your public profile.
Do you want to enable single user mode? yes
Are you using Docker to run Mastodon? no
PostgreSQL host: /var/run/postgresql
PostgreSQL port: 5432
Name of PostgreSQL database: mastodon_production
Name of PostgreSQL user: mastodon
Password of PostgreSQL user:
Database configuration works! 🎆
Redis host: localhost
Redis port: 6379
Redis password:
Redis configuration works! 🎆
Do you want to store uploaded files on the cloud? No
Do you want to send e-mails from localhost? No
SMTP server: smtp.mailgun.org
SMTP port: 587
SMTP username:
SMTP password:
SMTP authentication: plain
SMTP OpenSSL verify mode: none
Enable STARTTLS: auto
E-mail address to send e-mails "from": Mastodon <notifications@fedifiesta.louzao.network>
Send a test e-mail with this configuration right now? no
Do you want Mastodon to periodically check for important updates and notify you? (Recommended) Yes
This configuration will be written to .env.production
Save configuration? Yes
Now that configuration is saved, the database schema must be loaded.
If the database already exists, this will erase its contents.
Prepare the database now? Yes
Running `RAILS_ENV=production rails db:setup` ...
I, [2025-01-31T18:32:45.898417 #24563] INFO -- : [dotenv] Loaded .env.production
Created database 'mastodon_production'
Done!
The final step is compiling CSS/JS assets.
This may take a while and consume a lot of RAM.
Compile the assets now? Yes
Running `RAILS_ENV=production rails assets:precompile` ...](https://louzao.network/wp-content/uploads/2025/01/image-2-1024x1002.png)
Al terminar de hacer sus cosas, el instalador nos preguntará si queremos crear un usuario administrador, le decimos que sí, indicamos el nombre de usuario y el email, el instalador nos devolverá una clave que luego podremos cambiar.

Ejecutamos un exit para volver a nuestro usuario con privilegios y comenzar la configuración del servidor web, lo primero es invocar a certbot para obtener un certificado que nos permita comunicarnos de manera segura con nuestra instancia.
sudo certbot certonly --nginx -d fedidiesta.louzao.network
Una vez recibido el certificado, configuramos Nginx, Mastodon nos lo pone fácil, primero vamos a copiar el archivo de configuración
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
sudo rm /etc/nginx/sites-enabled/default
Ahora editamos el archivo que hemos copiado
sudo vi /etc/nginx/sites-available/mastodon
Tan solo tenemos que cambiar example.com por nuestro dominio y quitar el comentario de las líneas que le dicen a Nginx donde está el certificado de Let’s Encrypt
Y para finalizar, vamos a permitir que www-data, el usuario que ejecuta Nginx, pueda tocar nuestra carpeta de Mastodon
chmod o+x /home/mastodon
Reiniciamos Nginx
sudo systemctl restart nginx
Y ahora vamos a activar los procesos que necesita ejecutar Mastodon para funcionar
sudo cp /home/mastodon/live/dist/mastodon-*.service /etc/systemd/system/
sudo systemctl daemon-reload
sudo systemctl enable --now mastodon-web mastodon-sidekiq mastodon-streaming
sudo systemctl restart mastodon*
Y listo, ya tenemos nuestra instancia funcionando!

Ahora vamos a tocar algunas cosas para mejorar el rendimiento y la seguridad.
Empezamos visitando esta vez para crear un archivo de configuración mejor para nuestra base de datos.
https://pgtune.leopard.in.ua
Con proporcionar la cantidad de RAM y el número de CPUs de nuestra máquina llega, la configuración generada metemos aquí
sudo vi /etc/postgresql/17/main/postgresql.conf
Reiniciamos el servidor para que los cambios tengan efecto
sudo systemctl restart postgresql
Tema seguridad… esto es una cosa más compleja de lo que parece, vamos a realizar una serie de pasos para garantizar unos mínimos sin necesidad de hacer un Master.
Empecemos por algo básico, instalaremos un cortafuegos y la utilidad Fail2Ban
sudo apt install ufw fail2ban
Antes de activar el cortafuegos, vamos a darle un par de órdenes, la primera para que deje el puerto SSH abierto, así podremos seguir conectándonos en remoto, la segunda le va a decir que abra los dos puertos que todo servidor web usa por defecto, 80 y 443.
sudo ufw allow ssh
sudo ufw allow "Nginx Full"
sudo ufw enable
Si miramos el status de ufw, deberíamos ver algo así

En cuanto a Fail2Ban, queremos bloquear a quienes intenten acceder por ssh y estén probando combinaciones de usuario y password.
Realmente aquí no hay nada que tocar, la configuración por defecto ya hace esto, podemos jugar con los tiempos de bloqueo, intentos, etc en /etc/fail2ban/jails.conf

Vamos a subir la apuesta utilizando una herramienta que se interponga entre nuestra máquina y el resto de la red, seguro a más de uno se le ocurren varias ideas de porqué no usar un servicio como Cloudflare, que al fin y al cabo es un bonito Man in The Middle (salvo que seas usuario de pago y puedas poner tus certificados en su perímetro de red).
Para esta charla/taller vamos a confiar plenamente en la versión gratuita de Cloudflare, nos registraremos, apuntaremos nuestro dominio a sus DNS y entraremos en el panel de control.
Existen dos maneras de configurar Cloudflare. La primera es simplemente ir a las opciones de DNS, decirle la IP de nuestro servidor y dejar que obre la magia.
Bueno, habría que dar un segundo paso, borrar de ufw las reglas de «Nginx Full» que activamos y limitar los puertos 80 y 443 para que solo escuchen las peticiones que vienen a través de Cloudflare.
Para eso debemos crear un script como este:
#!/bin/sh
curl -s https://www.cloudflare.com/ips-v4 -o /tmp/cf_ips
curl -s https://www.cloudflare.com/ips-v6 >> /tmp/cf_ips
# Allow all traffic from Cloudflare IPs (no ports restriction)
for cfip in `cat /tmp/cf_ips`;
do
ufw delete allow proto tcp from $cfip to any port 80,443;
ufw allow proto tcp from $cfip to any port 80,443 comment 'Cloudflare IP';
done
ufw reload > /dev/null
Si lo metemos en un cron y lo ejecutamos cada 30 minutos, nos aseguramos de que si cambia alguna de esas IP, nuestro cortafuegos estará actualizado.
Existe un segundo método, que además nos resultará útil si se nos ocurre montar nuestra instancia en una máquina que tengamos por casa y no tengamos muchas ganas de andar tocando el router para abrir puertos.
Hay un apartado que se llama Zero Trust en Cloudflare que nos permitirá configurar una de sus utilidades para dar acceso a Internet a nuestra instancia en esta situación o similar (como estar conectado a Internet con dispositivos 5G, internet por satélite, etc que no nos permita abrir puertos al exterior).
Dentro de Zero Trust iremos a la opción Tunnels que hay dentro de Redes.

Pincharemos en «Crear un Tunel» y elegiremos la opción de Cloudflared

Le daremos un nombre, fedifiesta en este caso, y lo guardaremos.
El siguiente paso consistirá en instalar Cloudflared en nuestra máquina.
sudo mkdir -p --mode=0755 /usr/share/keyrings
curl -fsSL https://pkg.cloudflare.com/cloudflare-main.gpg | sudo tee /usr/share/keyrings/cloudflare-main.gpg >/dev/null
echo 'deb [signed-by=/usr/share/keyrings/cloudflare-main.gpg] https://pkg.cloudflare.com/cloudflared noble main' | sudo tee /etc/apt/sources.list.d/cloudflared.list
sudo apt update && sudo apt install cloudflared
Y ya tenemos cloudflared instalado, ahora toca activarlo con el comando que tenemos esperando en la web de Cloudflare
sudo cloudflared service install eyJhI.......
Nos aparecerá el conector activado en la web y podremos ir al siguiente paso.
Elegimos el nombre del subdominio, el dominio al que estará asociado y en el apartado Servicio elegiremos https y en la URL pondremos localhost

Ya solo nos queda añadir un par de parámetros, en TLS -> Nombre del servidor de origen y en Configuración HTTP -> Encabezado de host HTTP pondremos el nombre completo de nuestro dominio, el que espera recibir nuestro Nginx para poder servir la instancia de Mastodon.

Y para cerrar esta charla/taller, nos acabaremos encontrando un problema que es la falta de espacio en disco. Dependiendo de cuanta gente sigamos, cuantas cosas publiquemos y cuanto espacio tiene nuestra máquina, nos podemos encontrar con que el disco se llena enseguida.
Para evitarlo conviene hacer limpieza de vez en cuando, por ejemplo cada día ejecutando un script de limpieza como este, que borra todo lo que tenga más de 7 días de antigüedad.
#!/usr/bin/bash
cd /home/mastodon/live/bin
RAILS_ENV=production ./tootctl media remove --days=7 --prune-profiles
RAILS_ENV=production ./tootctl media remove --remove-headers --include-follows --days 7
RAILS_ENV=production ./tootctl media remove --days 7
RAILS_ENV=production ./tootctl emoji purge --remote-only
RAILS_ENV=production ./tootctl statuses remove --days=7
RAILS_ENV=production ./tootctl media remove-orphans
RAILS_ENV=production ./tootctl preview_cards remove --days=7
RAILS_ENV=production ./tootctl cache clear
RAILS_ENV=production ./tootctl cache recount accounts
También funciona si en lugar de almacenar en local, hemos configurado almacenamiento en algún servicio compatible con S3, algo que seguramente cuente en alguna otra charla empleando un servidor propio basado en MinIO.
En mi instancia personal days lo tengo puesto a 4 y siguiendo a unas mil personas, necesita entre 20 y 30GB de espacio libre para funcionar. Esto no es una ciencia exacta, depende de si la gente a la que sigues está muy activa, si publican muchas fotos…
Y hasta aquí la charla de Fedifiesta para el Hacklab 0day, voy a añadir algunas cosas que no me dio tiempo a mostrar y que me parecen fundamentales.
Una de las máximas en ciberseguridad es reducir al mínimo la superficie de ataque, y en no confiar en nada.
En el paso del cortafuegos hemos dejado expuesto el puerto de SSH para poder acceder a la máquina y así poder realizar tareas de mantenimiento, actualización de Mástodon, etc.
Aunque configuremos el servidor de SSH para acceder usando solo claves criptográficas en lugar de passwords y tengamos Fail2Ban listo para bloquear intentos de login no autorizados, nada nos garantiza que en el futuro no aparezca alguna vulnerabilidad que permita a un atacante abusar de este servicio en remoto.
Para evitar esto, lo que debemos hacer es cerrar el puerto SSH, pero antes nos tenemos que asegurar de que disponemos de una manera de seguir accediendo nosotros a la máquina.
Existen varias opciones, vamos a ver dos, la primera es la más sencilla, que es abrir el puerto SSH en UFW solo para nuestra IP de casa.
Si tenemos una IP fija asignada por nuestro proveedor de internet, algo bastante raro, el paso es tan simple como ejecutar este comando
sudo ufw allow from PONAQUITUIPDECASA to any port ssh
Luego podemos borrar la regla que deja SSH abierto a todo el mundo.
sudo ufw delete allow ssh
En caso de no tener una IP fija, una solución es darnos de alta en algún servicio como Free DDNS e instalar su cliente en una máquina de nuestra casa que actualice el servicio.
Si creamos una direccion tipo miipdecasa.free-ddns.com, posteriomente podemos crear en nuestra máquina de Mastodon un script, que deberá ejecutarse en un cron cada cierto tiempo, por ejemplo cada 15 minutos, para abrir el puerto SSH a la dirección IP dinámica de nuestra casa.
(Nota: para poder comprobar si la IP ha cambiado, el script almacena el contenido de la IP activa en /root/scripts/homeip.old)
#!/bin/bash
oldip=`cat /root/scripts/homeip.old`
homeip=`/usr/bin/dig +short miipdecasa.free-ddns.com`
if [ "$oldip" != "$homeip" ]
then
ufw delete allow from $oldip to any port 22 comment 'Home IP'
ufw allow from $homeip to any port 22 comment 'Home IP'
echo $homeip > /root/scripts/homeip.old
ufw reload > /dev/null
fi
La otra opción es utilizar una herramienta como Tailscale.
Existen muchas alternativas, Cloudflare ofrece su servicio Zero Trust llamado Warp, tenemos ZeroTier, Pomerium…
Mi preferencia por la empresa canadiense Tailscale es por la sencillez de configuración de una red Zero Trust, que es mayormente opensource, salvo el servidor de control, pero hay alternativas libres para tener nuestro propio servidor de control, https://github.com/juanfont/headscale. Cuando tenga un poco de tiempo escribiré un post sobre como hacerlo.
El primer paso es registrarnos en Tailscale, te obligan a usar algún proveedor de identidad como Google, Microsoft, GitHub… afortunadamente soportan OpenID, así que podemos usar nuestra cuenta de Codeberg o GitLab con nuestro propio dominio.
La opción más sencilla es usar una cuenta de las primeras.
Una vez que nos hemos registrado, podremos empezar a añadir dispositivos a nuestra Tailnet.
En la sección de descargas tenemos disponible el cliente de Tailscale para Linux, Windows, Mac, iOS y Android.
Para aumentar la seguridad, y por si se nos filtra el comando para añadir dispositivos, activaremos la opción de verificar manualmente las nuevas máquinas en https://login.tailscale.com/admin/settings/device-management.

En nuestro servidor instalaremos el repositorio de Tailscale para instalar el cliente:
curl -fsSL https://pkgs.tailscale.com/stable/ubuntu/noble.noarmor.gpg | sudo tee /usr/share/keyrings/tailscale-archive-keyring.gpg >/dev/null
curl -fsSL https://pkgs.tailscale.com/stable/ubuntu/noble.tailscale-keyring.list | sudo tee /etc/apt/sources.list.d/tailscale.list
sudo apt update
sudo apt install tailscale
Luego iniciaremos Tailscale y lo añadiremos a nuestra Tailnet
sudo tailscale up
Obtendremos una URL que podemos debemos abrir en el navegador de nuestro ordenador

Posteriormente debemos ir a nuestro panel de control y aprobar que esta máquina entre a formar parte de nuestra Tailnet.
Ahora repetimos el proceso instalando el cliente en nuestro ordenador que usamos habitualmente, en el panel de control encontraremos la IP que les ha asignado Tailscale a cada uno.
Ahora mismo podemos hacer SSH con nuestro usuario a esa nueva dirección IP y podremos acceder y borrar la autorización de acceso al SSH en UFW
sudo ufw delete allow ssh
Tailscale no necesita una regla en UFW para acceder.
Y listo, hemos cerrado el puerto SSH y solo nosotros podremos conectarnos a la shell de nuestra máquina para tocar cosas.