Servidores

MinIO self-hosted en 2026: tu propio S3-compatible storage

Si tu homelab necesita almacenamiento de objetos compatible con la API S3 — para respaldos con restic, fotos con Nextcloud, archivos estáticos para una web, o como destino de rclone desde cualquier máquina — MinIO sigue siendo la opción más popular. Es liviano, corre en cualquier Linux, y expone una API que cualquier cliente S3 entiende sin tocar una línea de código. Pero en 2026 hay matices que conviene saber antes de levantar uno: el cambio de licencia a AGPLv3, la eliminación de la consola web en la versión community, y un puñado de errores de configuración que revientan a la primera subida.

Esta guía te lleva de cero a un MinIO hardened detrás de Caddy con TLS válido, listo para que restic, aws-cli, rclone o tu aplicación favorita lo usen como destino S3.

TL;DR — Lo que vas a tener al final

  • Tiempo de setup: 30-50 minutos para single-node, 1-2 horas para cluster.
  • Requisitos: Linux moderno, 4+ GB RAM, disco dedicado para data.
  • Resultado: API S3 completa con deduplicación y cifrado.
  • Troubleshooting: mc admin info, permisos de /data, errores de No space left on device.

Por qué MinIO y no solo un NFS

El almacenamiento de objetos (object storage) trabaja con buckets y keys en lugar de archivos y carpetas. Cada objeto tiene una clave plana (backups/2026-08-19/home.tar.gz) y metadata arbitraria. Esto le permite a MinIO escalar a miles de millones de objetos sin un sistema de archivos tradicional, replicar entre nodos, y hablar el mismo idioma que Amazon S3, Cloudflare R2 o Backblaze B2. El beneficio práctico: si tu aplicación ya sabe hablar S3 (y casi todas lo saben), cambiar de proveedor es una variable de entorno.

MinIO está escrito en Go, corre como binario único o contenedor, y consume muy poca RAM para un homelab (arranca con 1 GB y escala según los objetos que sirvas). La diferencia con montar un NFS o SMB es grande: en object storage no hay bloqueos de archivos, no hay jerarquía de carpetas real, y la consistencia es inmediata por nodo con replicación síncrona entre nodos del mismo deployment. Para backups cifrados, archivos multimedia grandes, o servir como CDN interno, eso es justo lo que quieres.

Nota sobre la licencia. En abril de 2021 MinIO cambió de Apache 2.0 a GNU AGPLv3. Para uso personal o interno eso no cambia nada — la AGPL te permite correrlo, modificarlo y redistribuirlo mientras no ofrezcas el servicio al público como competidor de MinIO Inc. La OSI (Open Source Initiative) no reconoce la AGPLv3 como “open source” en sentido estricto porque exige liberar el código de toda la pila que uses para servir a usuarios externos. Si tu caso de uso es self-hosting puro, no te afecta. Si quieres evitar la AGPL por filosofía, las alternativas son Garage, SeaweedFS (Apache 2.0) o Ceph (LGPL). Garage es la más cercana en simplicidad a MinIO para un solo nodo; SeaweedFS escala más y maneja mejor archivos pequeños; Ceph es la bestia de tres letras que solo tiene sentido si vas a montar un cluster de varias máquinas.

Nota sobre la consola web. Desde la versión de mediados de 2025, MinIO removió la UI de administración de la edición community. Crear buckets, configurar políticas y rotar claves ahora se hace por el cliente mc o por llamadas directas a la API S3. Si quieres una GUI bonita, opciones como FileBrowser montado contra el bucket o storj.io/pio (en beta) cubren ese hueco.

Preparar el host

MinIO no necesita mucho, pero conviene separar datos y configuración desde el día uno para que las actualizaciones y los reinicios no te peguen un susto.

Partimos de un Ubuntu 24.04 LTS o Debian 13 fresco, con un usuario dedicado (minio) y dos directorios:

sudo useradd -r -s /sbin/nologin -d /var/lib/minio minio
sudo mkdir -p /var/lib/minio/data /etc/minio
sudo chown -R minio:minio /var/lib/minio /etc/minio

