9.3 KiB
Con Nginx Proxy Manager delante
Nginx Proxy Manager es un Nginx con panel web: en vez de escribir archivos de configuración a mano, se dan de alta los dominios desde un formulario y él pide los certificados de Let's Encrypt y los renueva solo. Se suele montar como contenedor y hace de puerta única para todo lo que se publica desde una red.
Ojo con la abreviatura. En este proyecto
npmes el gestor de paquetes de Node, el denpm run dev. No tienen nada que ver, así que aquí el proxy se escribe siempre con el nombre entero.
Este es el montaje cuando quien recibe del exterior no es el Nginx de la propia máquina, sino un Nginx Proxy Manager en otro sitio de la red:
internet → router (80, 443) → Nginx Proxy Manager → 192.168.18.171:3000 → Node
Con Proxy Manager delante, el Nginx de la máquina de la web sobra. Se puede dejar
instalado sin más, pero no tiene que estar en medio: adapter-node ya sirve los
estáticos con sus cabeceras de caché correctas, y meter un segundo proxy solo
añade un salto y complica el recuento de direcciones IP, que sí importa (ver más
abajo).
1. DNS, en Hostinger
Hoy el dominio apunta al alojamiento del registrador. Hay que cambiar dos registros y no tocar los demás:
| Acción | Tipo | Nombre | Contenido |
|---|---|---|---|
| Borrar | ALIAS |
@ |
senzapaura.es.cdn.hstgr.net |
| Crear | A |
@ |
89.46.247.16 |
| Borrar | CNAME |
www |
www.senzapaura.es.cdn.hstgr.net |
| Crear | A |
www |
89.46.247.16 |
Lo que NO se toca, bajo ningún concepto:
- Los dos
MXamx1/mx2.titan.email— son el correo del dominio. - El
TXTde@conv=spf1 include:spf.titan.email ~all— sin él, el correo que se envía desde el dominio acaba en spam. - El
TXTdetitan1._domainkey— la firma DKIM de ese mismo correo.
Borrar cualquiera de esos tres deja el correo del dominio inservible, y el síntoma aparece días después, cuando alguien no recibe algo.
El registro A de ftp (89.116.53.14) es del alojamiento antiguo. No estorba;
se puede borrar cuando se abandone del todo.
Con TTL de 300 segundos el cambio se ve en cinco minutos. Comprobación:
dig +short senzapaura.es @1.1.1.1 # tiene que decir 89.46.247.16
dig +short www.senzapaura.es @1.1.1.1
2. Router
Redirigir al equipo donde corre Proxy Manager:
80 TCP → <IP de Proxy Manager>:80
443 TCP → <IP de Proxy Manager>:443
El 80 hace falta aunque todo vaya por HTTPS: es por donde Let's Encrypt hace su comprobación.
3. El proxy, en Proxy Manager
Hosts → Proxy Hosts → Add Proxy Host
| Campo | Valor |
|---|---|
| Domain Names | senzapaura.es, www.senzapaura.es |
| Scheme | http |
| Forward Hostname / IP | 192.168.18.171 |
| Forward Port | 3000 |
| Cache Assets | apagado |
| Block Common Exploits | encendido |
| Websockets Support | apagado |
«Cache Assets» apagado a propósito: SvelteKit ya manda Cache-Control bien
puesto —eterno para lo que lleva huella en el nombre, corto para lo demás—, y
una caché por encima que no distinga las dos cosas acaba sirviendo una versión
vieja de la web después de un despliegue.
En la pestaña SSL: «Request a new SSL Certificate», con Force SSL y HTTP/2 encendidos.
En Advanced, nada. En particular, no hace falta tocar las cabeceras: Proxy Manager ya
manda X-Forwarded-For, X-Forwarded-Proto y X-Forwarded-Host.
4. El .env del servidor
Dos variables, y las dos importan:
ORIGIN=https://senzapaura.es
ADDRESS_HEADER=X-Forwarded-For
XFF_DEPTH=1
ORIGIN es el error más fácil de cometer y el más desconcertante: sin él,
SvelteKit rechaza todos los formularios con un 403 por comprobación de origen.
La web se ve perfectamente y nada funciona.
ADDRESS_HEADER y XFF_DEPTH son menos evidentes y hacen falta igual. Sin
ellos, getClientAddress() devuelve la IP del proxy, no la de quien pide. Y esa
función es la que usa el limitador del formulario de acceso: con todas las
peticiones pareciendo venir de la misma dirección, el límite salta con una
sola persona insistiendo y nadie más puede pedir su código.
XFF_DEPTH=1 porque hay un proxy entre internet y Node. Si algún día se
mete otro delante —un Cloudflare, por ejemplo—, hay que subirlo a 2: la cuenta
es desde el final de la cadena hacia atrás, y equivocarla deja la IP a merced de
quien mande su propia cabecera.
Después de tocar el .env:
sudo systemctl restart senzapaura
5. Y antes que todo lo anterior
Que el servicio exista y esté arrancado, que es lo que hoy falta:
sudo bash /var/www/senzapaura/despliegue/instalar.sh
Comprobación desde cualquier máquina de la red, sin pasar por el proxy:
curl -I http://192.168.18.171:3000/
Si eso no contesta 200, el problema está en el servidor y no en el DNS ni en Proxy Manager, y es ahí donde hay que mirar:
journalctl -u senzapaura -n 50 --no-pager
El panel, en admin.senzapaura.es
El panel de administración es otra aplicación y otro proceso, del mismo
repositorio. No cuelga de la web: el servidor público se compila sin sus rutas,
así que en senzapaura.es no existe ninguna dirección que lleve a él.
Se compila aparte y escribe en build-panel/:
npm run build # la web → build/
npm run build:panel # el panel → build-panel/
Lo que hay que añadir es lo mismo que para la web, una vez más:
- DNS: un registro
Aparaadmin, apuntando a la misma IP. - Servicio: una unidad de systemd que arranque
build-panel/index.jsen otro puerto —3001—, con su propio.env. - Proxy Host:
admin.senzapaura.es→192.168.18.171:3001, con su certificado, «Force SSL» y «HTTP/2», igual que el de la web.
El .env del panel lleva ORIGIN=https://admin.senzapaura.es. No es un
detalle: con el ORIGIN de la web, SvelteKit rechaza todos los formularios
del panel con un 403 y no se puede ni entrar.
PROXIMAMENTE no se pone aquí. El portón es de la web pública, y encendido
en el panel dejaría fuera al administrador justo mientras la web no está
abierta, que es cuando más falta hace entrar.
Por qué otro dominio y no /admin
Porque así el servidor que da la cara a internet no lleva dentro el código del
panel. No es una puerta cerrada: es que ahí no hay puerta. Y de paso, la cookie
de sesión del panel es de admin.senzapaura.es y solo de ahí —no se emite para
el dominio padre—, así que no viaja en ninguna petición de la web pública.
El precio es que el panel tiene su propia página de entrar: una sesión de
senzapaura.es no vale aquí. Usa el mismo código de acceso por correo, con una
diferencia: solo manda código a una cuenta que ya administre, y contesta lo
mismo exista o no, para que el formulario no sirva para averiguar qué correo
tiene las llaves.
El portón de «muy pronto»
Mientras la web no esté abierta al público, en el .env del servidor:
PROXIMAMENTE=1
PROXIMAMENTE_PERMITIDAS=89.46.247.16
Esa dirección es la IP pública fija de la línea —contratada como fija, no depende de renovaciones—, así que la lista no hay que revisarla.
Y ahí está lo que despista: hace falta ponerla aunque estés en casa.
La regla «la red local entra siempre» no sirve para esto. Cuando desde la propia
red se pide senzapaura.es, el nombre resuelve a la IP pública, la petición
sale al router y vuelve, y el router la reescribe: al proxy le llega como
89.46.247.16, no como 192.168.18.x. Es NAT de retorno, y lo hacen casi todos
los routers domésticos. Sin la IP pública en la lista, verías tu propia página
de espera en tu propio ordenador y no habría forma de entender por qué.
Se comprueba en el registro del proxy:
tail -3 /data/logs/proxy-host-5_access.log # [Client 89.46.247.16]
De paso, algo que conviene saber que funciona: mandar a mano un
X-Forwarded-For: 8.8.8.8 no abre el portón. El proxy añade la dirección
real al final de la cabecera y XFF_DEPTH=1 es la que se lee. Por eso el número
tiene que ser exacto: si algún día se mete otro proxy delante y no se sube a 2,
la dirección pasa a ser la que mande quien pide.
Si el operador bloquea el 80 y el 443
No es el caso aquí: la línea tiene IP pública fija contratada y los puertos 80 y 443 llegan. Queda escrito por si algún día se mueve el montaje a otra conexión, porque el síntoma es de los que se confunden con un fallo de configuración: el DNS resuelve bien, desde dentro de casa se ve, y desde fuera —desde los datos del móvil, por ejemplo— no responde nada.
Si eso pasara, hay dos salidas: pedir al operador la IP pública sin filtrar, o montar un túnel —Cloudflare Tunnel es lo más directo— que sale desde la red hacia fuera y no necesita ningún puerto abierto.