LECCIÓN · INFRAESTRUCTURA

Despliegue con HTTPS real en red local

La cadena completa: Hyper-V, Ubuntu, MikroTik, Cloudflare, Caddy y PM2, sin exponer el servidor a internet.


MÓDULO 01 · 471 pal · 2 min

Panorama: por qué existe cada pieza

La restricción del contexto seguro, las seis capas del montaje y el mapa completo de la cadena — de un teléfono en la red local a un candado auténtico.

Por qué importa

Toda esta arquitectura nace de una sola regla de los navegadores: las capacidades avanzadas de una aplicación web progresiva (Progressive Web App) — el service worker que permite trabajar sin conexión, la instalación en la pantalla de inicio, la cámara — solo se activan en un contexto seguro: HTTPS con certificado válido. La única excepción es localhost, que solo sirve para desarrollar en la propia máquina.

La restricción raíz

En una red local, los teléfonos alcanzan al servidor por una dirección privada del tipo http://10.10.2.245:3000. Ese origen no es un contexto seguro: el navegador carga la página, pero se niega a registrar el service worker. La aplicación «funciona» mientras hay red — y pierde exactamente la mitad que importa, el modo sin conexión. Todo el montaje existe para convertir esa dirección privada en https://asecna.ababcd.com con un candado auténtico, sin exigir que el servidor sea accesible desde internet.

Las seis capas y su papel

CapaPiezaProblema que resuelve
NombreCloudflareUn dominio real cuyo registro apunta a la IP privada, y una API para demostrar la propiedad del dominio sin exponer nada
CertificadoCaddy + Let's EncryptObtiene y renueva solo el certificado (desafío DNS-01) y entrega el tráfico a la aplicación
Puerta de la redMikroTikAplica el portal cautivo a las personas y exime al servidor, que no puede «iniciar sesión» en un portal
VirtualizaciónHyper-VHace de la máquina virtual un equipo más de la red (conmutador externo) con identidad estable (MAC estática)
Sistema invitadoUbuntu + netplanFija la IP con una sola fuente de verdad, para que el nombre nunca apunte al vacío
AplicaciónPM2Mantiene el proceso vivo, lo resucita tras un corte de luz y lo esconde tras el proxy (solo loopback)
INTERNET (SOLO SALIDA)CADDY :443 TELEFONOMIKROTIKAPP (PM2) dns cloudflare · api · let's encrypt pwa instaladahotspot 10.10.0.1 tls · renueva solo80 → 443 127.0.0.1:3000solo loopback VM UBUNTU · 10.10.2.245 · MAC ESTATICA red local 10.10.0.0/16 — el trafico de uso nunca sale de aqui renovacion dns-01 (saliente)
la cadena completa: el uso viaja por la red local; internet solo se toca hacia fuera, para resolver el nombre y renovar el certificado
La idea central

El candado no exige exponer el servidor. La validez del certificado se demuestra tocando el DNS del dominio — algo que solo su dueño puede hacer —, no recibiendo visitas de internet. Por eso una IP privada, invisible desde fuera, puede llevar HTTPS de primera clase.

Punto de control
  • Sé qué exige un contexto seguro y qué capacidades se pierden sin él.
  • Puedo nombrar las seis capas del montaje y el problema que resuelve cada una.
  • Entiendo que el tráfico de uso es local y que internet solo se usa hacia fuera.

MÓDULO 02 · 446 pal · 2 min

La capa de virtualización: Hyper-V

El conmutador externo que pone a la máquina virtual dentro de la red real, y la MAC estática que le da una identidad que no muta.

Por qué importa

El servidor vive dentro de una máquina virtual. Para que un teléfono de la red pueda alcanzarlo, la máquina virtual tiene que ser un vecino más de la red local — con su propia dirección en 10.10.0.0/16 y una identidad de red que el enrutador pueda reconocer siempre. Hyper-V, tal como viene de fábrica, no garantiza ninguna de las dos cosas.

Conmutador externo, no el conmutador por defecto

El Default Switch de Hyper-V hace traducción de direcciones (NAT): la máquina virtual recibe una dirección interna del tipo 172.x que solo el anfitrión Windows conoce. Los teléfonos jamás podrían alcanzarla y el enrutador ni sabría que existe. La solución es un conmutador virtual externo: un puente de capa 2 que conecta la tarjeta virtual de la máquina, a efectos prácticos, al mismo cable que la tarjeta física del anfitrión.

