Cloudflare es un proxy inverso con CDN que se coloca entre los visitantes y tu servidor: sirve el contenido estático desde su red, filtra ataques y oculta la IP de origen. En este tutorial pondrás detrás de Cloudflare un sitio servido por Nginx en Ubuntu 24.04, con cifrado de extremo a extremo en modo Full (strict), la IP real del visitante en los logs, una regla de caché, una regla de WAF y el firewall del servidor abierto solo a Cloudflare. Todo funciona con el plan gratuito.
Requisitos previos
Para seguir esta guía necesitas:
- Un VPS con Ubuntu 24.04 LTS, por ejemplo un VPS de CubePath, con un usuario no root con privilegios
sudo. - Nginx instalado y sirviendo tu sitio en el puerto 80 con un bloque
serverparayour_domainywww.your_domain. - UFW activo con SSH permitido (
sudo ufw allow OpenSSH). - Un dominio propio (
your_domain) y acceso al panel de su registrador para cambiar los servidores de nombres. - Una cuenta gratuita de Cloudflare.
En los ejemplos, your_server_ip es la IP pública del VPS.
Paso 1: Añadir el dominio a Cloudflare
En el panel de Cloudflare, añade tu dominio con la opción de incorporar un dominio (Add a domain), elige el plan Free y deja que Cloudflare importe los registros DNS existentes. Revisa la lista importada en DNS > Records y asegúrate de que existen estos registros:
| Tipo | Nombre | Contenido | Estado del proxy |
|---|---|---|---|
| A | @ | your_server_ip | Solo DNS (nube gris), de momento |
| CNAME | www | your_domain | Solo DNS (nube gris), de momento |
| MX | @ | tu servidor de correo | Siempre solo DNS |
Deja el proxy desactivado hasta tener el certificado en el paso 2. Los registros de correo (MX y el A o CNAME al que apuntan) nunca deben ir por el proxy, porque Cloudflare solo reenvía tráfico HTTP y HTTPS.
Cloudflare te asignará dos servidores de nombres, del tipo xxx.ns.cloudflare.com. Sustituye los actuales por esos dos en el panel de tu registrador. Cuando el cambio se haya propagado, Cloudflare mostrará el dominio como activo. Puedes comprobarlo desde el servidor:
dig +short NS your_domain
ava.ns.cloudflare.com.
cole.ns.cloudflare.com.
La propagación suele tardar menos de una hora, aunque puede llegar a 24 horas.
Paso 2: Obtener un certificado válido en el origen
El modo SSL recomendado, Full (strict), exige que el servidor tenga un certificado válido para el dominio. Obtén uno de Let's Encrypt mientras los registros siguen en modo solo DNS, para que la validación HTTP llegue directamente a Nginx:
sudo apt update
sudo apt install certbot python3-certbot-nginx
sudo ufw allow 'Nginx Full'
sudo certbot --nginx -d your_domain -d www.your_domain
Certbot modifica el bloque server de Nginx, añade la escucha en el puerto 443 y configura la renovación automática con un timer de systemd. Comprueba que la renovación funciona:
sudo certbot renew --dry-run
Congratulations, all simulated renewals succeeded:
/etc/letsencrypt/live/your_domain/fullchain.pem (success)
Las renovaciones posteriores seguirán funcionando con el proxy activo, porque Cloudflare reenvía las peticiones a /.well-known/acme-challenge/ al origen.
Paso 3: Activar el proxy y el modo Full (strict)
Vuelve a DNS > Records y activa el proxy (nube naranja) en los registros @ y www. Después, en SSL/TLS > Overview, configura el modo de cifrado en Full (strict). Los modos disponibles son:
| Modo | Visitante a Cloudflare | Cloudflare a origen | Uso |
|---|---|---|---|
| Flexible | HTTPS | HTTP sin cifrar | No recomendado; causa bucles de redirección |
| Full | HTTPS | HTTPS sin validar el certificado | Solo con certificado autofirmado |
| Full (strict) | HTTPS | HTTPS con certificado válido | Recomendado |
En SSL/TLS > Edge Certificates, activa Always Use HTTPS para que Cloudflare redirija HTTP a HTTPS en su red.
Comprueba que el dominio ya resuelve a IPs de Cloudflare y que las respuestas pasan por su proxy:
dig +short your_domain
curl -sI https://your_domain | grep -iE '^(server|cf-ray|cf-cache-status)'
104.21.48.77
172.67.155.12
server: cloudflare
cf-ray: 8c1f2a3b4d5e6f70-MAD
cf-cache-status: DYNAMIC
DYNAMIC significa que la respuesta (HTML) no se ha cacheado, que es el comportamiento por defecto.
Paso 4: Registrar la IP real del visitante en Nginx
Con el proxy activo, Nginx ve la IP de Cloudflare en cada conexión. Cloudflare envía la IP original en la cabecera CF-Connecting-IP, y el módulo realip de Nginx puede usarla siempre que la petición venga de un rango de Cloudflare. Genera el archivo de configuración a partir de las listas oficiales de rangos:
{
for ip in $(curl -fsS https://www.cloudflare.com/ips-v4) $(curl -fsS https://www.cloudflare.com/ips-v6); do
echo "set_real_ip_from $ip;"
done
echo "real_ip_header CF-Connecting-IP;"
} | sudo tee /etc/nginx/conf.d/cloudflare-realip.conf
set_real_ip_from 173.245.48.0/20;
set_real_ip_from 103.21.244.0/22;
...
set_real_ip_from 2c0f:f248::/32;
real_ip_header CF-Connecting-IP;
Ubuntu incluye /etc/nginx/conf.d/*.conf dentro del bloque http, así que la configuración se aplica a todos los sitios. Valida y recarga Nginx:
sudo nginx -t
sudo systemctl reload nginx
Visita el sitio desde tu navegador y revisa el log de acceso:
sudo tail -n 3 /var/log/nginx/access.log
La primera columna debe mostrar tu IP pública, no una IP de Cloudflare como 172.68.x.x o 162.158.x.x.
Paso 5: Cachear los recursos estáticos
Por defecto Cloudflare cachea por extensión (imágenes, CSS, JavaScript, fuentes) y nunca cachea HTML. Para contenido estático con nombres versionados puedes alargar el tiempo en la red de Cloudflare con una regla de caché. En la sección Cache Rules del panel, crea una regla:
- Nombre:
estaticos. - Condición: campo URI Path, operador starts with, valor
/static/. - Cache eligibility: Eligible for cache.
- Edge TTL: ignorar la cabecera de caché del origen y usar 1 mes.
Crea una segunda regla con Bypass cache para las rutas que nunca deben cachearse, como /wp-admin/ o /api/. Comprueba el resultado pidiendo dos veces el mismo recurso:
curl -sI https://your_domain/static/app.css | grep -i cf-cache-status
curl -sI https://your_domain/static/app.css | grep -i cf-cache-status
cf-cache-status: MISS
cf-cache-status: HIT
Tras desplegar cambios, purga la caché desde Caching > Configuration > Purge Cache, o por API con un token que tenga el permiso Zone > Cache Purge. El ID de la zona aparece en la página Overview del dominio:
curl -X POST "https://api.cloudflare.com/client/v4/zones/your_zone_id/purge_cache" \
-H "Authorization: Bearer your_api_token" \
-H "Content-Type: application/json" \
--data '{"files":["https://your_domain/static/app.css"]}'
La respuesta incluye "success": true. Usa {"purge_everything":true} como cuerpo para vaciar toda la zona.
Paso 6: Proteger el sitio con el WAF
En Security > WAF > Custom rules puedes bloquear tráfico antes de que llegue al VPS; el plan gratuito permite cinco reglas personalizadas. Por ejemplo, para permitir el acceso a /wp-admin solo desde la IP de tu oficina, crea una regla con acción Block y esta expresión (editor de expresiones):
(starts_with(http.request.uri.path, "/wp-admin") and not ip.src in {203.0.113.10})
Sustituye 203.0.113.10 por tu IP. Otras expresiones útiles son (ip.src.country in {"XX" "YY"}) para bloquear países o (http.user_agent contains "sqlmap") para herramientas de escaneo conocidas.
En la sección de bots activa Bot Fight Mode, incluido en el plan gratuito. Si sufres un ataque de capa 7 en curso, activa I'm Under Attack Mode desde la página de resumen del dominio: Cloudflare mostrará un desafío a cada visitante hasta que lo desactives.
Comprueba la regla del WAF desde una IP distinta a la permitida:
curl -s -o /dev/null -w '%{http_code}\n' https://your_domain/wp-admin/
403
El evento aparece en Security > Events con el nombre de la regla.
Paso 7: Aceptar solo tráfico web de Cloudflare
Aunque el DNS apunte a Cloudflare, cualquiera que conozca your_server_ip puede atacar el servidor directamente y saltarse el WAF. Permite los puertos 80 y 443 solo desde los rangos de Cloudflare:
for ip in $(curl -fsS https://www.cloudflare.com/ips-v4) $(curl -fsS https://www.cloudflare.com/ips-v6); do
sudo ufw allow proto tcp from "$ip" to any port 80,443 comment 'Cloudflare'
done
Elimina después la regla que abría los puertos web a todo el mundo y revisa el resultado:
sudo ufw delete allow 'Nginx Full'
sudo ufw status numbered
Debes ver OpenSSH permitido desde cualquier origen y los puertos 80,443/tcp solo desde los rangos de Cloudflare. Comprueba que el origen ya no responde de forma directa desde otra máquina:
curl -m 5 -sI http://your_server_ip
El comando debe agotar el tiempo de espera, mientras que https://your_domain sigue funcionando. Cloudflare cambia sus rangos con muy poca frecuencia; si publica rangos nuevos, repite este paso y el paso 4.
Solución de problemas
Error 521 (Web server is down): Cloudflare no consigue conectar con el origen. Comprueba sudo systemctl status nginx y que UFW permite los rangos de Cloudflare en los puertos 80 y 443.
Error 522 (Connection timed out): la conexión se abre pero no se completa, normalmente por un firewall externo o porque el registro A apunta a una IP equivocada. Revisa el registro en DNS > Records.
Error 526 (Invalid SSL certificate): el modo es Full (strict) y el certificado del origen está caducado o no cubre el nombre. Compruébalo en el servidor con sudo certbot certificates.
ERR_TOO_MANY_REDIRECTS: el modo SSL está en Flexible y Nginx redirige a HTTPS. Cloudflare pide la página por HTTP, Nginx responde con una redirección a HTTPS, y el ciclo se repite. Cambia el modo a Full (strict).
Los cambios no se ven: el recurso está en la caché de Cloudflare o del navegador. Purga la URL como en el paso 5.
Conclusión
Tu sitio se sirve ahora a través de Cloudflare con HTTPS verificado en los dos tramos, los estáticos se cachean en su red, el WAF filtra el tráfico no deseado y el VPS solo acepta conexiones web de Cloudflare. Como siguientes pasos puedes activar Authenticated Origin Pulls para que Nginx exija un certificado de cliente de Cloudflare, sustituir el certificado de Let's Encrypt por un certificado Cloudflare Origin CA de larga duración, o exponer servicios internos sin abrir puertos con Cloudflare Tunnel.
