self-hostingvpsdockernginx

Self-hosting desde cero: tu primer servidor con SSH, Docker y Nginx

Guía completa para montar tu primer servidor propio: asegurar SSH con claves Ed25519, desplegar con Docker Compose y publicar con Nginx y HTTPS.

23 de agosto de 2026·9 min de lectura

Alquilar un VPS por 5 euros al mes y ejecutar en él tus propios servicios es una de las habilidades más formativas del oficio: aprendes redes, Linux, seguridad y operaciones a la vez. Esta guía recorre el camino completo —de servidor recién estrenado a aplicación publicada con HTTPS— usando exactamente las piezas que usarías en producción.

Paso 0: elegir el proveedor y crear la máquina

Hetzner, OVH, DigitalOcean, Vultr... cualquier proveedor serio sirve. Lo que importa en el momento de crear la instancia:

  • Ubuntu LTS o Debian: máxima documentación, actualizaciones largas.
  • La región más cercana a tus usuarios: la latencia se nota más que el precio.
  • Clave SSH desde el inicio: si el panel lo permite, inyecta tu clave pública durante la creación y evita contraseñas desde el primer segundo.

Si aún no tienes par de claves, genera una Ed25519 (explicamos el proceso completo en la guía de claves SSH):

ssh-keygen -t ed25519 -C "miguel@portatil"

Paso 1: primeros minutos críticos

Un VPS recién nacido recibe escaneos automatizados en minutos. Antes de instalar nada, cierra la puerta principal:

ssh root@TU_IP

adduser miguel
usermod -aG sudo miguel

Ahora edita la configuración de SSH (/etc/ssh/sshd_config) con tres cambios:

PasswordAuthentication no
PermitRootLogin no
PubkeyAuthentication yes

Reinicia el servicio (sudo systemctl restart ssh) sin cerrar tu sesión actual y abre una segunda terminal para verificar que puedes entrar con clave antes de perder la primera conexión. Este orden te salva de dejarte fuera.

El firewall llega después, con ufw:

sudo ufw allow OpenSSH
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw enable

Todo lo demás queda bloqueado: solo SSH, HTTP y HTTPS responden desde fuera.

Paso 2: instalar Docker y Compose

Docker convierte cada servicio en algo reproducible y aislable. La instalación oficial en dos comandos:

curl -fsSL https://get.docker.com | sh
sudo usermod -aG docker miguel

(Cierra sesión y vuelve a entrar para que el grupo docker aplique.)

A partir de aquí, cada aplicación es un directorio con su docker-compose.yml. Un stack típico app + base de datos:

services:
  app:
    build: .
    restart: unless-stopped
    environment:
      - DATABASE_URL=postgres://app:cambiar@db:5432/app
    depends_on:
      - db
  db:
    image: postgres:16-alpine
    volumes:
      - pgdata:/var/lib/postgresql/data
    environment:
      - POSTGRES_USER=app
      - POSTGRES_PASSWORD=cambiar
      - POSTGRES_DB=app
    restart: unless-stopped

volumes:
  pgdata:

Fíjate en lo que NO hay: publicación de puertos. La app no necesita ports: porque Nginx hablará con ella a través de la red interna de Docker. Menos superficie expuesta, cero riesgo de colisiones de puertos entre servicios. El patrón completo está desarrollado en la guía de Docker Compose.

Para construir la imagen de tu aplicación con caché de capas y multistage, el Dockerfile paso a paso cubre Node, Next.js, Python, Go y Rust.

Paso 3: Nginx como puerta de entrada

Con la app corriendo dentro de Docker, Nginx en el host hace de único punto de entrada: termina TLS, comprime, aplica cabeceras de seguridad y enruta dominios.

server {
    listen 80;
    server_name midominio.es;
    return 301 https://$host$request_uri;
}

server {
    listen 443 ssl;
    http2 on;
    server_name midominio.es;

    ssl_certificate /etc/letsencrypt/live/midominio.es/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/midominio.es/privkey.pem;

    add_header X-Frame-Options "DENY" always;
    add_header X-Content-Type-Options "nosniff" always;

    location / {
        proxy_pass http://127.0.0.1:3000;
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

Espera —si dijimos que la app no publica puertos, ¿cómo la alcanza 127.0.0.1:3000? Dos opciones válidas: publicar el puerto solo en loopback ("127.0.0.1:3000:3000" en el compose) o unir Nginx a la red Docker del proyecto y apuntar al nombre del servicio. Ambas funcionan; la segunda es más limpia, la primera más simple. Los tres patrones completos de configuración (estático, SPA, proxy) están en la guía de Nginx.

Los certificados, gratis y automáticos:

sudo apt install certbot python3-certbot-nginx
sudo certbot --nginx -d midominio.es

Certbot reescribe tu configuración añadiendo el bloque SSL y programa la renovación sola.

Paso 4: despliegues sin dolor

Con la base montada, actualizar tu aplicación debe ser un comando:

git pull && docker compose up -d --build

Cuando el proyecto crece, ese comando migra a GitHub Actions: push a main → CI corre tests → deploy automático. El workflow de GitHub Actions listo para copiar hace exactamente eso.

Mantenimiento mínimo viable

Tres rutinas mantienen un servidor pequeño sano:

  • Actualizaciones: sudo apt update && sudo apt upgrade semanal (activa unattended-upgrades para parches de seguridad automáticos). Las imágenes Docker se actualizan re-pulleando tags.
  • Backups: el volumen pgdata no es backup. Un cron nocturno con docker compose exec db pg_dump app > backup.sql sincronizado fuera del servidor sí lo es.
  • Vigilancia básica: docker compose logs -f, espacio en disco (df -h) y los intentos fallidos de SSH (journalctl -u ssh) te cuentan todo lo que necesitas saber en servidores pequeños. Fail2ban opcionalmente automatiza el baneo de fuerza bruta.

Errores de novato (que todos cometemos)

Abrir el puerto 3000 de la app al mundo en vez de proxyficarla — duplicas superficies de ataque. Dejar PasswordAuthentication yes tras crear usuarios. No probar la nueva sesión SSH antes de cerrar la vieja. Olvidar que docker compose down -v borra los volúmenes —y con ellos la base de datos—. Cada uno se arregla en un minuto si lo detectas temprano; ninguno tiene gracia a las 3 AM.

Genera las piezas antes de tocar el servidor

Prepara tus claves con el generador de claves SSH, valida el estado TLS resultante con el comprobador SSL, y monta el compose de tu stack con el generador de Docker Compose.

Preguntas frecuentes

¿Cuánto VPS necesito para empezar? Para 2-3 servicios ligeros, 2 GB de RAM y 1 vCPU sobran. Docker permite crecer verticalmente cambiando de plan sin reconstruir nada.

¿IP dinámica del VPS? Los VPS traen IP fija; apunta tu dominio con un registro A y listo. Solo los servicios domésticos detrás de IP dinámica requieren DDNS.

¿Kubernetes ya? No. Compose escala hasta niveles que sorprenden, y Kubernetes resuelve problemas (multi-nodo, autoescalado, rollouts complejos) que un primer servidor ni tiene. Llegará si algún día hagan falta.


Empieza generando tu par de claves con el generador de claves SSH, directamente en tu navegador.

Pruébalo sin código

Generador de Claves SSH

Ed25519/RSA en tu navegador.

Abrir Generador de Claves SSH

Hecho por

Miguel Ángel Colorado Marin (MACM)

Full-Stack Developer · Guadalajara, España

Desarrollo aplicaciones web, herramientas digitales y proyectos completos — desde el diseño hasta el despliegue.

Contáctame