# Hyper-V Manager (interfaz gráfica): Virtual Switch Manager New External vincular a la tarjeta física (la que va al enrutador) marcar "Allow management operating system to share this network adapter" # aviso: al crearlo, el anfitrión pierde la red unos segundos (normal) VM Settings Network Adapter Virtual switch: el externo
DEFAULT SWITCH (NAT) CONMUTADOR EXTERNO VM172.x.x.xHOST WINDOWS VM10.10.2.245NIC FISICAMIKROTIK10.10.0.1 la red local no ve a la VMlos telefonos no llegan la VM es un equipo mas de 10.10.0.0/16alcanzable desde cualquier telefono
los dos conmutadores de hyper-v: solo el externo pone a la máquina virtual dentro de la red que importa

MAC estática: la identidad que no muta

Hyper-V asigna por defecto direcciones MAC dinámicas: pueden cambiar entre reinicios o al mover la máquina. Todo lo que el enrutador sabe del servidor — el lease reservado, la exención del portal — cuelga de esa MAC; si muta, esas reglas dejan de aplicarle en silencio. Con la máquina apagada:

VM Settings Network Adapter Advanced Features MAC address: Static # aquí: 00:15:5d:00:09:0b
Trampa: el prefijo no prueba nada

El prefijo 00:15:5d aparece igual en las MAC dinámicas y en las estáticas de Hyper-V: verlo no demuestra que la dirección sea fija. La única prueba es la casilla Static marcada en Advanced Features. Una suposición aquí se paga meses después, con un servidor que «desaparece» de la red tras un reinicio — el fallo lejos de su causa, la clase de avería más cara de diagnosticar.

Punto de control
  • Distingo el conmutador por defecto (NAT, 172.x) del externo (puente a la red real).
  • Sé crear el conmutador externo y mover a él el adaptador de la máquina.
  • Entiendo por qué la MAC debe fijarse como estática y dónde se verifica.

MÓDULO 03 · 417 pal · 2 min

La red del sistema invitado: Ubuntu y netplan

La IP fija con una sola fuente de verdad — y el incidente real de la dirección duplicada que enseña por qué «una sola» no es adorno.

Por qué importa

El nombre asecna.ababcd.com apuntará para siempre a 10.10.2.245. Esa correspondencia es una foto fija: si la dirección del servidor cambiara, el nombre señalaría al vacío. La dirección se fija dentro del sistema con netplan, el gestor de red declarativo de Ubuntu Server — y con un solo fichero al mando.

La configuración estática

# /etc/netplan/01-netcfg.yaml network: version: 2 ethernets: eth0: dhcp4: false addresses: - 10.10.2.245/16 gateway4: 10.10.0.1 # equivalente moderno: routes: [{to: default, via: 10.10.0.1}] nameservers: addresses: [8.8.8.8, 1.1.1.1]
sudo netplan apply ip -4 addr show eth0 # → una sola dirección: 10.10.2.245/16 ip route | head -1 # → default via 10.10.0.1

Tres decisiones del fichero merecen palabra. La máscara /16 debe coincidir con la de la red (10.10.0.0/16): una máscara equivocada deja al servidor «viendo» solo una parte de sus vecinos, con síntomas intermitentes. La puerta 10.10.0.1 es el MikroTik — el único camino de salida para las renovaciones del certificado. Los servidores de nombres públicos hacen que el servidor resuelva directamente contra internet, sin depender del DNS del enrutador.

El incidente de la IP duplicada

Lo que pasó

Durante la puesta en marcha real, la máquina llegó a presentar dos direcciones a la vez10.10.2.245 y una segunda 10.11.0.7. Convivían ficheros de netplan redundantes y cloud-init, el sistema de aprovisionamiento de las imágenes de Ubuntu Server, que regenera la configuración de red en cada arranque si nadie se lo prohíbe. Dos gestores para el mismo recurso: el patrón de avería más repetido de la administración de sistemas.

# 1 · prohibir a cloud-init tocar la red (permanente): echo 'network: {config: disabled}' | sudo tee \ /etc/cloud/cloud.cfg.d/99-disable-network-config.cfg # 2 · dejar UN solo fichero en /etc/netplan/ (retirar los demás): ls /etc/netplan/ # → solo 01-netcfg.yaml # 3 · limpiar las direcciones heredadas y reaplicar: sudo ip addr flush dev eth0 sudo netplan apply
Regla: un recurso, un gestor

