Tema
Subdominios y puertos
Tienes mitienda.com, blog.mitienda.com y api.mitienda.com. Los tres apuntan a la misma IP (203.0.113.10). Entonces:
- ¿Cómo sabe el servidor que a uno le debe mostrar la tienda, al otro el blog y al otro tu API de Node?
- ¿Y cómo sabe a qué puerto conectarse, si en el DNS nunca escribiste ningún puerto?
Esta lección responde esas dos preguntas siguiendo una petición de principio a fin. Cuando la termines vas a entender por qué un subdominio "muestra la página por defecto del hosting", por qué https://mitienda.com:3000 no carga con Cloudflare y cómo publicar varias apps en un mismo servidor.
La idea clave (apréndetela de memoria)
La frase más importante de esta lección
El DNS solo traduce un nombre en una IP. Nada más. No sabe de puertos, ni de carpetas, ni de rutas (/blog), ni de qué app corre en el servidor. El puerto lo decide el navegador (por el https:// o http:// de la URL) y el sitio correcto lo decide el servidor web leyendo el nombre que el navegador le manda.
Son tres responsables distintos:
| Pregunta | Quién la responde | Con qué |
|---|---|---|
| ¿A qué máquina voy? | El DNS | Registro A / AAAA (o CNAME que termina en un A) |
| ¿A qué puerta de esa máquina toco? | El navegador | El esquema de la URL: https → 443, http → 80, o el :puerto escrito en la URL |
| ¿Qué sitio o app me entregan? | El servidor web (Apache, Nginx, LiteSpeed) | El nombre que llega en el SNI (TLS) y en la cabecera Host (HTTP) |
La analogía: un edificio de oficinas
txt
Edificio en "Av. Reforma 123" ← la IP (203.0.113.10). El DNS te da esta dirección.
├── Puerta 80 (entrada antigua, sin seguridad) ← puerto HTTP
├── Puerta 443 (entrada con guardia y detector) ← puerto HTTPS
└── Puerta 22 (acceso de mantenimiento) ← puerto SSH
En la puerta 443 hay un RECEPCIONISTA (Apache/Nginx). Tu sobre dice:
"Para: blog.mitienda.com" ← la cabecera Host
El recepcionista revisa su lista:
mitienda.com → oficina 101 (carpeta public_html)
blog.mitienda.com → oficina 202 (carpeta blog.mitienda.com/)
api.mitienda.com → lo pasa al empleado de la extensión 3000 (app Node)
(nombre que no conoce) → oficina por defecto ("página del hosting")El DNS solo te dijo la dirección del edificio. La puerta la eliges tú según cómo llegas (https = puerta 443). El recepcionista decide a qué oficina va tu sobre leyendo el nombre.
Anatomía de una URL
txt
https://api.mitienda.com:8443/productos?id=5#fotos
└─┬──┘ └──────┬───────┘ └┬─┘└───┬───┘└─┬─┘└─┬─┘
esquema host puerto ruta query fragmento| Parte | Ejemplo | ¿Quién la usa? | ¿Va al DNS? |
|---|---|---|---|
| Esquema | https | Navegador: decide protocolo y puerto por defecto | ❌ |
| Host | api.mitienda.com | DNS (para obtener la IP) y servidor web (para elegir el sitio) | ✅ Solo esto |
| Puerto | 8443 | Navegador: si viene escrito, usa ese en lugar del de por defecto | ❌ |
| Ruta | /productos | Servidor web / tu aplicación | ❌ |
| Query | ?id=5 | Tu aplicación | ❌ |
| Fragmento | #fotos | Solo el navegador (ni siquiera se envía al servidor) | ❌ |
Por eso no se puede "apuntar mitienda.com/blog a otro servidor" con DNS: la ruta /blog nunca llega al DNS. Y por eso no existe un registro A con puerto: 203.0.113.10:3000 es inválido en el campo IP.
¿Qué es un puerto?
Una IP identifica una máquina; un puerto (número de 0 a 65535) identifica un servicio dentro de esa máquina. Un mismo servidor puede tener a la vez la web, el correo, SSH y MySQL, cada uno escuchando en su puerto.
¿Cómo sabe el navegador a qué puerto ir?
- Si la URL trae puerto (
https://mitienda.com:8443) → usa ese. - Si no trae → usa el puerto por defecto del esquema:
| Esquema | Puerto por defecto |
|---|---|
http:// | 80 |
https:// | 443 |
ws:// / wss:// (WebSockets) | 80 / 443 |
ftp:// | 21 |
Por eso nunca escribes :443: está implícito en https. Escribir https://mitienda.com y https://mitienda.com:443 es exactamente lo mismo.
Puertos que vas a ver seguido
| Puerto | Servicio | ¿Público? |
|---|---|---|
| 80 | HTTP (web sin cifrar; solo para redirigir a https) | Sí |
| 443 | HTTPS (web) | Sí |
| 22 | SSH (administrar el servidor) | Restringido |
| 21 | FTP | Mejor no (usa SFTP por el 22) |
| 25 / 465 / 587 | SMTP (enviar correo) | Sí, para correo |
| 993 / 995 | IMAP / POP3 con SSL (leer correo) | Sí, para correo |
| 2083 / 2087 / 2096 | cPanel / WHM / Webmail | Lo abre tu hosting |
| 3000, 5173, 8000, 8080 | Apps en desarrollo (Node, Vite, Django...) | No, solo internos |
| 3306 / 5432 | MySQL / PostgreSQL | Nunca al público |
Los puertos de tus apps no se publican
Tu app Node escucha en 3000, pero tus visitantes nunca deberían escribir :3000. Lo profesional es que la gente entre por 443 y el servidor web reenvíe internamente al 3000. Lo vemos abajo en "proxy inverso".
El viaje completo de una petición
Escribes https://blog.mitienda.com/hola y das Enter. Esto pasa, en orden:
txt
① NAVEGADOR separa la URL
esquema=https → puerto 443 · host=blog.mitienda.com · ruta=/hola
② DNS: "¿IP de blog.mitienda.com?"
Resolver (1.1.1.1) → raíz → .com → nameservers de mitienda.com (Cloudflare)
Respuesta: blog.mitienda.com → A → 203.0.113.10
(si fuera CNAME: blog → mitienda.com → A → 203.0.113.10)
③ TCP: conectar a 203.0.113.10 : 443
④ TLS: el navegador dice en claro "busco blog.mitienda.com" ← SNI
El servidor elige el CERTIFICADO de ese nombre y cifran la conexión
⑤ HTTP (ya cifrado):
GET /hola HTTP/1.1
Host: blog.mitienda.com ← cabecera Host
⑥ SERVIDOR WEB (Apache/Nginx) busca un sitio con ese nombre
blog.mitienda.com → carpeta /home/usuario/blog.mitienda.com
Entrega /hola (o lo pasa a WordPress/PHP/Node)
⑦ Respuesta: 200 OK + el HTMLFíjate: el nombre viaja tres veces y cada vez lo usa alguien distinto:
- En el DNS → para encontrar la IP.
- En el SNI del saludo TLS → para elegir el certificado.
- En la cabecera Host → para elegir el sitio.
Esto se llama virtual hosting basado en nombre y es lo que permite que un solo servidor de hosting compartido tenga miles de sitios en una sola IP.
Pruébalo tú: el recorrido en vivo
Escribe cualquier URL (la tuya, con subdominio o con puerto) y mira cada paso con datos reales:
Prueba con tu dominio, con un subdominio, con un puerto (https://mitienda.com:3000) o con http:// para comparar.
Verlo con tus propios ojos en la terminal
curl -v muestra los pasos ③, ④ y ⑤:
bash
curl -v https://mitienda.com/ -o /dev/nullpowershell
curl.exe -v https://mitienda.com/ -o NULtxt
* Trying 203.0.113.10:443... ← ③ IP del DNS + puerto 443 por usar https
* Connected to mitienda.com (203.0.113.10) port 443
* TLS ... SNI: mitienda.com ← ④ SNI
* Server certificate: subject: CN=mitienda.com ← certificado elegido por SNI
> GET / HTTP/2
> Host: mitienda.com ← ⑤ cabecera Host
< HTTP/2 200En el navegador: F12 → Network (Red) → clic en la primera petición → Headers. Verás Remote Address: 203.0.113.10:443 (IP y puerto) y en los Request Headers el Host (en HTTP/2 aparece como :authority).
Truco profesional: probar un servidor antes de cambiar el DNS
Con --resolve le dices a curl "para este nombre usa esta IP", sin tocar el DNS. Ideal para probar un hosting nuevo antes de migrar:
bash
curl -v --resolve mitienda.com:443:198.51.100.20 https://mitienda.com/El navegador manda el Host: mitienda.com correcto al servidor nuevo, así ves exactamente lo que verán tus visitantes después de cambiar el DNS. (En Windows también puedes editar C:\Windows\System32\drivers\etc\hosts temporalmente.)
¿Cómo se resuelve un subdominio en el DNS?
Un subdominio no es otro dominio ni se compra: es solo otro nombre dentro de la misma zona. Cuando alguien pregunta por blog.mitienda.com:
- El resolver llega a los mismos nameservers de
mitienda.com(Cloudflare). - Cloudflare busca en la zona un registro con nombre exacto
blog. - Si existe → responde su valor (A, AAAA o CNAME).
- Si no existe pero hay un comodín
*→ responde el del comodín. - Si no hay nada → NXDOMAIN ("no existe").
txt
Zona de mitienda.com en Cloudflare:
A @ 203.0.113.10
CNAME www mitienda.com
A blog 203.0.113.10
A api 198.51.100.20 ← otro servidor (un VPS)
CNAME app cname.vercel-dns.com ← otra empresa
A * 203.0.113.10 ← comodín (opcional)
Preguntas:
blog.mitienda.com → coincide "blog" → 203.0.113.10
api.mitienda.com → coincide "api" → 198.51.100.20
tienda.mitienda.com → no hay "tienda" → usa "*" → 203.0.113.10
(sin el comodín) → NXDOMAIN- Cada subdominio puede apuntar a donde quieras: al mismo servidor, a otro VPS, a Vercel, a Shopify. Son independientes.
- También hay varios niveles:
admin.api.mitienda.comes un nombre más (admin.apien el campo Nombre). - Solo si creas registros NS para un subdominio lo delegas a otros nameservers (ver Nameservers y zona). Casi nunca hace falta.
La regla de oro de los subdominios: SIEMPRE son dos pasos
Si te acuerdas de una sola cosa, que sea esta
Un subdominio funciona solo cuando están hechas las dos mitades:
- DNS (en Cloudflare): el nombre apunta a la IP del servidor → "dónde".
- Servidor (cPanel / Nginx / Vercel...): el servidor sabe qué entregar para ese nombre → "qué".
Si haces solo el 1 → llega al servidor, pero el recepcionista no conoce ese nombre: página por defecto del hosting, o el sitio principal, o error de certificado. Si haces solo el 2 → nadie llega: NXDOMAIN.
En un hosting cPanel
- cPanel → Dominios → Crear un nuevo dominio (en versiones viejas: Subdominios). Escribe
blog.mitienda.com.- cPanel crea el virtual host con su carpeta raíz: suele ser
/home/usuario/blog.mitienda.com/(en versiones antiguaspublic_html/blog/). Ahí subes los archivos del blog. - cPanel también agrega el registro DNS en su propia zona... pero si tus nameservers son de Cloudflare, esa zona no cuenta.
- cPanel crea el virtual host con su carpeta raíz: suele ser
- Cloudflare → DNS → Records → Add record:
A, nombreblog, IP203.0.113.10(la IP de tu cPanel), nube naranja. - Espera a que AutoSSL emita el certificado del subdominio (cPanel → SSL/TLS Status) si usas Full (strict).
En un VPS con Nginx: varios subdominios, varias apps, varios puertos
Este es el caso que responde "¿cómo se sabe a qué puerto?". Tienes en el mismo VPS:
mitienda.com→ sitio estático en/var/www/mitiendaapi.mitienda.com→ app Node en el puerto 3000panel.mitienda.com→ otra app en el puerto 4000
En el DNS los tres apuntan a la misma IP. Nginx escucha en el 443 (el único puerto público) y, según el nombre (server_name), entrega una carpeta o reenvía (proxy inverso) a un puerto interno:
nginx
# /etc/nginx/sites-available/mitienda
server {
listen 443 ssl;
server_name mitienda.com www.mitienda.com;
ssl_certificate /etc/letsencrypt/live/mitienda.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/mitienda.com/privkey.pem;
root /var/www/mitienda; # entrega archivos de una carpeta
}
server {
listen 443 ssl;
server_name api.mitienda.com; # ← nombre que llega en Host/SNI
ssl_certificate /etc/letsencrypt/live/api.mitienda.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/api.mitienda.com/privkey.pem;
location / {
proxy_pass http://127.0.0.1:3000; # ← AQUÍ se decide el puerto interno
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
server {
listen 443 ssl;
server_name panel.mitienda.com;
ssl_certificate /etc/letsencrypt/live/panel.mitienda.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/panel.mitienda.com/privkey.pem;
location / {
proxy_pass http://127.0.0.1:4000;
}
}
# Redirige todo http:80 a https
server {
listen 80;
server_name mitienda.com www.mitienda.com api.mitienda.com panel.mitienda.com;
return 301 https://$host$request_uri;
}txt
Visitante → api.mitienda.com:443 ──► Nginx (lee Host: api.mitienda.com)
└──► 127.0.0.1:3000 (tu app Node)
Visitante → panel.mitienda.com:443 ─► Nginx (lee Host: panel.mitienda.com)
└──► 127.0.0.1:4000Las apps escuchan solo en 127.0.0.1, así que nadie de fuera puede entrar directo a :3000. Respuesta corta a tu pregunta: el puerto interno lo define tu configuración del servidor (proxy_pass), no el DNS. Más detalle en Nginx y Node con PM2.
El equivalente en Apache son bloques <VirtualHost *:443> con ServerName api.mitienda.com y ProxyPass / http://127.0.0.1:3000/.
El sitio "por defecto"
Si llega un Host que ningún bloque reconoce, el servidor usa el primer sitio o el marcado como default_server. Por eso:
- Un subdominio creado en el DNS pero no en cPanel → muestra la página genérica del hosting o el sitio principal.
- Si entras por IP (
https://203.0.113.10) → ves el sitio por defecto y un error de certificado (ningún certificado es "para una IP").
Con Cloudflare en medio (nube naranja)
Con el proxy activado hay dos conexiones, y cada una tiene su puerto:
txt
Visitante ──(1)──► Cloudflare ──(2)──► Tu servidor (origen)
https:443 revisa caché, por defecto, al MISMO puerto
seguridad, SSL que usó el visitante (443)
y con el mismo Host: blog.mitienda.com- El DNS le da al visitante una IP de Cloudflare, no la tuya (por eso
nslookupmuestra104.21.x.xo172.67.x.x). - Cloudflare se conecta a tu servidor con el mismo Host, así que tu virtual host sigue funcionando igual.
- En modo SSL Full / Full (strict) la conexión (2) va a tu puerto 443. En Flexible va al 80 (sin cifrar; por eso es mala idea, ver SSL sin pagar).
Puertos que acepta el proxy de Cloudflare
Con nube naranja, el visitante solo puede usar estos puertos:
| HTTP | HTTPS |
|---|---|
| 80, 8080, 8880, 2052, 2082, 2086, 2095 | 443, 2053, 2083, 2087, 2096, 8443 |
https://api.mitienda.com:3000 con nube naranja no carga (3000 no está en la lista). Tienes tres soluciones, de mejor a peor:
Proxy inverso en tu servidor (Nginx/Apache en el 443 →
127.0.0.1:3000), como arriba. Lo estándar.Cloudflare Tunnel: un programa (
cloudflared) en tu servidor se conecta hacia fuera a Cloudflare, y tú defines qué subdominio va a qué puerto. No necesitas abrir puertos ni tener IP pública: funciona hasta con la PC de tu casa.yaml# ~/.cloudflared/config.yml tunnel: 6ff42ae2-765d-4adf-8112-31c55c1551ef credentials-file: /home/erick/.cloudflared/6ff42ae2-765d-4adf-8112-31c55c1551ef.json ingress: - hostname: api.mitienda.com # subdominio... service: http://localhost:3000 # ...a este puerto - hostname: panel.mitienda.com service: http://localhost:4000 - service: http_status:404 # todo lo demásCloudflare crea solo el registro DNS (un CNAME a
<id>.cfargotunnel.com). Desde el panel: Zero Trust → Networks → Tunnels (el nombre del menú puede variar).Origin Rules (Cloudflare → Rules → Origin Rules): "si el host es
api.mitienda.com, conéctate al origen por el puerto 3000". El visitante entra por 443 y Cloudflare cambia el puerto hacia tu servidor. Útil si no puedes instalar Nginx, pero tu puerto 3000 queda expuesto a internet (protégelo con el firewall permitiendo solo IPs de Cloudflare).
SSL de los subdominios con Cloudflare
El SSL Universal gratis cubre mitienda.com y un nivel de subdominio (*.mitienda.com): blog., api., www. ✅. No cubre dos niveles como admin.api.mitienda.com ❌ (error de certificado con nube naranja). Usa nombres de un solo nivel (admin-api.mitienda.com) o pon ese registro en gris con su propio certificado Let's Encrypt.
La excepción: el registro SRV sí lleva puerto
Existe un tipo de registro que guarda puerto: SRV. Pero solo lo usan programas que lo consultan a propósito (Minecraft, Teams/SIP, XMPP, algunos clientes de correo). Los navegadores no usan SRV, así que no sirve para sitios web.
txt
Tipo Nombre Prioridad Peso Puerto Destino
SRV _minecraft._tcp.mc 0 5 25565 juego.mitienda.com
A juego (DNS only) 198.51.100.30Así los jugadores escriben mc.mitienda.com y Minecraft descubre solo el puerto 25565. Más en Otros registros.
Recetas: "quiero que este subdominio muestre..."
| Quiero | DNS (Cloudflare) | Servidor |
|---|---|---|
blog. en el mismo cPanel | A blog 203.0.113.10 🟠 | cPanel → Dominios → crear blog.mitienda.com |
api. = app Node en el mismo VPS | A api <IP VPS> 🟠 | Nginx server_name api... + proxy_pass :3000 |
app. en Vercel | CNAME app cname.vercel-dns.com ⚪ | Vercel → Settings → Domains → agregar app.mitienda.com |
tienda. en Shopify | CNAME tienda shops.myshopify.com ⚪ | Shopify → Dominios → conectar |
demo. = app en la PC de tu casa | (lo crea el túnel) | Cloudflare Tunnel → localhost:5173 |
mc. = servidor Minecraft | A juego <IP> ⚪ + SRV | El juego escuchando en 25565 |
mail. = correo de cPanel | A mail 203.0.113.10 ⚪ | Nada: cPanel ya lo atiende |
🟠 = nube naranja (proxied) · ⚪ = nube gris (DNS only). Vercel y Shopify recomiendan gris para emitir su propio SSL.
Errores comunes y qué significan
| Síntoma | Qué pasa realmente | Solución |
|---|---|---|
DNS_PROBE_FINISHED_NXDOMAIN en blog. | Falta el registro en la zona activa (paso 1) | Crea A/CNAME blog en Cloudflare |
| El subdominio muestra el sitio principal o "página por defecto" | El DNS está bien, pero el servidor no conoce ese nombre (paso 2) | Crea el subdominio en cPanel / server_name en Nginx |
ERR_CONNECTION_REFUSED | Llegaste a la IP pero nada escucha en ese puerto | Arranca el servicio o revisa el puerto |
ERR_CONNECTION_TIMED_OUT | Un firewall bloquea el puerto | Abre 80/443 en el firewall (ufw allow 443) |
| Error 521 de Cloudflare | Cloudflare no pudo conectar con tu servidor | Servidor caído, puerto 443 cerrado o firewall bloqueando a Cloudflare |
NET::ERR_CERT_COMMON_NAME_INVALID | El certificado no incluye ese subdominio (o es de 2 niveles) | Emite el certificado del subdominio / usa un solo nivel |
https://x.mitienda.com:3000 no carga con 🟠 | Puerto no admitido por el proxy | Proxy inverso, Tunnel u Origin Rules |
Ejercicios
- Escribe la URL
https://shop.mitienda.com:8443/carrito?id=9y separa esquema, host, puerto, ruta y query. ¿Qué parte se envía al DNS? blog.mitienda.comymitienda.comapuntan a203.0.113.10. Al abrir el blog ves la tienda. ¿Qué mitad falta y dónde se arregla?- Tienes un VPS con una API en el puerto 5000 y quieres
https://api.mitienda.comsin escribir puerto. Escribe el registro DNS y el bloque de Nginx. - ¿Por qué no puedes escribir
203.0.113.10:5000en un registro A? - ¿Funcionará
https://admin.panel.mitienda.comcon nube naranja y el SSL Universal? ¿Qué harías?
Ver solución
- Esquema
https, hostshop.mitienda.com, puerto8443(escrito; si no, sería 443), ruta/carrito, queryid=9. Al DNS solo vashop.mitienda.com. - Falta la parte del servidor: el DNS ya lleva al servidor, pero este no tiene un sitio para
blog.mitienda.comy responde con el principal. Créalo en cPanel → Dominios (o unserver_nameen Nginx) y sube los archivos del blog a su carpeta. - DNS:
A api <IP del VPS>(naranja). Nginx:nginxserver { listen 443 ssl; server_name api.mitienda.com; ssl_certificate /etc/letsencrypt/live/api.mitienda.com/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/api.mitienda.com/privkey.pem; location / { proxy_pass http://127.0.0.1:5000; proxy_set_header Host $host; } } - Porque el DNS solo traduce nombres a IPs; el campo de un registro A acepta únicamente una IPv4. El puerto lo decide el navegador por el esquema (https → 443), y el reenvío al 5000 lo hace tu servidor.
- No: el certificado Universal cubre
*.mitienda.com(un nivel). Opciones: renombrar aadmin-panel.mitienda.com, poner el registro en gris con su propio Let's Encrypt, o pagar un certificado avanzado de Cloudflare (no vale la pena para esto).
Resumen
El DNS solo da la IP. El puerto lo decide el navegador por el esquema (
https→ 443,http→ 80) o por el:puertode la URL.El nombre viaja en el DNS, en el SNI y en la cabecera Host; el servidor usa los dos últimos para elegir certificado y sitio (virtual hosts).
Un subdominio es solo otro nombre de la zona y puede apuntar a cualquier servidor.
Siempre son dos pasos: registro DNS (dónde) + configuración en el servidor (qué).
Las apps en puertos internos (3000, 4000...) se publican con proxy inverso (Nginx/Apache), Cloudflare Tunnel u Origin Rules, nunca pidiendo al visitante que escriba el puerto.
Con nube naranja solo funcionan ciertos puertos, y el SSL gratis cubre un nivel de subdominio.
SRV es el único registro con puerto, y los navegadores no lo usan.
Siguiente: MX: cómo funciona el correo.