/var/lib/minio/data es donde vivirán los objetos. Si vas a usar discos grandes (>2 TB) monta aquí un volumen ZFS o Btrfs con compresión. La compresión ZSTD en ZFS es prácticamente gratis en CPU moderna y te ahorra entre 10% y 30% en espacio para backups. Si vas a usar varios discos en el mismo servidor, deja que MinIO haga erasure coding por ti (lo configuramos más abajo).

/etc/minio guardará el archivo minio.env con las credenciales y la configuración. Nunca pongas las claves en el docker-compose.yml directamente: el archivo se monta dentro del contenedor y queda en el historial de git si descuidas el .gitignore.

Ahora el firewall. MinIO expone dos puertos: el API S3 (default 9000) y la consola (default 9001, ahora inútil en community). Solo el 9000 debería ser alcanzable, y solo desde tu red local o desde el reverse proxy.

sudo ufw allow from 192.168.0.0/16 to any port 9000 proto tcp
sudo ufw deny 9001

Si vas a exponerlo a internet detrás de Caddy, abre el 443 público y deja el 9000 restringido a localhost. MinIO tiene rate limiting configurable desde 2023 (mc admin config get/set), pero viene con límites permisivos por default. Si lo dejas expuesto sin proxy te van a hacer scan y credential stuffing en menos de una hora — el rate limit nativo no es suficiente como única defensa.

Levantar MinIO con Docker Compose

Este compose es la base. Usa la imagen oficial minio/minio, monta los volúmenes por nombre para que sobrevivan a docker compose down, y crea el archivo .env separado.

sudo mkdir -p /opt/minio
cd /opt/minio
sudo tee .env <<'EOF'
MINIO_ROOT_USER=minioadmin
MINIO_ROOT_PASSWORD=CAMBIAR_ESTA_CLAVE_LARGA_Y_ALEATORIA
MINIO_VOLUMES=/var/lib/minio/data
MINIO_OPTS=--console-address :9001
EOF
sudo chmod 600 .env
sudo chown minio:minio .env

La contraseña de root debe ser de al menos 16 caracteres aleatorios. openssl rand -base64 24 te da una. La variable MINIO_OPTS=--console-address :9001 mantiene el puerto de la consola vivo por si MinIO decide devolverlo en alguna versión futura, pero recuerda que en community ya no funciona la UI. Más adelante deshabilitamos la consola por completo.

Ahora el compose:

sudo tee docker-compose.yml <<'EOF'
services:
  minio:
    image: minio/minio:RELEASE.2025-09-07T16-13-09Z
    container_name: minio
    restart: unless-stopped
    user: "1000:1000"
    env_file:
      - .env
    command: server ${MINIO_VOLUMES} --console-address :9001
    volumes:
      - /var/lib/minio/data:/var/lib/minio/data
      - /etc/minio:/etc/minio
    ports:
      - "127.0.0.1:9000:9000"
      - "127.0.0.1:9001:9001"
    healthcheck:
      test: ["CMD", "mc", "ready", "local"]
      interval: 30s
      timeout: 10s
      retries: 3
    logging:
      driver: json-file
      options:
        max-size: "10m"
        max-file: "3"
  minio-init:
    image: minio/mc:RELEASE.2025-08-13T08-35-41Z
    container_name: minio-init
    depends_on:
      minio:
        condition: "service_healthy"
    entrypoint: >
      /bin/sh -c "
      mc alias set local http://minio:9000 '$$MINIO_ROOT_USER' '$$MINIO_ROOT_PASSWORD' &&
      mc mb --ignore-existing local/backups &&
      mc mb --ignore-existing local/media &&
      mc anonymous set none local/media &&
      mc admin info local || true
      "
    env_file:
      - .env
    restart: "no"
EOF

El user: "1000:1000" mapea al usuario minio que creamos antes. Verifica el UID con id minio. El healthcheck usa el cliente mc desde el propio contenedor para confirmar que el API está respondiendo; el init container crea dos buckets iniciales (backups y media) y bloquea el acceso anónimo al segundo para que no se use como CDN abierto por accidente.

Para arrancarlo:

cd /opt/minio && sudo docker compose up -d
sudo docker compose logs -f minio | head -50