La dirección del servidor la gobierna un único fichero de netplan; cloud-init queda excluido por escrito y el DHCP del enrutador no reparte esa dirección. Cuando dos sistemas creen gobernar lo mismo, el conflicto no estalla al configurar — estalla un lunes cualquiera, tras un reinicio.

Punto de control
  • Puedo escribir de memoria la estructura del netplan estático: dirección con máscara, puerta y servidores de nombres.
  • Sé desactivar la gestión de red de cloud-init y por qué es necesario.
  • Reconozco el síntoma de «dos gestores» — direcciones duplicadas — y su limpieza (flush + apply).

MÓDULO 04 · 337 pal · 2 min

La puerta de la red: el enrutador MikroTik

Un portal cautivo pensado para personas, la exención que necesita un servidor sin manos, y el lease que reserva su dirección.

Por qué importa

Esta red usa el hotspot de MikroTik: un portal cautivo que intercepta la primera conexión de cada dispositivo y exige usuario y contraseña antes de dar salida. Excelente para controlar el acceso de personas — y letal para un servidor: Caddy no tiene manos para rellenar un formulario. Sin una exención, sus conexiones salientes hacia la API de Cloudflare y Let's Encrypt — las renovaciones del certificado — morirían en silencio en la página de acceso.

La exención: IP Binding

La dirección declarada con tipo bypassed atraviesa el hotspot sin pasar por el portal, conservando el resto de reglas del enrutador.

# Terminal de MikroTik (WinBox → New Terminal, o SSH): /ip hotspot ip-binding add address=10.10.2.245/32 type=bypassed \ comment="Servidor - exento del portal" # verificar: /ip hotspot ip-binding print

El lease estático: el enrutador también conoce al servidor

Aunque la dirección viva fijada dentro del sistema (netplan), el MikroTik registra la pareja MAC↔IP como lease estático: su DHCP jamás repartirá 10.10.2.245 a otro equipo, y sus tablas identificarán siempre al servidor.

/ip dhcp-server lease add address=10.10.2.245 \ mac-address=00:15:5D:00:09:0B \ comment="VM Ubuntu - servidor" # verificar (la entrada aparece sin la marca D de dinámica): /ip dhcp-server lease print
La comprobación que ahorra un misterio

Algunos enrutadores traen protección contra reasociación DNS (DNS rebinding protection): descartan respuestas DNS públicas que contengan direcciones privadas — exactamente lo que hace el registro de este montaje. El síntoma es asimétrico: el portátil resuelve asecna.ababcd.com (usa otro DNS) y el teléfono no. La salida: eximir el dominio o declararlo en el DNS estático del enrutador — /ip dns static add name=asecna.ababcd.com address=10.10.2.245. En esta red no hizo falta; en otra puede ser el primer sospechoso.

Punto de control
  • Sé por qué un proceso automático no puede convivir con un portal cautivo sin exención.
  • Puedo escribir el IP Binding bypassed y el lease estático, y verificar ambos.
  • Reconozco el síntoma del filtro de reasociación DNS y conozco sus dos salidas.

MÓDULO 05 · 468 pal · 2 min

El nombre: Cloudflare

El dominio a precio de coste, el registro A que apunta hacia dentro, y el token que solo puede hacer una cosa.

Por qué importa

El certificado se demostrará en el DNS, así que el DNS es la pieza de identidad de todo el sistema. Registrar el dominio directamente en Cloudflare (venta a precio de coste, ~10 USD al año un .com, renovación automática) tiene una consecuencia útil: nace con sus servidores de nombres ya en Cloudflare — cero delegaciones, cero esperas — y con la API de DNS que Caddy necesitará.

Dos ajustes antes de nada

Autenticación en dos pasos en la cuenta — desde este momento custodia el dominio, el DNS y la emisión de certificados: es la credencial más valiosa del montaje. Y DNSSEC — la firma criptográfica de las respuestas de la zona; con el dominio registrado allí se activa con un clic.

El registro A que mira hacia dentro

# Panel → dominio → DNS → Records → Add record Type: A Name: asecna # se completa: asecna.ababcd.com IPv4: 10.10.2.245 # la IP PRIVADA del servidor Proxy: DNS only # forzado: el proxy no aplica a IP reservadas TTL: Auto

Publicar una dirección privada en un DNS público es perfectamente legal y es el truco del montaje: cualquier dispositivo del mundo puede preguntar por el nombre y recibirá 10.10.2.245 — pero esa dirección solo significa algo dentro de la red local. Fuera, no conduce a nada. El nombre es público; el destino, privado.

