Recientemente me enfrenté a un problema interesante al desplegar una aplicación Spring Boot en un servidor de Hetzner Cloud. Decidà probar algo que me parecÃa curioso: configurarlo solo con IPv6, sin solicitar la IPv4 adicional.
¿Por qué? Porque me resultó llamativo que Hetzner cobre €0.50/mes extra por cada dirección IPv4 pública, mientras que otros proveedores como Akamai (Linode) y varios competidores ya lo ofrecen de forma gratuita en la mayorÃa de sus planes. Me dio curiosidad ver hasta dónde se podÃa llegar hoy en dÃa (enero 2026) desplegando realmente solo con IPv6 en un entorno de producción, y qué dolores de cabeza reales encontrarÃa.
El problema inicial
Al hacer un health check a la aplicación, aparecÃa este error:
org.eclipse.angus.mail.util.MailConnectException: Couldn't connect to host, port: email-smtp.us-west-2.amazonaws.com, 587; timeout -1
La aplicación Spring Boot no podÃa conectarse a AWS SES para enviar emails. La configuración era la estándar:
spring.mail.host=email-smtp.us-west-2.amazonaws.com
spring.mail.port=587
spring.mail.username=${AWS_SES_USERNAME}
spring.mail.password=${AWS_SES_PASSWORD}
spring.mail.properties.mail.smtp.auth=true
spring.mail.properties.mail.smtp.starttls.enable=true
Primer diagnóstico: ¿Hetzner bloqueando puertos SMTP?
telnet email-smtp.us-west-2.amazonaws.com 587
Resultado:
Connection failed: Network is unreachable
Hetzner bloquea puertos SMTP (25, 587, 465) por defecto, pero el error “Network is unreachable” ya apuntaba a algo más profundo.
La verdadera causa: IPv6-only
curl -4 ifconfig.me # Error: no responde
ip addr show
Salida (solo IPv6):
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 state UP
inet6 2a01:4ff:f0:e064::1/64 scope global
Y al resolver el dominio de SES:
nslookup email-smtp.us-west-2.amazonaws.com
Solo IPv4:
Name: email-smtp.us-west-2.amazonaws.com
Address: 54.185.234.152
Address: 52.13.3.84
Address: 44.225.148.59
Conclusión: servidor solo IPv6 + servicio solo IPv4 = imposible conectar.
Solución principal: Forzar preferencia IPv6 en Java
El endpoint clásico de AWS SES sà tiene soporte dual-stack (IPv4 + IPv6), pero Java por defecto prefiere IPv4.
Solución definitiva:
java -Djava.net.preferIPv6Addresses=true -jar tu-aplicacion.jar
Comando completo usado:
java -Xmx1g -Xms512m -XX:MaxDirectMemorySize=512m \
-Djava.net.preferIPv6Addresses=true \
-Dserver.port=8080 \
-jar app.jar
Problema secundario: localhost y MariaDB
Error:
Socket fail to connect to localhost. Connection refused
Causa: localhost → ::1 (IPv6), pero MariaDB escucha solo en IPv4.
Solución simple y efectiva:
# Antes
spring.datasource.url=jdbc:mariadb://localhost:3306/mi_base
# Después (recomendado en IPv6-only)
spring.datasource.url=jdbc:mariadb://127.0.0.1:3306/mi_base
Zero Downtime Deployment (Blue-Green simple con puertos)
Estructura:
Nginx (443) → Spring Boot (8080 o 8081)
Script de deployment (restart_api.sh):
#!/bin/bash
set -e
APP_DIR="/home/user/app"
JAR_PATH="$APP_DIR/app-*.jar"
STATE_FILE="$APP_DIR/.current_port"
MAX_HEALTH_RETRIES=30
# Puerto actual
[ -f "$STATE_FILE" ] && CURRENT_PORT=$(cat "$STATE_FILE") || CURRENT_PORT=8080
# Alternar
[ "$CURRENT_PORT" = "8080" ] && NEW_PORT=8081 OLD_PORT=8080 || NEW_PORT=8080 OLD_PORT=8081
echo "Deployment: $OLD_PORT → $NEW_PORT"
# Nueva instancia
screen -S "app-$NEW_PORT" -d -m bash -c \
"java -Xmx1g -Djava.net.preferIPv6Addresses=true -Dserver.port=$NEW_PORT -jar $JAR_PATH"
# Health check
for i in $(seq 1 $MAX_HEALTH_RETRIES); do
if curl -f -s "http://localhost:$NEW_PORT/actuator/health" | grep -q '"status":"UP"'; then
echo "New instance healthy"
break
fi
[ $i -eq $MAX_HEALTH_RETRIES ] && { echo "Health check failed"; screen -S "app-$NEW_PORT" -X quit; exit 1; }
sleep 2
done
# Actualizar nginx
sudo sed -i "s|proxy_pass http://localhost:[0-9]*;|proxy_pass http://localhost:$NEW_PORT;|" \
/etc/nginx/sites-available/api.dominio.com
sudo nginx -t && sudo systemctl reload nginx
sleep 5
# Matar vieja
screen -S "app-$OLD_PORT" -X quit 2>/dev/null || true
echo "$NEW_PORT" > "$STATE_FILE"
echo "Deployment completado: puerto activo $NEW_PORT"
Configuración Nginx (dual stack)
server {
server_name api.dominio.com;
listen [::]:443 ssl ipv6only=on;
listen 443 ssl;
ssl_certificate /etc/letsencrypt/live/api.dominio.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/api.dominio.com/privkey.pem;
location / {
proxy_pass http://localhost:8080;
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;
}
}
Resultados del experimento
- ✅ Aplicación 100% funcional en IPv6-only
- ✅ Emails vÃa AWS SES sin problemas
- ✅ MariaDB estable
- ✅ Deployments zero-downtime sencillos
Conclusión
En 2026, desplegar en IPv6-only en Hetzner es totalmente viable y bastante maduro, aunque requiere ajustes puntuales (sobre todo Java + preferencia de direcciones). El mayor “dolor” no fue la falta de IPv6 en servicios, sino el comportamiento por defecto de Java.
¿Vale la pena por ahorrar 0,50€? Probablemente no. ¿Vale la pena como experimento y para estar preparado para el futuro? Totalmente sÃ.
¿Y tú? ¿Ya tienes algún servicio corriendo puro IPv6? 😄