Si ves MinIO Object Storage Server en los logs y al final API: http://172.x.x.x:9000 https://127.0.0.1:9001, todo está en pie. La parte del 127.0.0.1 en el log es normal: MinIO está obligado a anunciar al menos una dirección interna, pero tu puerto publicado está en localhost y el reverse proxy llegará.

Si el healthcheck falla con connection refused durante el up, suele ser que MINIO_VOLUMES apunta a una ruta que el usuario 1000 no puede escribir. Verifica sudo -u minio touch /var/lib/minio/data/test && sudo rm /var/lib/minio/data/test.

Configurar el cliente mc

mc (MinIO Client) es como el aws-cli pero hecho por el mismo equipo. Lo necesitamos para todo lo que antes hacía la consola web. Instálalo en tu máquina de administración, no en el server.

curl -O https://dl.min.io/client/mc/release/linux-amd64/mc
chmod +x mc
sudo mv mc /usr/local/bin/
mc --version

Apunta al servidor local. Aunque el server escucha en 127.0.0.1:9000, mc corre en tu laptop, así que la dirección es la del server en tu LAN.

mc alias set local http://192.168.1.50:9000 minioadmin 'TU_CLAVE_LARGA'
mc admin info local

mc admin info te devuelve versión, uso de disco y uptime. Si ves algo como Uptime: 5 minutes y Servers: 1/1, todo OK. Si dice connection refused, estás apuntando a la IP equivocada o el firewall del server está bloqueando tu red — recuerda que abrimos 9000 solo a 192.168.0.0/16 en el paso de firewall.

Ahora crea un bucket adicional para pruebas y verifica que se puede escribir:

mc mb local/test
mc cp /etc/hosts local/test/hosts-backup.txt
mc ls local/test
mc cat local/test/hosts-backup.txt | head

Si el mc cat devuelve el contenido de tu /etc/hosts, la cadena está completa: Docker → MinIO → bucket → objeto. Borra el bucket de prueba cuando termines:

mc rb --force local/test

Reverse proxy con Caddy y TLS válido

Exponer MinIO directamente a internet con un puerto arbitrario y sin TLS es pedir problemas. Caddy te da HTTPS automático con Let’s Encrypt sin tocar archivos de configuración complejos. Si todavía no tienes Caddy instalado, sigue nuestra guía de Caddy como reverse proxy en Linux 2026 (post 2597) y vuelve cuando la tengas funcionando.

El truco con MinIO detrás de un proxy es que el protocolo S3 firma las peticiones incluyendo el dominio. Si el cliente envía Host: minio.tudominio.com y el proxy lo reenvía sin preservarlo, MinIO rechaza la firma. Hay que asegurarse de que Caddy reenvía el header Host tal cual y que MinIO sabe que está detrás de un proxy.

Crea un Caddyfile dedicado:

sudo tee /etc/caddy/sites/minio.caddy <<'EOF'
minio.tudominio.com {
    encode zstd gzip
    reverse_proxy 127.0.0.1:9000 {
        header_up Host {host}
        header_up X-Real-IP {remote}
        header_up X-Forwarded-For {remote}
        header_up X-Forwarded-Proto https
        transport http {
            dial_timeout 30s
            response_header_timeout 30s
        }
    }
}
EOF
sudo systemctl reload caddy

Espera un minuto a que Let’s Encrypt emita el certificado y verifica:

curl -sI https://minio.tudominio.com/minio/health/live

Si devuelve HTTP/2 200 con server: cloudflare o server: Caddy, el TLS está funcionando. Si devuelve 502 Bad Gateway, Caddy no puede alcanzar MinIO: revisa que 127.0.0.1:9000 está abierto con ss -tlnp | grep 9000 desde el server.

Una vez que el proxy responde, actualiza el alias de mc para usar HTTPS:

mc alias set minio https://minio.tudominio.com minioadmin 'TU_CLAVE_LARGA' --api S3v4
mc admin info minio

La opción --api S3v4 fuerza la firma v4, que es lo que espera cualquier cliente S3 moderno (incluido el aws-cli).

Hardening: usuarios, políticas y rotación