TELEFONOSERVIDOR DNS CLOUDFLARE ¿ip del nombre?responde: 10.10.2.24510.10.2.245 (lan) trafico real: directo, dentro de la red local desde fuera, 10.10.2.245 no conduce a nada: el nombre es publico, el destino es privado
la resolución del nombre viaja por internet; el uso de la aplicación, nunca

El token: capacidad mínima

Caddy necesitará crear un registro de texto temporal en la zona. Se le da un token de API con la plantilla Edit zone DNS, acotado en Zone Resources a la zona concreta, sin fecha de caducidad — un token que caduca en silencio es un certificado que deja de renovarse en silencio — y sin filtro de IP de cliente: Cloudflare vería la IP pública del proveedor, que además rota; un filtro aquí bloquearía la propia renovación meses después. El token se muestra una sola vez al crearlo: se copia entero en ese momento.

La llave que no se usa

La Global API Key del panel no se toca jamás para esto: es la llave maestra de toda la cuenta, sin acotar. Usarla donde basta un token acotado es el anti-patrón que los tokens existen para jubilar.

Punto de control
  • Sé crear el registro A con la IP privada y por qué «DNS only» es lo correcto (y forzado).
  • Entiendo por qué publicar una IP privada no expone nada.
  • Puedo enunciar las tres propiedades del token: acotado a la zona, sin caducidad, sin filtro de IP.

MÓDULO 06 · 550 pal · 2 min

El candado: Caddy y el desafío DNS-01

La propiedad demostrada en el DNS, la instalación paquete + binario con plugin, y las seis líneas que gestionan el certificado solas.

Por qué importa

Para emitir un certificado, Let's Encrypt exige demostrar el control del dominio. El método habitual (HTTP-01) consiste en visitar el servidor desde internet — imposible aquí, y esa es precisamente la gracia del montaje. El método DNS-01 lo sustituye: demostrar el control del DNS creando un registro de texto (TXT) con un valor que Let's Encrypt dicta. Solo el dueño de la zona puede crearlo — y Caddy, armado con el token, lo hace por API. El servidor solo necesita salida a internet, nunca entrada.

API CLOUDFLARELET'S ENCRYPT CADDY CERTIFICADO zona ababcd.comautoridad certificadoraquiere el candadovalido ~90 dias 1 crea txt (token)2 pide y valida3 le consulta el txt4 emite todo saliente · renovacion sola ~30 dias antes · txt borrado al terminar · validado en 14 s reales
el desafío dns-01: la propiedad se demuestra en el dns, no recibiendo visitas

Instalación: paquete oficial + binario con plugin

El paquete apt trae el servicio systemd bien montado (usuario propio, permisos para los puertos 80 y 443), pero no incluye el plugin de DNS de Cloudflare. El patrón oficial: instalar el paquete y sustituir el ejecutable por uno compilado con el plugin — el servicio se conserva.

# 1 · repositorio oficial y paquete: sudo apt install -y debian-keyring debian-archive-keyring apt-transport-https curl curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | sudo gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | sudo tee /etc/apt/sources.list.d/caddy-stable.list sudo apt update && sudo apt install caddy # 2 · binario con el plugin (el servicio queda intacto): sudo systemctl stop caddy curl -fsSL "https://caddyserver.com/api/download?os=linux&arch=amd64&p=github.com/caddy-dns/cloudflare" -o /tmp/caddy sudo mv /tmp/caddy /usr/bin/caddy && sudo chmod +x /usr/bin/caddy caddy list-modules | grep cloudflare # → dns.providers.cloudflare

La configuración entera: seis líneas

# /etc/caddy/Caddyfile asecna.ababcd.com { tls { dns cloudflare {env.CLOUDFLARE_API_TOKEN} } reverse_proxy localhost:3000 }

Tres afirmaciones en un bloque: para este nombre — y por escribir un dominio, Caddy activa solo el HTTPS automático: escucha en 443, redirige 80→443, renueva —; demuestra la propiedad por DNS con este token — leído de una variable de entorno, el secreto jamás se escribe en el fichero —; y entrega todo a la aplicación en el puerto 3000 local.

El secreto, al servicio — y el arranque

sudo systemctl edit caddy # entre los dos avisos del fichero (lo escrito bajo el segundo se descarta): [Service] Environment=CLOUDFLARE_API_TOKEN=el_token_completo sudo systemctl restart caddy journalctl -u caddy -f ... "certificate obtained successfully" # ✓ en la puesta real, ~60 s
La avería clásica del primer arranque

