Dolor: su servidor “funcionaba ayer”, instaló “solo una cosa” y ahora el puerto 80 está “ya en uso”, las redirecciones TLS giran sin fin, o Nginx está proxying a… sí mismo. Nada está en llamas, pero su gráfico de disponibilidad está sudando.
Ubuntu 24.04 facilita la instalación de Apache y Nginx, a veces sin que usted se dé cuenta. El truco no es “reiniciar todo hasta que funcione”. El truco es decidir quién posee qué puerto, y luego hacer que sus proxies y cabeceras digan la verdad.
Guía de diagnóstico rápido
Cuando Apache y Nginx están presentes, la ruta más rápida es responder tres preguntas en orden. No toque configuraciones hasta que pueda responderlas con evidencias.
1) ¿Quién está realmente escuchando en 80 y 443?
Si no sabe qué proceso tiene los sockets, está adivinando. Adivinar es cómo se crean “soluciones temporales” que duran años.
- Compruebe los listeners con
ssy asocie PIDs a servicios. - Si ve
apache2ynginxintentando poseer:80, ya encontró la primera falla.
2) ¿A dónde va el tráfico después del primer accept()?
Confirme si Nginx hace reverse proxy a Apache (común), Apache hace proxy a otra cosa, o ambos están en un bucle de proxy.
- Use
curl -vpara ver cabecerasLocation:, códigos de estado y si la detección de HTTPS es incorrecta. - Inspeccione los virtual hosts activos en ambos servidores (
apachectl -S,nginx -T).
3) ¿Las redirecciones y las decisiones de “scheme” se basan en la realidad?
La mayoría de los bucles de proxy no son “problemas de red”. Son problemas de verdad: el backend piensa que la petición es HTTP cuando el cliente vino por HTTPS, así que sigue redirigiendo “a HTTPS” para siempre.
- Arregle las cabeceras:
X-Forwarded-Proto,X-Forwarded-Host, y (si es necesario) el manejo deRemoteIPde Apache. - Corrija las expectativas de puerto y esquema del backend (vhost de Apache en 127.0.0.1:8080, no en 80).
Punto de decisión: Si no puede describir la ruta de la petición en una oración (“El cliente llega a Nginx :443, se proxya a Apache :8080 por loopback, Apache sirve la app con cabeceras de esquema correctas”), deténgase y mapéela. No haga cambios hasta que pueda narrarla.
Datos y contexto interesantes (por qué esto sigue ocurriendo)
- Nginx fue diseñado para el problema C10k. El diseño orientado a eventos de Igor Sysoev (principios de los 2000) se convirtió en la opción por defecto para frontends de alta concurrencia.
- Los modelos de procesos/hilos de Apache evolucionaron para distintas eras. Existen MPMs prefork, worker y event porque “una talla sirve para todos” nunca encajó en producción.
- Históricamente Ubuntu convirtió a Apache en el servidor web por defecto. Esa memoria muscular persiste; muchos paquetes asumen que Apache está presente o puede activarse.
- Systemd introdujo la activación de sockets como ciudadano de primera clase. Puede mantener un puerto abierto y arrancar un servicio bajo demanda, lo que confunde el depurado de “¿quién escucha?” si no revisa los tipos de unidad.
- Las cabeceras de reverse proxy son un estándar de facto, no una única especificación.
X-Forwarded-*se usa ampliamente pero se interpreta de forma inconsistente por apps y frameworks. - “Demasiadas redirecciones” suele ser determinista. Se siente aleatorio porque caches y HSTS lo amplifican, pero el bucle es repetible con
curl -v. - El puerto 80 es especial porque todo lo quiere. Portales cautivos, desafíos ACME HTTP-01, sitios por defecto y “servidores de depuración temporales” compiten por él.
- La cultura de
a2enmod/a2ensitede Apache es distinta a los symlinks desites-enabledde Nginx. Mezclar modelos mentales conduce a momentos de “cambié un archivo, ¿por qué no surtió efecto?”.
Modelo mental: bind, accept, proxy, redirect
La confusión en la pila web rara vez trata sobre “qué servidor es mejor”. Trata sobre la propiedad de los sockets y la claridad de intención.
Binding: solo un proceso posee un puerto TCP
En un host Linux normal, 0.0.0.0:80 puede ser enlazado por exactamente un proceso a la vez. Si inicia Apache después de que Nginx ya está enlazado a :80, Apache fallará (o viceversa). Si systemd mantiene el puerto para activación por socket, ambos pueden “parecer que están arrancando” mientras uno no puede realmente recibir tráfico.
Aceptación: el primer servidor marca la pauta
El servidor que acepta la conexión controla la terminación TLS, HTTP/2 vs HTTP/1.1, logs de acceso en el borde y decisiones de “IP real del cliente”. Todo lo que está detrás es un backend. Trátelo como una capa edge, incluso si está en la misma VM.
Proxying: una dirección, sin boomerangs
Un reverse proxy debe enviar tráfico a un puerto distinto o a un destino diferente. La forma más fácil de crear un bucle accidental es proxyar a un hostname que resuelve de nuevo al mismo listener (o al mismo puerto a través de reglas NAT). “Funciona en mi portátil” se convierte en “se derrite en producción” porque DNS y /etc/hosts difieren.
Redirección: la app debe saber lo que vio el cliente
Los backends a menudo generan redirecciones absolutas basadas en el esquema y host que perciben. Si TLS termina en Nginx pero Apache ve HTTP simple en 127.0.0.1:8080, Apache (o su app) puede insistir “debe ser HTTPS” y redirigir a HTTPS. El navegador vuelve a Nginx HTTPS, que reenvía HTTP otra vez, y el backend redirige otra vez. Ese es su bucle.
Hay dos verdades limpias que puede enseñar a su backend:
- Dígale el esquema original vía
X-Forwarded-Proto: https. - O haga que el backend hable HTTPS también (menos común localmente; generalmente innecesario).
Una cita, una regla de vida: la frase de Andrew S. Grove “Only the paranoid survive” es una idea útil para operaciones: asuma que sus valores por defecto lo traicionarán a menos que los verifique.
Tareas prácticas (comandos, salida esperada, decisiones)
Estas son tareas reales que puede ejecutar en Ubuntu 24.04 para diagnosticar y arreglar enlaces de puertos y bucles de proxy. Cada tarea incluye: comando, significado de la salida y la decisión que debe tomar.
Task 1: Identify who listens on 80/443 (the only starting point that matters)
cr0x@server:~$ sudo ss -ltnp '( sport = :80 or sport = :443 )'
State Recv-Q Send-Q Local Address:Port Peer Address:Port Process
LISTEN 0 511 0.0.0.0:80 0.0.0.0:* users:(("nginx",pid=1274,fd=6))
LISTEN 0 511 0.0.0.0:443 0.0.0.0:* users:(("nginx",pid=1274,fd=7))
Qué significa: Nginx posee ambos sockets. Apache no es la capa edge, incluso si está instalado.
Decisión: Si quiere Apache detrás de Nginx, mueva Apache fuera de 80/443 (p. ej., a 8080). Si quiere Apache como único servidor, detenga/deshabilite Nginx.
Task 2: Check if Apache is failing due to bind errors
cr0x@server:~$ sudo journalctl -u apache2 -n 50 --no-pager
Aug 14 09:12:10 server apachectl[2219]: (98)Address already in use: AH00072: make_sock: could not bind to address 0.0.0.0:80
Aug 14 09:12:10 server apachectl[2219]: AH00451: no listening sockets available, shutting down
Aug 14 09:12:10 server apachectl[2219]: AH00015: Unable to open logs
Qué significa: Apache está configurado para escuchar en el puerto 80 y no puede.
Decisión: O detenga al otro listener (a menudo Nginx), o cambie las directivas Listen de Apache a un puerto de backend.
Task 3: Confirm systemd unit states (enabled vs running vs socket-activated)
cr0x@server:~$ systemctl status nginx --no-pager
● nginx.service - A high performance web server and a reverse proxy server
Loaded: loaded (/usr/lib/systemd/system/nginx.service; enabled; preset: enabled)
Active: active (running) since Wed 2025-08-14 09:10:22 UTC; 4min 18s
Docs: man:nginx(8)
Main PID: 1274 (nginx)
Tasks: 3 (limit: 18931)
Memory: 6.8M
CPU: 78ms
cr0x@server:~$ systemctl status apache2 --no-pager
● apache2.service - The Apache HTTP Server
Loaded: loaded (/usr/lib/systemd/system/apache2.service; enabled; preset: enabled)
Active: failed (Result: exit-code) since Wed 2025-08-14 09:12:10 UTC; 2min 30s ago
Main PID: 2219 (code=exited, status=1/FAILURE)
Qué significa: Nginx está sano; Apache ha fallado. Esto no es una situación de “reiniciar ambos”; es una decisión de diseño.
Decisión: Elija una topología: (A) solo Nginx, (B) solo Apache, (C) Nginx edge + Apache backend. Luego configure en consecuencia.
Task 4: Inventory Apache listening directives and enabled sites
cr0x@server:~$ grep -R --line-number -E '^\s*Listen\s+' /etc/apache2
/etc/apache2/ports.conf:5:Listen 80
/etc/apache2/ports.conf:9:Listen 443
cr0x@server:~$ sudo apachectl -S
VirtualHost configuration:
*:80 is a NameVirtualHost
default server 000-default (/etc/apache2/sites-enabled/000-default.conf:1)
port 80 namevhost example.internal (/etc/apache2/sites-enabled/example.conf:1)
*:443 example.internal (/etc/apache2/sites-enabled/example-le-ssl.conf:2)
ServerRoot: "/etc/apache2"
Main DocumentRoot: "/var/www/html"
Main ErrorLog: "/var/log/apache2/error.log"
Qué significa: Apache está configurado para enlazar a 80 y 443. Si Nginx es el borde, esto debe cambiar.
Decisión: Edite /etc/apache2/ports.conf y los vhosts relacionados para enlazar a 127.0.0.1:8080 (y posiblemente 8443 si fuera necesario). O deshabilite los vhosts de Apache por completo si no se usan.
Task 5: Dump Nginx effective config to see real includes and upstreams
cr0x@server:~$ sudo nginx -T
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful
# configuration file /etc/nginx/nginx.conf:
http {
include /etc/nginx/mime.types;
include /etc/nginx/conf.d/*.conf;
include /etc/nginx/sites-enabled/*;
}
Qué significa: No puede confiar en “edité un archivo” a menos que sepa qué se incluye. nginx -T le dice la verdad.
Decisión: Localice dónde se define proxy_pass y verifique que apunte a un puerto de backend distinto del listener.
Task 6: Check for the classic self-proxy mistake (proxying to the same host/port)
cr0x@server:~$ sudo grep -R --line-number -E 'proxy_pass\s+http' /etc/nginx/sites-enabled /etc/nginx/conf.d
/etc/nginx/sites-enabled/example.conf:18: proxy_pass http://127.0.0.1:80;
Qué significa: Si Nginx escucha en 80 y hace proxy a 127.0.0.1:80, se está proxyando a sí mismo. Eso es un bucle que puede parecer un bloqueo, un 502 o un pico de CPU.
Decisión: Cambie el backend a un puerto distinto (p. ej., Apache en 127.0.0.1:8080) o a un destino diferente por completo.
Task 7: Validate backend reachability (separate connectivity from HTTP behavior)
cr0x@server:~$ curl -sS -D- http://127.0.0.1:8080/ -o /dev/null
HTTP/1.1 200 OK
Date: Wed, 14 Aug 2025 09:17:02 GMT
Server: Apache/2.4.58 (Ubuntu)
Content-Type: text/html; charset=UTF-8
Qué significa: Apache es accesible en 8080 y sirve contenido. Bien. Ahora su proxy puede apuntar allí.
Decisión: Si esto falla, arregle Apache primero (puertos, vhost, firewall, estado del servicio). No “ajuste Nginx” cuando el backend está muerto.
Task 8: Catch a redirect loop with headers (you can’t fix what you won’t observe)
cr0x@server:~$ curl -k -I https://example.internal/
HTTP/2 301
date: Wed, 14 Aug 2025 09:18:41 GMT
location: http://example.internal/
server: nginx
cr0x@server:~$ curl -I http://example.internal/
HTTP/1.1 301 Moved Permanently
Date: Wed, 14 Aug 2025 09:18:45 GMT
Location: https://example.internal/
Server: nginx
Qué significa: HTTPS redirige a HTTP, e HTTP redirige a HTTPS. Ese es un bucle de dos nodos. Usualmente reglas de redirección mal configuradas o configs en conflicto en diferentes vhosts.
Decisión: Elija un esquema canónico (normalmente HTTPS) y aplíquelo en un solo lugar (normalmente el servidor edge). Elimine redirecciones competidoras en el backend.
Task 9: Inspect headers Nginx forwards (scheme and host are the usual culprits)
cr0x@server:~$ sudo grep -n 'proxy_set_header' -n /etc/nginx/sites-enabled/example.conf
22: proxy_set_header Host $host;
23: proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
24: proxy_set_header X-Forwarded-Proto $scheme;
Qué significa: Si X-Forwarded-Proto no está establecido, muchas apps detectarán mal HTTPS y generarán redirecciones incorrectas o contenido mixto.
Decisión: Asegúrese de que Host y X-Forwarded-Proto estén establecidos. Si termina TLS en Nginx, $scheme debe ser https para solicitudes HTTPS del cliente.
Task 10: Verify Apache sees the forwarded scheme and doesn’t “upgrade” again
cr0x@server:~$ sudo grep -R --line-number -E 'RewriteRule|RewriteCond|HTTPS|X-Forwarded-Proto' /etc/apache2/sites-enabled
/etc/apache2/sites-enabled/example.conf:12:RewriteCond %{HTTPS} !=on
/etc/apache2/sites-enabled/example.conf:13:RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]
Qué significa: Apache está haciendo una redirección HTTPS basada en su propia variable %{HTTPS}. Detrás de un proxy que termina SSL, Apache pensará que HTTPS está apagado.
Decisión: Elimine esta redirección de Apache cuando Nginx sea el edge, o reescriba la condición para respetar X-Forwarded-Proto para que el backend no pelee con el frontend.
Task 11: Check for duplicate/default sites shadowing your intended host
cr0x@server:~$ ls -l /etc/nginx/sites-enabled
total 0
lrwxrwxrwx 1 root root 34 Aug 14 09:05 default -> /etc/nginx/sites-available/default
lrwxrwxrwx 1 root root 34 Aug 14 09:06 example.conf -> /etc/nginx/sites-available/example.conf
Qué significa: El sitio por defecto de Nginx puede capturar tráfico que esperaba que fuera a otro sitio, dependiendo del emparejamiento de server_name y las reglas default_server.
Decisión: Deshabilite el sitio por defecto si no se necesita, o asegúrese de que su sitio intencionado esté marcado como default_server para las líneas listen relevantes.
Task 12: Confirm the “real” config is loaded and reload safely
cr0x@server:~$ sudo nginx -t
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful
cr0x@server:~$ sudo systemctl reload nginx
Qué significa: La sintaxis es válida, y el reload aplica cambios sin caer conexiones existentes (la mayoría de las veces, eso es lo que quiere).
Decisión: Prefiera reload sobre restart para Nginx/Apache al aplicar cambios en producción, a menos que esté cambiando módulos de bajo nivel o el servicio esté bloqueado.
Task 13: Check DNS/hosts resolution to avoid “proxy to myself” by name
cr0x@server:~$ getent hosts example.internal
10.10.20.15 example.internal
Qué significa: Si Nginx proxya a http://example.internal y eso resuelve a la IP pública del servidor, puede reenviarse de nuevo a Nginx en lugar de alcanzar Apache.
Decisión: Haga proxy a 127.0.0.1 (o a una IP de backend dedicada) y a un puerto de backend. Evite usar el hostname público para backends locales a menos que explíctamente quiera atravesar el enrutamiento edge.
Task 14: Verify UFW isn’t “helping” by blocking your backend port
cr0x@server:~$ sudo ufw status verbose
Status: active
Logging: on (low)
Default: deny (incoming), allow (outgoing), deny (routed)
New profiles: skip
To Action From
OpenSSH ALLOW IN Anywhere
80/tcp ALLOW IN Anywhere
443/tcp ALLOW IN Anywhere
Qué significa: El puerto backend 8080 no está permitido desde internet, lo cual es bueno. Pero si Apache se enlaza a 127.0.0.1:8080, UFW no importa para el loopback de todos modos.
Decisión: Enlace los servicios backend al loopback. No abra puertos backend a menos que realmente necesite acceso remoto.
Task 15: Confirm Apache is actually listening where you think after changes
cr0x@server:~$ sudo ss -ltnp | grep -E ':(80|8080|443|8443)\b'
LISTEN 0 511 127.0.0.1:8080 0.0.0.0:* users:(("apache2",pid=3012,fd=4))
LISTEN 0 511 0.0.0.0:80 0.0.0.0:* users:(("nginx",pid=1274,fd=6))
LISTEN 0 511 0.0.0.0:443 0.0.0.0:* users:(("nginx",pid=1274,fd=7))
Qué significa: Esta es la separación limpia que desea: Nginx posee puertos de internet, Apache permanece privado en loopback.
Decisión: Asegúrelo: confirme que Apache no esté configurado para enlazar a interfaces públicas y que ningún otro servicio vaya a capturar 8080 inesperadamente.
Task 16: Look for 502/504 root causes in logs (don’t stare at the browser)
cr0x@server:~$ sudo tail -n 30 /var/log/nginx/error.log
2025/08/14 09:22:33 [error] 1274#1274: *118 connect() failed (111: Connection refused) while connecting to upstream, client: 10.10.20.33, server: example.internal, request: "GET / HTTP/2.0", upstream: "http://127.0.0.1:8080/", host: "example.internal"
Qué significa: Nginx no puede conectar al upstream; Apache está caído, escucha en otro lugar o está bloqueado.
Decisión: Arregle el estado y el binding del servicio backend, no los timeouts de Nginx. Connection refused no es un problema de rendimiento.
Broma corta #1: Si “arregló” un conflicto de puertos reiniciando, felicitaciones—enseñó al problema a esconderse mejor.
Patrones de solución limpios (elija uno y comprométalo)
Puede ejecutar Apache y Nginx juntos. También puede ejecutar cualquiera de los dos en solitario. El modo de fallo viene de ejecutar ambos con responsabilidades superpuestas y sin clara propiedad de puertos.
Patrón A: Nginx edge (80/443) + Apache backend (127.0.0.1:8080)
Este es el estado más común “ambos instalados” que se mantiene sensato. Nginx maneja TLS, HTTP/2, archivos estáticos si quiere, buffering y ruteo simple. Apache sirve apps PHP (vía PHP-FPM o mod_php) o setups heredados con .htaccess. Apache nunca toca los puertos 80/443 en la interfaz pública.
Apache: muévase fuera de 80/443
Edite /etc/apache2/ports.conf para que sea algo como:
cr0x@server:~$ sudo sed -n '1,120p' /etc/apache2/ports.conf
# If you just change these ports or add more ports here, you will likely also
# have to change the VirtualHost statement in
# /etc/apache2/sites-enabled/000-default.conf
Listen 127.0.0.1:8080
Luego asegúrese de que su vhost del sitio escuche en 8080:
cr0x@server:~$ sudo grep -n '<VirtualHost' /etc/apache2/sites-enabled/example.conf
1:<VirtualHost 127.0.0.1:8080>
Decisión: Enlazar explícitamente a 127.0.0.1 previene exposición accidental y elimina argumentos de “¿por qué UFW bloquea mi backend?”.
Nginx: proxye a Apache y establezca las cabeceras correctas
Un bloque de servidor Nginx mínimo y honesto luce así (conceptualmente):
proxy_pass http://127.0.0.1:8080;- Reenvíe
Hosty esquema víaX-Forwarded-Proto - Opcionalmente establezca
X-Forwarded-HostyX-Forwarded-Port
Decisión: La meta es que Apache/app pueda reconstruir la URL externa exactamente.
Patrón B: Solo Apache (80/443) y eliminar Nginx del camino
Si no está usando características de Nginx, no lo ejecute “solo porque sí”. Las pilas más simples son más fáciles de depurar a las 3 a.m.
Haga esto:
- Detenga y deshabilite Nginx.
- Asegúrese de que Apache se enlace a 80/443 y tenga los vhosts correctos.
Y no haga esto:
- Dejar Nginx habilitado “por si lo necesitamos después.” Así es como obtiene conflictos de puerto inesperados durante actualizaciones de paquetes.
Patrón C: Solo Nginx y eliminar Apache del camino
Si el “backend” es en realidad un app server moderno (Gunicorn, uWSGI, Node, etc.), puede que no necesite Apache en absoluto. Nginx puede hacer reverse proxy directo a la app y evitar el salto extra.
Decisión: Si su única razón para Apache es “vino con el tutorial”, borre ese tutorial de su cabeza y siga adelante.
Bucles de proxy e infierno de redirecciones: cómo detectarlos y arreglarlos
Hay dos grandes clases de bucles:
- Bucles de red/proxy: proxyar a sí mismo (mismo host/puerto), o a un nombre que resuelve de nuevo al listener del borde.
- Bucles de redirección: el backend sigue redirigiendo porque piensa que esquema/host/puerto difieren de lo que usó el cliente.
Tipo de bucle 1: Nginx se proxya a sí mismo
Síntoma típico: Las peticiones se cuelgan, la CPU sube, el error log de Nginx muestra timeouts de upstream, o ve muchas conexiones internas hacia sí mismo.
Causa raíz: proxy_pass http://127.0.0.1:80 mientras Nginx escucha en 80; o proxy_pass http://example.internal y DNS resuelve a la misma IP que Nginx escucha.
Solución: Asegure que el upstream sea un destino distinto: loopback en un puerto diferente, un socket Unix o un host diferente. Use IP:puerto explícito para evitar sorpresas de DNS.
Tipo de bucle 2: HTTP ↔ HTTPS ping-pong de redirecciones
Síntoma típico: El navegador dice “demasiadas redirecciones”, curl -I muestra alternancia Location: http:// y Location: https://.
Causa raíz: Una capa fuerza HTTPS, otra fuerza HTTP, o el backend fuerza HTTPS porque no entiende que está detrás de terminación TLS.
Solución: Elija un punto de enforcement. Normalmente: Nginx fuerza HTTP→HTTPS, el backend no. Enseñe al backend el esquema vía X-Forwarded-Proto y asegúrese de que la app confíe en él adecuadamente.
Tipo de bucle 3: Redirecciones de host canónico peleando con server_name
Síntoma típico: Solicita www, redirige al apex, luego vuelve a www.
Causa raíz: Dos configuraciones diferentes creen que ellas manejan la canonicalización: reescritura Nginx + ajuste de host canónico en la app.
Solución: Decida dónde vive el host canónico. Para la mayoría: hágalo en el borde (Nginx) y desactive redirecciones canónicas en la app a menos que sea necesario para enrutamiento multi-tenant.
Tipo de bucle 4: Rutas ACME/Let’s Encrypt mal dirigidas
Síntoma típico: Emisión de certificado falla, y el endpoint de challenge devuelve una redirección o un 404 desde el servidor equivocado.
Causa raíz: Ambos servidores intentan servir /.well-known/acme-challenge/, o el proxy envía esa ruta a un backend que redirige a HTTPS cuando la CA espera HTTP simple.
Solución: Sirva el challenge ACME directamente en el vhost HTTP del edge y evite que el backend procese esa ruta.
Broma corta #2: Un bucle de redirección es la forma del web de decirle: “Te gustan los viajes de ida y vuelta”.
Tres micro-historias corporativas desde las trincheras
Micro-historia 1: El incidente causado por una suposición equivocada
Migraron un panel interno a Ubuntu 24.04 en una VM nueva. El ingeniero que hizo el corte instaló “un servidor web” por memoria muscular. Apache vino primero. Más tarde ese día, alguien añadió Nginx porque el equipo de seguridad quería un perfil de cifrado TLS específico que usaban en otro lado. Nadie removió Apache, porque ¿por qué? No “molestaba nada”.
Durante la ventana de cambio, actualizaron DNS al nuevo VM. El tráfico llegó, y el panel cargaba… a veces. A la mitad del tiempo servía la página por defecto de Apache. La otra mitad, servía la app correcta. El equipo hizo lo que los equipos hacen bajo presión: reiniciaron servicios, limpiaron caches y culparon a la propagación DNS como si fuera un evento meteorológico.
La causa raíz fue dolorosamente aburrida. Nginx estaba enlazado a 80/443, pero Apache también estaba habilitado y configurado para 80/443. Apache no pudo enlazar, así que falló. Sin embargo, el equipo de la app siguió probando vía un port-forward local que alcanzaba el puerto interno de Apache desde una configuración previa, y asumieron que el tráfico de producción seguía el mismo camino. No fue así.
La “solución” fue decidir una topología y aplicarla: Nginx como edge, Apache en 127.0.0.1:8080. Luego hicieron que el chequeo de salud apunte al endpoint edge, no al backend. El incidente terminó no con heroísmos, sino con un entendimiento compartido de que las suposiciones no son arquitectura.
Micro-historia 2: La optimización que salió mal
Otra compañía tenía una app PHP heredada detrás de Apache. Alguien quería mejor rendimiento y leyó que Nginx es “más rápido”. Pusieron Nginx delante de Apache como reverse proxy. Razonable. Luego intentaron optimizar más: habilitaron caching agresivo y añadieron reglas de rewrite para forzar HTTPS y host canónico en ambas capas “por seguridad”.
En horas, los usuarios no pudieron iniciar sesión. Las sesiones se perdían, y la app redirigía aleatoriamente entre HTTP y HTTPS. El monitoreo mostró un aumento de respuestas 301 y una tasa sospechosamente baja de 200s. El cache de Nginx estaba orgullosamente sirviendo redirecciones cacheadas. Ese es un tipo especial de autogol: toma un error de configuración y lo hace más rápido.
El debugging tomó más tiempo del que debía porque el equipo miraba el comportamiento en el navegador. Una vez que cambiaron a curl -v y compararon cabeceras de edge vs backend, fue obvio. El rewrite de Apache forzaba HTTPS porque no sabía que TLS se terminaba en Nginx. Nginx también forzaba HTTPS, pero con una regla de host canónico ligeramente distinta. El cliente rebotaba entre ellos.
La solución fue eliminar la mitad de la sofisticación. La aplicación de HTTPS quedó en Nginx únicamente. Apache dejó de redirigir y en su lugar confió en X-Forwarded-Proto para la lógica de esquema donde era necesario. Las reglas de cache excluyeron redirecciones y cualquier ruta autenticada. El rendimiento mejoró, pero más importante, el comportamiento se estabilizó. La lección: optimizar un malentendido solo produce respuestas incorrectas más rápido.
Micro-historia 3: La práctica aburrida pero correcta que salvó el día
Un equipo fintech corrió Nginx como edge y Apache como backend para un puñado de herramientas internas más antiguas. Nada emocionante. La práctica que importó fue su “contrato de propiedad de puertos”: un pequeño runbook interno que decía, en lenguaje claro, qué servicio se enlaza a qué puertos en cada clase de host.
Una tarde, una actualización de paquete trajo Apache a un host que previamente solo tenía Nginx. La actualización habilitó Apache por defecto. En el reboot, Apache intentó agarrar el puerto 80 antes de que Nginx arrancara. Nginx falló en enlazar, y el host sirvió la página por defecto de Apache. Eso podría haber sido un outage desordenado.
Pero los chequeos de monitoreo se construyeron desde el mismo contrato. No solo comprobaban “puerto 80 abierto”. Comprobaban que la respuesta edge contuviera una cabecera esperada y que el certificado del servidor coincidiera con el vhost previsto. La alerta fue específica: “edge identity mismatch”, no “sitio caído”.
El on-call siguió el runbook: comprobar listeners, deshabilitar Apache, recargar Nginx, confirmar con curl. El tiempo total de recuperación fue corto, no porque fueran genios, sino porque escribieron la verdad aburrida y el monitoreo la verificó.
Errores comunes: síntomas → causa raíz → solución
Esta sección es deliberadamente específica. Si ve un síntoma, debería poder saltar a una causa probable y a una corrección concreta.
1) Apache no arranca: “Address already in use: AH00072”
- Síntomas:
systemctl status apache2muestra fallado; el journal muestra error de bind en 0.0.0.0:80 o :443. - Causa raíz: Nginx (u otro servicio) ya posee el puerto; o systemd socket activation lo está reteniendo.
- Solución: Decida quién posee 80/443. Si Nginx es edge, mueva Apache a 127.0.0.1:8080 y actualice vhosts. Si Apache es edge, detenga/deshabilite Nginx.
2) Nginx arranca, pero obtiene 502 Bad Gateway
- Síntomas: Nginx devuelve 502; el error log muestra
connect() failed (111: Connection refused)oupstream timed out. - Causa raíz: El upstream no está escuchando donde Nginx espera; puerto incorrecto; Apache caído; firewall irrelevante si es loopback pero binding no lo es.
- Solución:
curlal upstream directamente (127.0.0.1:8080). Arregle el binding y estado del servicio Apache. Luego corrijaproxy_passen Nginx.
3) Navegador: “Demasiadas redirecciones”
- Síntomas: 301/302 alternantes;
curl -ImuestraLocationrebotando. - Causa raíz: Enforcement de HTTPS o reglas de host canónico duplicadas entre capas; el backend no conoce el esquema original.
- Solución: Haga enforcement en un solo lugar (edge). Reenvíe
X-Forwarded-Proto. Elimine reglas de redirección en el backend o condúzcalas en base al forwarded scheme.
4) Nginx se proxya a sí mismo (bucle silencioso)
- Síntomas: Las peticiones se cuelgan; alta CPU de workers; logs de Nginx muestran timeouts de upstream; el upstream es el mismo host:puerto.
- Causa raíz:
proxy_passapunta a 127.0.0.1:80 mientras Nginx escucha en 80, o apunta a un hostname que resuelve al mismo listener. - Solución: Cambie el upstream a un puerto/IP backend distinto. Prefiera loopback IP:puerto o socket Unix para upstreams locales.
5) Sitio equivocado servido (página por defecto) en lugar de su app
- Síntomas: Ve “Apache2 Ubuntu Default Page” o la página de bienvenida de Nginx inesperadamente.
- Causa raíz: El sitio por defecto está habilitado y es el vhost por defecto; mismatch de server_name; mismatch SNI en 443.
- Solución: Deshabilite sitios por defecto que no use. Haga que su host previsto coincida con
server_namey marquedefault_serverdeliberadamente si es necesario.
6) HTTP funciona, HTTPS falla (o viceversa)
- Síntomas: Un esquema funciona; el otro devuelve 404, bucles 301, o backend equivocado.
- Causa raíz: Diferentes vhost/server blocks para 80 vs 443 no sincronizados; vhost TLS que proxya a un upstream distinto; mismatch de certificado vhost.
- Solución: Asegure que ambos esquemas enruten de forma consistente. Use HTTP solo para redirigir a HTTPS en el edge, luego unifique el ruteo en el bloque HTTPS.
Listas de verificación / plan paso a paso
Checklist 1: Decida su topología (deje de negociar con el universo)
- ¿Necesita características de Nginx (ajuste HTTP/2, caching, ruteo simple, auth a nivel edge, rate limiting)? Si sí, hágalo edge.
- ¿Necesita características de Apache (comportamiento heredado .htaccess, dependencias mod_* existentes)? Si sí, manténgalo como backend.
- Si no necesita ninguno, ejecute un solo servidor. Prefiera menos partes móviles.
Checklist 2: Nginx edge + Apache backend (separación limpia)
- Asegúrese de que Nginx escuche en
0.0.0.0:80y0.0.0.0:443. - Mueva Apache a
127.0.0.1:8080editando/etc/apache2/ports.confy vhosts. - En Nginx, establezca:
proxy_set_header Host $host;proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;proxy_set_header X-Forwarded-Proto $scheme;
- Elimine redirecciones HTTPS en el backend que usen
%{HTTPS}a menos que se reescriban para respetar el forwarded proto. - Pruebe el backend directamente con curl a 127.0.0.1:8080, luego pruebe vía Nginx HTTPS.
- Recargue servicios de forma segura:
nginx -tluegosystemctl reload nginx;apachectl configtestluegosystemctl reload apache2.
Checklist 3: Solo Apache (sencillo y aburrido)
- Detenga y deshabilite Nginx:
systemctl stop nginx,systemctl disable nginx. - Asegure que Apache escuche en 80/443 y tenga vhosts correctos.
- Confirme con
ss -ltnpque Apache posee los puertos. - Ejecute
apachectl -Sy confirme que el vhost esperado sea el por defecto para cada puerto.
Checklist 4: Solo Nginx (backend de app moderno)
- Detenga y deshabilite Apache si no se usa.
- Proxye al servidor de app en puerto/socket separado.
- Mantenga el enforcement de redirecciones en Nginx únicamente.
- Asegúrese de que los logs estén configurados para que pueda depurar fallos upstream sin adivinar.
Preguntas frecuentes
1) ¿Pueden Apache y Nginx escuchar ambos en el puerto 80 si uno enlaza a IPv6 y el otro a IPv4?
A veces, pero está comprando un caso límite extraño. Las reglas de binding dual-stack pueden hacer que esto parezca funcionar hasta que deje de hacerlo. Elija un propietario para 80/443.
2) ¿Debería usar 8080 o 8000 para el backend de Apache?
Use cualquier puerto alto no ocupado, pero sea consistente. 8080 es convencional, lo que ayuda a futuros humanos. Enlácelo a 127.0.0.1 a menos que necesite acceso remoto.
3) ¿Por qué mi app sigue redirigiendo a HTTP aunque los clientes usen HTTPS?
Porque el backend ve una conexión HTTP simple desde el proxy y no sabe que el cliente usó HTTPS. Arregle reenviando X-Forwarded-Proto y configure la app/framework para confiar en las cabeceras del proxy.
4) ¿Cuál es la forma más rápida de probar un bucle de self-proxy?
Mire proxy_pass y compárelo con los sockets que escuchan. Si Nginx escucha en :80 y proxya a 127.0.0.1:80, ese es el bucle. También vigile los logs de error de Nginx para timeouts de upstream mientras el upstream es “sí mismo”.
5) ¿Por qué importa deshabilitar el sitio por defecto de Nginx?
Porque la selección de vhost por defecto es determinista pero no siempre intuitiva bajo presión. Dejar un sitio por defecto habilitado aumenta la probabilidad de que un hostname o petición por IP inesperada aterrice en contenido equivocado.
6) ¿Está bien hacer proxy a un hostname en lugar de 127.0.0.1?
Puede ser, pero es más arriesgado. Cambios de DNS, setups split-horizon y hacks en /etc/hosts pueden convertir “backend” en “puerta de entrada” otra vez. Para backends locales, prefiera el loopback explícito.
7) ¿Necesito abrir el puerto backend en UFW?
No, si el backend se enlaza a 127.0.0.1. Manténgalo privado. Si se enlaza a 0.0.0.0, estará obligado a gestionar reglas de firewall y eventualmente olvidará una.
8) ¿Por qué veo cabeceras de Apache aunque Nginx esté delante?
Porque el backend establece cabeceras Server: y Nginx las pasa por defecto. Si le importan, puede ocultarlas o sobrescribirlas, pero no confunda cabeceras con quién posee el socket.
9) ¿Qué pasa con HTTP/2, HTTP/3 y ajustes TLS?
Esos pertenecen al edge. Si Nginx termina TLS, afine TLS y HTTP/2 allí. Mantenga el backend simple: HTTP/1.1 estable sobre loopback está bien y es fácil de depurar.
10) ¿Cómo evito que esto vuelva a ocurrir después de actualizaciones de paquetes?
Deshabilite servicios no usados y monitorice identidad, no solo disponibilidad. Confirme que el servidor correcto responde en el puerto correcto con las cabeceras/certificados correctos. “Puerto abierto” es una barra baja.
Conclusión: siguientes pasos que puede hacer hoy
La “confusión” Apache vs Nginx en Ubuntu 24.04 por lo general no es un misterio. Son dos servicios intentando ser lo mismo, en los mismos puertos, con ideas descoordinadas sobre esquema y host. Arréglelo siendo decisivo y verificando los primitivos aburridos: listeners, upstreams, cabeceras, redirecciones.
- Ejecute
ss -ltnpy anote quién posee 80/443. - Elija una topología: solo Nginx, solo Apache o Nginx edge + Apache backend.
- Si hace proxy, haga que los upstreams sean inequívocos (127.0.0.1:8080) y reenvíe
X-Forwarded-Proto. - Elimine la lógica duplicada de redirecciones entre capas; aplique HTTPS una sola vez.
- Ciérrelo: deshabilite sitios por defecto y servicios no usados para que las actualizaciones no puedan “reavivarlos”.