El usuario root que creaste al inicio es solo para emergencias. Para cada aplicación que use el bucket crea un usuario dedicado con política limitada. Si un día se filtra esa clave, la cancelas sin tocar todo el resto.

mc admin user add minio backup-user 'CLAVE_DE_32_CHARS_ALEATORIA'
mc admin policy attach minio readwrite-backups --user backup-user

Donde readwrite-backups es el nombre de una política que defines así:

mc admin policy info minio readwrite-backups > /tmp/policy.json 2>/dev/null
cat > /tmp/policy.json <<'EOF'
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "s3:GetObject",
        "s3:PutObject",
        "s3:DeleteObject",
        "s3:AbortMultipartUpload",
        "s3:ListBucketMultipartUploads",
        "s3:ListBucket",
        "s3:GetBucketLocation"
      ],
      "Resource": [
        "arn:aws:s3:::backups",
        "arn:aws:s3:::backups/*"
      ]
    }
  ]
}
EOF
mc admin policy create minio readwrite-backups /tmp/policy.json
mc admin policy attach minio readwrite-backups --user backup-user

La política limita al usuario a solo el bucket backups, no a todos los del servidor. Si el bucket media se vuelve público por error o alguien sube algo confidencial, el usuario backup-user no puede ni leerlo. Este patrón de “una clave por aplicación, con el scope justo” es la primera línea de defensa real.

Para rotar la clave cada 90 días hay un comando directo:

mc admin user svcacct set-secret minio ACCESS_KEY_DE_BACKUP_USER 'CLAVE_NUEVA'

Los clientes S3 que estén usando la clave vieja empezarán a recibir 403 SignatureDoesNotMatch cuando la cambies, así que coordina la rotación con un reinicio programado del servicio que la consume, o usa el comando mc para actualizar el secret sin downtime.

Deshabilitar la consola y cerrar puertos

La consola web en :9001 ya no tiene UI en community edition, pero el puerto sigue abierto y responde a requests HTTP. Cualquier persona que descubra tu IP puede pedir /minio/login y obtener respuesta (aunque ningún login funcione). Mejor cerrarla.

Edita docker-compose.yml y borra la línea 127.0.0.1:9001:9001. También quita MINIO_OPTS=--console-address :9001. Si por alguna razón necesitas mantener el puerto para debugging interno, déjalo mapeado a 127.0.0.1:9001:9001 (solo loopback) — nunca :9001:9001 accesible desde la red.

cd /opt/minio
sudo docker compose down
sudo docker compose up -d
ss -tlnp | grep -E '9000|9001'

Solo deberías ver el puerto 9000 en escucha.

Conectar restic, rclone y otros clientes

Una vez que MinIO responde por HTTPS, cualquier cliente S3 funciona. Los tres casos más comunes en un homelab:

restic como destino de backup (más detalle en nuestro post sobre restic, post 1803):

export AWS_ACCESS_KEY_ID=backup-user
export AWS_SECRET_ACCESS_KEY='CLAVE_DE_32_CHARS'
export RESTIC_REPOSITORY=s3:s3.minio.tudominio.com/backups
restic init
restic backup /home

La URL sigue el formato s3:/ que entiende restic. El bucket backups ya existe de cuando arrancamos el init container.

rclone para sincronizar archivos grandes:

rclone config create minio s3 
  provider Minio 
  endpoint https://minio.tudominio.com 
  access_key_id backup-user 
  secret_access_key 'CLAVE_DE_32_CHARS'
rclone sync /mnt/fotos minio:media/fotos --progress

aws-cli para operaciones puntuales:

aws --endpoint-url https://minio.tudominio.com s3 ls s3://backups/
aws --endpoint-url https://minio.tudominio.com s3 cp archivo.zip s3://media/

aws-cli no soporta oficialmente MinIO como provider, pero la API S3 es lo bastante estándar para que --endpoint-url funcione con cualquier operación. Si necesitas presigned URLs (URLs temporales para descargar sin clave), MinIO las emite igual que S3: mc share download minio/backups/archivo.zip.

Troubleshooting común