listen tcp :80: bind: address already in use — otro servicio ocupa el puerto. En la puesta en marcha real era un Apache de fábrica que había entrado de acompañante con algún paquete. Identificación y retirada: sudo ss -tlnp | grep ':80 'sudo systemctl disable --now apache2. El disable importa tanto como el --now: sin él, Apache volvería a robar el puerto en el siguiente reinicio.

Punto de control
  • Puedo explicar el desafío DNS-01 en cuatro pasos y por qué no exige exposición.
  • Sé instalar Caddy con el plugin (paquete + binario) y verificar el módulo.
  • Escribo el Caddyfile de memoria y sé dónde vive el token (drop-in del servicio, nunca el fichero).
  • Ante un puerto ocupado, sé identificar al inquilino y retirarlo del arranque.

MÓDULO 07 · 411 pal · 2 min

La aplicación en producción: PM2 y el arranque en frío

Las dos variables que cierran el mapa, la resurrección tras el corte de luz, y la prueba que declara «producción» de verdad.

Por qué importa

Un servidor solo es «de producción» cuando un corte de luz no lo mata y cuando no deja puertas traseras abiertas. Esta capa cierra las dos cosas: la aplicación escucha solo donde debe, y cada proceso resucita sin manos.

Dos variables que cambian el mapa

# en el entorno del proceso (.env que lee el script de arranque): AUTH_URL=https://asecna.ababcd.com # la app conoce su dirección: cookies Secure, callbacks correctos HOSTNAME=127.0.0.1 # escucha SOLO en loopback

La puerta trasera

Sin la segunda variable, cualquier teléfono podría entrar por http://10.10.2.245:3000 esquivando el candado — sin cifrado, sin service worker, con las cookies en claro. Con HOSTNAME=127.0.0.1, el único camino hacia la aplicación pasa por Caddy. La verificación es doble: curl -m3 http://10.10.2.245:3000 desde otro equipo debe fallar, y https://asecna.ababcd.com debe responder.

PM2: vivir y resucitar

# arranque bajo PM2 con el script de producción: pm2 start scripts/demarrer-production.mjs --name gestion-equipement # persistencia tras reinicio — DOS pasos, en este orden: pm2 startup # imprime UN comando sudo → ejecutarlo tal cual pm2 save # congela la lista actual: es lo que systemd restaurará
El orden importa

pm2 save fotografía el estado presente: se ejecuta después de dejar los procesos exactamente como deben resucitar — variables de entorno incluidas. Un save prematuro resucitaría la versión a medio configurar. Y cada cambio futuro de la lista termina con otro pm2 save, o no sobrevivirá al siguiente reinicio.

La prueba del arranque en frío

sudo reboot # ... tras volver, cuatro comprobaciones: systemctl status caddy # active (running) pm2 list # gestion-equipement · online curl -sI https://asecna.ababcd.com | head -1 # HTTP/2 307 # y desde el teléfono: candado + la aplicación responde

El HTTP/2 307 son dos verdades en tres caracteres: hay HTTP/2 — que solo existe sobre TLS negociado con certificado válido — y la aplicación está viva respondiendo su redirección legítima a la página de conexión.

ENCENDIDOSYSTEMD CADDY.SERVICEPM2-BRSI.SERVICE APLICACION vuelve la luzarranca servicios certificado del discorestaura pm2 save127.0.0.1:3000 nadie toca nada: cada capa resucita a la siguiente
el arranque en frío: systemd levanta a caddy y a pm2, y pm2 restaura la aplicación fotografiada
Punto de control
  • Sé qué hace cada variable — AUTH_URL y HOSTNAME — y cómo verificar la puerta cerrada.
  • Ejecuto pm2 startup y pm2 save en el orden correcto y sé por qué.
  • Puedo realizar el ensayo del corte de luz y leer sus cuatro comprobaciones.

MÓDULO 08 · 445 pal · 2 min

Glosario

Los términos del montaje, con su definición breve.

Todos los términos técnicos usados en esta guía.

