|
|
|
|
# Con Nginx Proxy Manager delante
|
|
|
|
|
|
|
|
|
|
[Nginx Proxy Manager](https://nginxproxymanager.com/) 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 `npm` es el gestor de paquetes de
|
|
|
|
|
> Node, el de `npm 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 `MX` a `mx1/mx2.titan.email` — son el correo del dominio.
|
|
|
|
|
- El `TXT` de `@` con `v=spf1 include:spf.titan.email ~all` — sin él, el correo
|
|
|
|
|
que se envía desde el dominio acaba en spam.
|
|
|
|
|
- El `TXT` de `titan1._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:
|
|
|
|
|
|
|
|
|
|
```sh
|
|
|
|
|
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:
|
|
|
|
|
|
|
|
|
|
```sh
|
|
|
|
|
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`:
|
|
|
|
|
|
|
|
|
|
```sh
|
|
|
|
|
sudo systemctl restart senzapaura
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
## 5. Y antes que todo lo anterior
|
|
|
|
|
|
|
|
|
|
Que el servicio exista y esté arrancado, que es lo que hoy falta:
|
|
|
|
|
|
|
|
|
|
```sh
|
|
|
|
|
sudo bash /var/www/senzapaura/despliegue/instalar.sh
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
Comprobación desde cualquier máquina de la red, sin pasar por el proxy:
|
|
|
|
|
|
|
|
|
|
```sh
|
|
|
|
|
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:
|
|
|
|
|
|
|
|
|
|
```sh
|
|
|
|
|
journalctl -u senzapaura -n 50 --no-pager
|
|
|
|
|
```
|
|
|
|
|
|
El panel se va a su propio dominio y su propio proceso
Estaba mal hecho y el síntoma era el `if` sobre `/admin` que acabó metido en el
layout de la web pública: la web sabiendo que existe el panel, al revés de como
debe ser.
Ahora son dos aplicaciones del mismo repositorio. Lo que las separa son tres
líneas de `vite.config.ts`: con `APP=panel`, las rutas salen de
`src/panel/rutas`, los hooks de `src/panel/hooks.server.ts` y el resultado va a
`build-panel/`. El servidor público se compila sin las rutas del panel, así que
en `senzapaura.es` no es que `/admin` esté protegido: no existe, y hay una
prueba que lo comprueba pidiéndolo.
Comparten `src/lib` —esquema, consultas, acceso—, que es lo que evita mantener
las migraciones por duplicado. Para que eso funcione, dos cosas que resolvían
rutas del sitio salen de en medio: la navegación, a `$lib/navegacion`, y los
tipos ambientales, que ahora son dos porque el `PageData` del sitio lleva
carrito y favoritos y el del panel no.
El precio de otro dominio es que el panel necesita su propia página de entrar:
la cookie se emite para el host y no para el dominio padre, que es justo lo que
hace que la sesión del panel no viaje en las peticiones de la web. Usa el mismo
código de `$lib/server/auth`, con una diferencia: solo manda código a una cuenta
que ya administre, y contesta lo mismo exista o no. Si dijera «esa cuenta no
administra», el formulario sería una forma cómoda de averiguar cuál sí.
Y con dominio propio, el 404 de ayer pasa a ser un 403 con el motivo escrito:
esconder el panel de quien ya está en `admin.senzapaura.es` no engaña a nadie, y
a esa rama solo llega alguien que administraba y ha dejado de administrar.
Dos servidores en las pruebas, uno por proyecto, con su `baseURL`: una prueba
del panel no puede pedir por accidente una página del sitio.
Aparte, la prueba de contacto otra vez. El arreglo de ayer no valía —comprobar
que el texto está escrito no impide que lo borren un instante después—, así que
el layout marca con `data-hidratado` el momento en que Svelte toma el control y
la prueba espera a eso. Quince pasadas con cuatro trabajadores, sin un fallo.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
4 weeks ago
|
|
|
## 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/`:
|
|
|
|
|
|
|
|
|
|
```sh
|
|
|
|
|
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:
|
|
|
|
|
|
|
|
|
|
1. **DNS**: un registro `A` para `admin`, apuntando a la misma IP.
|
|
|
|
|
2. **Servicio**: una unidad de systemd que arranque `build-panel/index.js` en
|
|
|
|
|
otro puerto —**3001**—, con su propio `.env`.
|
|
|
|
|
3. **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:
|
|
|
|
|
|
|
|
|
|
```sh
|
|
|
|
|
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:
|
|
|
|
|
|
|
|
|
|
```sh
|
|
|
|
|
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.
|