“S3 API Request was not a valid HTTP request” al subir archivos grandes. El proxy está cerrando conexiones antes de que termine la subida. Agrega a tu bloque reverse_proxy en Caddy: flush_interval -1 o sube timeouts en el transporte. Si usas Nginx en lugar de Caddy, revisa proxy_read_timeout y client_max_body_size (debe ser >= el tamaño máximo de objeto que esperas, por defecto MinIO permite hasta 5 TiB por objeto pero Nginx limita a 1m).

“AccessDenied” con clave correcta. El usuario existe pero no tiene política atada, o la política apunta a un bucket que no existe. Verifica con mc admin user info minio backup-user (debe listar readwrite-backups en PolicyName) y con mc ls minio/ que el bucket está creado. Un caso frecuente: creaste el usuario antes de crear el bucket, así que MinIO no tiene a qué atar la política del recurso.

MinIO arranca pero mc admin info dice “0 disks online”. Estás pasando un directorio que MinIO no puede escribir, o el UID del contenedor no coincide con el del host. Verifica sudo ls -la /var/lib/minio/data desde el host. El error exacto en los logs de MinIO te dice qué disco falló: busca líneas con Drive y Read fault o Disk not found.

TLS funciona en el navegador pero mc falla con “x509: certificate signed by unknown authority”. Tu Caddy está usando un certificado interno o auto-firmado. Si es Caddy con Let’s Encrypt público, el certificado debería ser válido. Si estás en una red local sin DNS público, necesitas o (a) un dominio real apuntando al server, o (b) un CA interno y agregar mc con la flag --insecure.

El contenedor se reinicia cada pocos minutos. Revisa docker compose logs minio | grep -i panic. El motivo más común es que el volumen está montado como read-only o que el filesystem del host (XFS, ext4) tiene algún error. Verifica con dmesg | tail si hay I/O errors.

Preguntas frecuentes

¿Por qué MinIO self-hosted en 2026: tu propio S3-compatible storage es relevante para sysadmins Linux?

Forma parte del flujo diario de administración. Los conceptos explicados se aplican directamente a servidores en producción y resuelven problemas reales que enfrentan sysadmins hispanohablantes.

¿Qué necesito para empezar con MinIO self-hosted en 2026: tu?

Distribución Linux actualizada (Debian, Ubuntu, RHEL, Fedora o Arch), acceso root, y conexión a internet. Para producción: entorno de staging separado antes de aplicar cambios.

¿Cuáles son los errores más comunes con MinIO self-hosted en 2026: tu?

Los tres más frecuentes: (1) saltar la lectura de logs, (2) aplicar cambios directos en producción sin probar, (3) olvidar permisos y ownership. La documentación y un plan de rollback siempre.

¿Cuándo usar MinIO self-hosted en 2026: tu y cuándo una alternativa?

Esta solución es ideal para el caso descrito. Alternativas como Docker, Ansible o configuración manual pueden ser mejores según contexto: portabilidad, escala y reproducibilidad son los factores clave.

¿Cómo migro desde una configuración previa a MinIO self-hosted en 2026: tu?

Documentar el estado actual, probar la nueva configuración en staging, validar funcionalmente, y aplicar en producción con rollback plan listo. Los posts de SoyLinux incluyen troubleshooting para los problemas más comunes.

Lee también

Alfonso Moreno | Sysadmin Senior

Soy Alfonso, sysadmin senior con RHCE y más de 10 años administrando sistemas Linux para empresas y proyectos personales. Me especializo en infraestructura, redes, virtualización, monitoring, automation y self-hosting. Sobre SoyLinux.com SoyLinux.com es un blog técnico en español sobre Linux, software libre y self-hosting. Lo mantengo en mi tiempo libre. Áreas de expertise - Linux server administration: RHEL, CentOS, Rocky, Alma, Fedora, Debian, Ubuntu - Network services: Postfix, BIND, Samba, NFS, Squid - Virtualización y containers: KVM, Proxmox, Docker, Podman - Monitoring: Prometheus, Grafana, Zabbix - Automation: Ansible, Bash scripting - Self-hosting: Vaultwarden, Nextcloud, Jellyfin, Immich, Homelab Contacto - Email: [email protected]

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *