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 upgradesemanal (activaunattended-upgradespara parches de seguridad automáticos). Las imágenes Docker se actualizan re-pulleando tags. - Backups: el volumen
pgdatano es backup. Un cron nocturno condocker compose exec db pg_dump app > backup.sqlsincronizado 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.