Skip to content

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:

PreguntaQuién la respondeCon qué
¿A qué máquina voy?El DNSRegistro A / AAAA (o CNAME que termina en un A)
¿A qué puerta de esa máquina toco?El navegadorEl 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
ParteEjemplo¿Quién la usa?¿Va al DNS?
EsquemahttpsNavegador: decide protocolo y puerto por defecto❌
Hostapi.mitienda.comDNS (para obtener la IP) y servidor web (para elegir el sitio)✅ Solo esto
Puerto8443Navegador: si viene escrito, usa ese en lugar del de por defecto❌
Ruta/productosServidor web / tu aplicación❌
Query?id=5Tu aplicación❌
Fragmento#fotosSolo 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? ​

  1. Si la URL trae puerto (https://mitienda.com:8443) → usa ese.
  2. Si no trae → usa el puerto por defecto del esquema:
EsquemaPuerto 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 ​

PuertoServicio¿Público?
80HTTP (web sin cifrar; solo para redirigir a https)Sí
443HTTPS (web)Sí
22SSH (administrar el servidor)Restringido
21FTPMejor no (usa SFTP por el 22)
25 / 465 / 587SMTP (enviar correo)Sí, para correo
993 / 995IMAP / POP3 con SSL (leer correo)Sí, para correo
2083 / 2087 / 2096cPanel / WHM / WebmailLo abre tu hosting
3000, 5173, 8000, 8080Apps en desarrollo (Node, Vite, Django...)No, solo internos
3306 / 5432MySQL / PostgreSQLNunca 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 HTML

Fíjate: el nombre viaja tres veces y cada vez lo usa alguien distinto:

  1. En el DNS → para encontrar la IP.
  2. En el SNI del saludo TLS → para elegir el certificado.
  3. 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/null
powershell
curl.exe -v https://mitienda.com/ -o NUL
txt
*   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 200

En 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:

  1. El resolver llega a los mismos nameservers de mitienda.com (Cloudflare).
  2. Cloudflare busca en la zona un registro con nombre exacto blog.
  3. Si existe → responde su valor (A, AAAA o CNAME).
  4. Si no existe pero hay un comodín * → responde el del comodín.
  5. 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.com es un nombre más (admin.api en 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:

  1. DNS (en Cloudflare): el nombre apunta a la IP del servidor → "dónde".
  2. 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 ​

  1. 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 antiguas public_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.
  2. Cloudflare → DNS → Records → Add record: A, nombre blog, IP 203.0.113.10 (la IP de tu cPanel), nube naranja.
  3. 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/mitienda
  • api.mitienda.com → app Node en el puerto 3000
  • panel.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:4000

Las 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 nslookup muestra 104.21.x.x o 172.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:

HTTPHTTPS
80, 8080, 8880, 2052, 2082, 2086, 2095443, 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:

  1. Proxy inverso en tu servidor (Nginx/Apache en el 443 → 127.0.0.1:3000), como arriba. Lo estándar.

  2. 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ás

    Cloudflare crea solo el registro DNS (un CNAME a <id>.cfargotunnel.com). Desde el panel: Zero Trust → Networks → Tunnels (el nombre del menú puede variar).

  3. 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.30

Así los jugadores escriben mc.mitienda.com y Minecraft descubre solo el puerto 25565. Más en Otros registros.

Recetas: "quiero que este subdominio muestre..." ​

QuieroDNS (Cloudflare)Servidor
blog. en el mismo cPanelA blog 203.0.113.10 🟠cPanel → Dominios → crear blog.mitienda.com
api. = app Node en el mismo VPSA api <IP VPS> 🟠Nginx server_name api... + proxy_pass :3000
app. en VercelCNAME app cname.vercel-dns.com ⚪Vercel → Settings → Domains → agregar app.mitienda.com
tienda. en ShopifyCNAME 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 MinecraftA juego <IP> ⚪ + SRVEl juego escuchando en 25565
mail. = correo de cPanelA 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íntomaQué pasa realmenteSolució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_REFUSEDLlegaste a la IP pero nada escucha en ese puertoArranca el servicio o revisa el puerto
ERR_CONNECTION_TIMED_OUTUn firewall bloquea el puertoAbre 80/443 en el firewall (ufw allow 443)
Error 521 de CloudflareCloudflare no pudo conectar con tu servidorServidor caído, puerto 443 cerrado o firewall bloqueando a Cloudflare
NET::ERR_CERT_COMMON_NAME_INVALIDEl 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 proxyProxy inverso, Tunnel u Origin Rules

Ejercicios ​

  1. Escribe la URL https://shop.mitienda.com:8443/carrito?id=9 y separa esquema, host, puerto, ruta y query. ¿Qué parte se envía al DNS?
  2. blog.mitienda.com y mitienda.com apuntan a 203.0.113.10. Al abrir el blog ves la tienda. ¿Qué mitad falta y dónde se arregla?
  3. Tienes un VPS con una API en el puerto 5000 y quieres https://api.mitienda.com sin escribir puerto. Escribe el registro DNS y el bloque de Nginx.
  4. ¿Por qué no puedes escribir 203.0.113.10:5000 en un registro A?
  5. ¿Funcionará https://admin.panel.mitienda.com con nube naranja y el SSL Universal? ¿Qué harías?
Ver solución
  1. Esquema https, host shop.mitienda.com, puerto 8443 (escrito; si no, sería 443), ruta /carrito, query id=9. Al DNS solo va shop.mitienda.com.
  2. Falta la parte del servidor: el DNS ya lleva al servidor, pero este no tiene un sitio para blog.mitienda.com y responde con el principal. Créalo en cPanel → Dominios (o un server_name en Nginx) y sube los archivos del blog a su carpeta.
  3. DNS: A api <IP del VPS> (naranja). Nginx:
    nginx
    server {
        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; }
    }
  4. 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.
  5. No: el certificado Universal cubre *.mitienda.com (un nivel). Opciones: renombrar a admin-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 :puerto de 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.

Academia Web — aprende, copia y construye.