Aplicación web progresiva (PWA)
Aplicación web instalable que puede funcionar sin conexión gracias a un service worker.
Service worker
Proceso del navegador que intercepta las peticiones de la aplicación y sirve desde caché; exige contexto seguro.
Contexto seguro
Origen servido por HTTPS con certificado válido (o localhost); condición para las capacidades avanzadas del navegador.
Certificado TLS
Documento criptográfico que liga un nombre de dominio a una clave; lo emite una autoridad certificadora.
Let's Encrypt
Autoridad certificadora gratuita y automatizable; emite certificados de ~90 días.
Desafío DNS-01
Prueba de propiedad del dominio mediante un registro TXT temporal en su zona DNS; no exige servidor accesible desde internet.
Desafío HTTP-01
Prueba de propiedad mediante un fichero que la autoridad visita por internet; inviable para servidores no expuestos.
Registro A
Entrada de DNS que asocia un nombre a una dirección IPv4.
Registro TXT
Entrada de DNS de texto libre; el desafío DNS-01 la usa como prueba.
DNSSEC
Firma criptográfica de las respuestas de una zona DNS.
Token de API
Credencial acotada a capacidades concretas; aquí, editar el DNS de una sola zona.
Proxy inverso
Servidor que recibe el tráfico exterior y lo entrega a la aplicación; aquí, Caddy terminando el TLS.
Loopback
La interfaz interna 127.0.0.1: solo procesos de la propia máquina pueden hablarle.
NAT
Traducción de direcciones de red: varios equipos comparten una dirección; el conmutador por defecto de Hyper-V la usa.
Conmutador virtual externo
Puente de capa 2 de Hyper-V que conecta la máquina virtual a la red física real.
Dirección MAC
Identificador físico de una tarjeta de red; las reglas del enrutador se atan a ella.
DHCP
Protocolo que reparte direcciones IP automáticamente.
Lease estático
Reserva en el DHCP que fija una pareja MAC↔IP.
Portal cautivo (hotspot)
Página de autenticación que intercepta a los dispositivos antes de darles salida.
IP Binding (bypassed)
Exención de MikroTik: la dirección declarada atraviesa el hotspot sin portal.
Reasociación DNS (rebinding)
Técnica de ataque con nombres públicos e IP privadas; algunos enrutadores filtran esas respuestas por defecto.
netplan
Gestor de red declarativo de Ubuntu Server; aquí, la única fuente de la IP del servidor.
cloud-init
Sistema de aprovisionamiento de las imágenes de servidor; regenera la red en cada arranque si no se desactiva.
systemd
Gestor de servicios de Linux; arranca Caddy y PM2 tras cada reinicio.
Drop-in de systemd
Fragmento de configuración que amplía una unidad sin tocarla; aquí guarda el token como variable de entorno.
PM2
Gestor de procesos de Node.js: mantiene la aplicación viva y la restaura tras un reinicio (startup + save).
Arranque en frío
Encendido tras corte total; la prueba de que todas las capas resucitan sin intervención.

MÓDULO 09 · 308 pal · 2 min

Síntesis

El montaje en una frase y las claves que hay que retener.

En una frase Un nombre público que apunta hacia dentro, un certificado que se demuestra en el DNS y una red local que basta para trabajar: el registro A publica la IP privada 10.10.2.245 bajo asecna.ababcd.com, Caddy demuestra la propiedad del dominio creando un TXT por la API de Cloudflare con un token acotado — sin que el servidor reciba jamás una visita de internet —, el MikroTik exime al servidor de un portal en el que no podría autenticarse, Hyper-V lo pone en la red real con conmutador externo y MAC estática, netplan es la única fuente de su dirección, la aplicación escucha solo en loopback detrás del proxy, y systemd con PM2 resucitan la cadena entera tras el corte de luz — de modo que la prueba final no es un comando sino un teléfono en modo avión registrando trabajo que, al volver la red, llega intacto al servidor.
Claves para retener
  • Sin contexto seguro no hay service worker: HTTPS válido o nada — esa regla origina todo el montaje.
  • El tráfico de uso nunca sale de la red local; internet solo se toca hacia fuera (resolver y renovar).
  • DNS-01 demuestra propiedad en el DNS; HTTP-01 exigiría exposición y por eso queda descartado.
  • El token: plantilla Edit zone DNS, acotado a la zona, sin caducidad, sin filtro de IP — y la Global API Key, jamás.
  • Hyper-V: conmutador externo (no el NAT por defecto) y MAC estática marcada, no supuesta.
  • Un recurso, un gestor: un solo netplan, cloud-init excluido por escrito, lease reservado en el enrutador.
  • El servidor no rellena portales: IP Binding bypassed, o las renovaciones mueren en silencio.
  • HOSTNAME=127.0.0.1 cierra la puerta trasera del :3000; AUTH_URL da a la app su dirección real.
  • pm2 startup + pm2 save (en ese orden), y el ensayo del corte de luz con sus cuatro comprobaciones en verde.