SSD Nodes Learn 🎉 VPS desde $4.99/mes
Guías Matt ConnorPor Matt Connor · Actualizado 2026-08-07

HTTP explicado para administradores de servidores

Aprende HTTP desde el servidor: métodos, códigos de estado, cabeceras clave, registros de nginx y cómo encajan TLS y HTTP/3 en cada solicitud.

¿Qué es HTTP?

HTTP (protocolo de transferencia de hipertexto) es el conjunto de reglas que un cliente y un servidor web usan para solicitar algo y devolverlo. El cliente envía una solicitud: un método como GET, una ruta como /pricing, una versión del protocolo, una lista de cabeceras y, en algunos casos, un cuerpo. El servidor responde con un código de estado como 200, seguido de sus propias cabeceras y, normalmente, un cuerpo. Cada visita a una página y cada llamada a una API (interfaz de programación de aplicaciones) en el servidor es ese intercambio, repetido.

HTTP no mantiene estado por sí mismo. El servidor no recuerda lo que solicitó hace un segundo, por lo que cualquier comportamiento que actúe como memoria, como una sesión de inicio de sesión, se transporta en una cabecera en cada solicitud. Esta propiedad explica gran parte de lo que sigue: la caché depende completamente de las cabeceras y un equilibrador de carga puede enviar la siguiente solicitud a otro backend sin que nada deje de funcionar.

Todo lo que se explica a continuación muestra cómo funciona este modelo desde el lado del servidor, en el registro de acceso y en la configuración de nginx.

Una solicitud y una respuesta sin procesar, con anotaciones

Esta es una solicitud HTTP/1.1 completa. Una línea en blanco termina las cabeceras. Todo lo que aparece después de esa línea es el cuerpo. Un GET normalmente no tiene cuerpo.

GET /pricing HTTP/1.1
Host: example.com
User-Agent: curl/8.5.0
Accept: */*
Accept-Encoding: gzip
  • GET es el método. Indica la operación que se solicita. GET lee, POST envía datos, PUT reemplaza, DELETE elimina y HEAD solicita las cabeceras de un GET sin su cuerpo.
  • /pricing es la ruta. El nombre de host no forma parte de la línea de solicitud. Por eso existe la siguiente cabecera.
  • HTTP/1.1 es la versión del protocolo que utiliza el cliente.
  • Host: example.com identifica el sitio que solicita el cliente. HTTP/1.1 lo exige. Por eso nginx responde a una solicitud sin esta cabecera con 400 Bad Request.
  • El resto son preferencias. Accept-Encoding: gzip indica que el cliente puede descomprimir contenido. Por tanto, el servidor puede comprimir el cuerpo.

La respuesta tiene la misma estructura, con una línea de estado al principio.

HTTP/1.1 200 OK
Date: Thu, 06 Aug 2026 09:12:44 GMT
Server: nginx
Content-Type: text/html; charset=utf-8
Content-Length: 5310
Cache-Control: public, max-age=300

<!doctype html>...
  • 200 OK es el código de estado con su frase descriptiva. El código es lo importante. La frase es decorativa y los clientes la ignoran.
  • Content-Type indica al cliente cómo debe tratar los bytes siguientes.
  • Content-Length es el tamaño del cuerpo en bytes. Así, el cliente sabe dónde termina el cuerpo. Si el tamaño no se conoce de antemano, el servidor envía Transfer-Encoding: chunked y marca el final con un bloque de longitud cero.
  • Cache-Control indica al navegador y a cualquier caché intermedia cuánto tiempo pueden conservar esta respuesta.
  • La línea en blanco posterior a las cabeceras las separa del cuerpo en ambas direcciones.

Los nombres de las cabeceras no distinguen mayúsculas de minúsculas. Además, cada línea termina con un retorno de carro seguido de un avance de línea, no con un salto de línea aislado. No escribirá estos caracteres manualmente, pero los encontrará en una captura de paquetes.

Para observar un par real, ejecute esto contra un sitio que administre:

curl -sS -o /dev/null -D - https://example.com/

-D - escribe las cabeceras de respuesta en el terminal y -o /dev/null descarta el cuerpo. Prefiera esta opción a curl -I, porque -I envía una solicitud HEAD. Un servidor de aplicaciones que trata HEAD de forma diferente a GET, como ocurre con muchos servidores, mostrará entonces cabeceras que ningún navegador recibe. curl -v imprime ambos lados, con las líneas de solicitud marcadas con > y las líneas de respuesta marcadas con <.

Cómo es la línea de solicitud en el registro de acceso de nginx

nginx incluye un formato de registro combined, cuya definición es la siguiente:

log_format combined '$remote_addr - $remote_user [$time_local] '
                    '"$request" $status $body_bytes_sent '
                    '"$http_referer" "$http_user_agent"';

Una línea generada por este formato:

203.0.113.45 - - [06/Aug/2026:09:12:44 +0000] "GET /pricing HTTP/1.1" 200 5310 "https://example.com/" "Mozilla/5.0 (X11; Linux x86_64) Chrome/127.0.0.0 Safari/537.36"
  • 203.0.113.45 es $remote_addr, la dirección que abrió la conexión TCP (protocolo de control de transmisión). Detrás de un proxy, es la dirección del proxy, no la del visitante.
  • El primer - es un marcador de posición fijo. El segundo es $remote_user, que sólo se completa cuando se usa autenticación básica HTTP.
  • "GET /pricing HTTP/1.1" es $request, la línea de solicitud copiada exactamente como llegó.
  • 200 es el estado que devolvió el servidor, no el estado que percibió el visitante.
  • 5310 es $body_bytes_sent, sólo el cuerpo. Las cabeceras de respuesta no se contabilizan, por lo que este número siempre es menor que el número de bytes enviados realmente.
  • Los dos últimos campos entre comillas son Referer y User-Agent. Ambos proceden del cliente, por lo que pueden contener cualquier dato.

Como $request se copia literalmente, la basura también aparece literalmente. Un cliente que habla TLS (seguridad de la capa de transporte) con el puerto 80, que usa texto sin cifrar, deja una línea 400 cuyo campo de solicitud empieza con bytes escapados como "\x16\x03\x01\x02\x00\x01". \x16 es el tipo de registro del handshake TLS, por lo que esos bytes son el inicio de un ClientHello y no de una línea de solicitud. El servidor funciona correctamente. Algo está dirigiendo HTTPS a un puerto HTTP.

Añada también $server_protocol al formato de registro. Muestra HTTP/1.1, HTTP/2.0 o HTTP/3.0, y es la forma más rápida de demostrar que un cambio de protocolo se aplicó realmente.

Qué significan los códigos de estado habituales cuando los devuelve su propio sitio

El primer dígito indica la clase, y eso es lo primero que debe leer.

2xx significa que la operación se realizó correctamente. 200 OK para una lectura normal. 201 Created después de un POST que creó algo. 204 No Content para una operación correcta sin nada que devolver, que es la respuesta habitual a un DELETE.

3xx significa que debe buscar en otro lugar. 301 es permanente y los navegadores lo almacenan en caché durante mucho tiempo, a veces hasta que el usuario borra su perfil, por lo que un 301 que apunte al nombre de host equivocado es difícil de deshacer. Use 302 mientras siga probando una redirección. 304 Not Modified indica una operación correcta, no un error: el cliente envió If-None-Match con un ETag (etiqueta de entidad) que todavía reconoce, por lo que respondió con cabeceras y sin cuerpo. Un registro lleno de 304s significa que la caché funciona.

4xx significa que la solicitud era incorrecta. 400 Bad Request indica una entrada con formato incorrecto. 401 Unauthorized significa realmente que el cliente no está autenticado y debe incluir una cabecera WWW-Authenticate que indique el esquema. 403 Forbidden significa que la solicitud se entendió, pero se rechazó de todos modos. 404 Not Found es una ruta que no existe. 405 Method Not Allowed es la ruta correcta con el método incorrecto, que es lo que devuelve un POST a una ubicación de archivo estático. 413 indica un cuerpo mayor que client_max_body_size de nginx, cuyo valor predeterminado es 1 megabyte; el registro de errores lo confirma con client intended to send too large body.

Un 403 en un archivo estático casi siempre indica un problema del sistema de archivos, no una regla HTTP. Consulte /var/log/nginx/error.log antes de cambiar la configuración. open() "/srv/site/index.html" failed (13: Permission denied) significa que el usuario de trabajo de nginx no puede leer el archivo, normalmente porque un directorio principal no tiene permiso de ejecución para otros usuarios. directory index of "/srv/site/" is forbidden significa que la ruta se resolvió en un directorio sin archivo de índice mientras autoindex está desactivado.

5xx significa que falló su lado. 500 es un error no gestionado en la aplicación. 502 Bad Gateway significa que nginx no pudo obtener una respuesta utilizable del upstream, y el registro de errores indica la causa: connect() failed (111: Connection refused) while connecting to upstream significa que no hay ningún proceso escuchando en la dirección de proxy_pass. 504 Gateway Timeout significa que el upstream aceptó la conexión y después no respondió dentro de proxy_read_timeout, cuyo valor predeterminado es 60 segundos; el registro lo muestra como upstream timed out (110: Connection timed out) while reading response header from upstream. 503 Service Unavailable es un rechazo deliberado. Tenga en cuenta que el limitador de tasa propio de nginx devuelve 503, porque limit_req_status tiene el valor predeterminado 503. Si busca 429 Too Many Requests en el registro y encuentra 503 en su lugar, esa es la razón. Configure limit_req_status 429; para obtener el código correcto.

Las cabeceras importantes al ejecutar el servidor

Host selecciona el sitio. Una dirección IP puede servir cientos de nombres de host, y nginx compara Host con server_name para decidir qué bloque server responde. Si no hay ninguna coincidencia, nginx usa el servidor predeterminado. Es el primer bloque que escucha en esa dirección y puerto, salvo que otro esté marcado como default_server. Si un host virtual nuevo devuelve el sitio equivocado, casi siempre se debe a esto: el nombre no coincidió y la petición terminó en el servidor predeterminado. Pruébelo sin modificar DNS:

curl -sS -o /dev/null -D - -H 'Host: app.example.com' http://127.0.0.1/

User-Agent es una descripción del cliente escrita por el propio cliente y su contenido es texto libre. Úselo como indicio al revisar los registros. No lo use como mecanismo de control, porque un cliente que quiera falsificarlo simplemente lo hará. Por tanto, bloquear un scraper mediante User-Agent sólo filtra a los que se identifican correctamente.

Content-Type determina cómo se interpretan los bytes: application/json para una petición de API y text/html; charset=utf-8 para una página. nginx asigna tipos a las extensiones de archivo mediante /etc/nginx/mime.types, y el nginx.conf incluido en el paquete establece default_type application/octet-stream;. Por eso, un archivo cuya extensión nginx no conoce se ofrece como descarga en lugar de mostrarse. El síntoma visible es una página que carga sin estilos mientras la consola del navegador muestra Refused to apply style from ... because its MIME type ('text/plain') is not a supported stylesheet MIME type. MIME significa multipurpose internet mail extensions, el esquema de nombres del que proceden esas cadenas de tipos.

Cache-Control permite controlar todas las cachés entre el servidor y el lector. public, max-age=31536000, immutable es adecuado para recursos cuyo nombre contiene un hash del contenido, porque el nombre cambia cuando cambia el contenido. no-store debe usarse para cualquier contenido específico de un usuario, porque una caché compartida que conserve una página con la sesión iniciada se la entregará a la siguiente persona que solicite la misma URL. private es la configuración intermedia: el navegador puede conservar el contenido, pero una caché compartida no.

X-Forwarded-For existe porque un proxy oculta la dirección del visitante. Cuando una petición pasa por un reverse proxy, $remote_addr es la dirección del proxy. Por tanto, los registros, la geolocalización y los límites de tasa identifican siempre al mismo cliente. El proxy debe reenviar la dirección original:

proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;

Después, hay que indicar al servidor receptor que confíe en esa dirección y especificar exactamente en quién debe confiar:

set_real_ip_from 10.0.0.0/8;
real_ip_header X-Forwarded-For;

Enumere sólo los rangos que controle. X-Forwarded-For es texto sin validación que cualquier cliente puede enviar, por lo que set_real_ip_from 0.0.0.0/0; permite al visitante elegir la dirección que se registra y la dirección que cuenta para el límite de tasa.

X-Forwarded-Proto evita un fallo concreto y muy habitual. El proxy termina TLS y reenvía la petición a la aplicación mediante HTTP sin cifrar. La aplicación recibe una petición sin cifrar, decide que el visitante debe usar HTTPS y responde con 301 https://example.com/. El navegador sigue la redirección, el proxy vuelve a terminar TLS y reenvía otra vez HTTP sin cifrar. El ciclo se repite hasta que el navegador abandona con ERR_TOO_MANY_REDIRECTS. Enviar X-Forwarded-Proto: https indica a la aplicación que el visitante ya usa HTTPS, por lo que deja de redirigirlo.

HTTP/1.1 frente a HTTP/2 frente a HTTP/3: qué cambia para usted

HTTP/1.1 usa texto y gestiona una petición cada vez por conexión. Connection: keep-alive permite que la siguiente petición reutilice la misma conexión TCP, lo que ahorra el coste de establecerla, pero las respuestas siguen llegando en el orden en que se solicitaron. Una respuesta lenta bloquea todo lo que espera detrás. Esto se denomina bloqueo de cabecera de línea, y los navegadores lo evitan abriendo varias conexiones al mismo nombre de host a la vez.

HTTP/2 mantiene los mismos métodos y códigos de estado, pero cambia el formato de las tramas a binario. Muchas peticiones comparten una conexión como streams independientes, y el texto de las cabeceras repetidas se comprime. Esto es importante porque una petición moderna contiene mucho texto de cabecera. La conexión sigue usando TCP, por lo que un paquete perdido detiene todos los streams de esa conexión hasta que llega la retransmisión. El bloqueo de cabecera de línea no desapareció. Bajó de HTTP a la capa de transporte. Server push formaba parte de HTTP/2, pero en la práctica ya no se usa porque Chrome eliminó su compatibilidad en 2022.

HTTP/3 mantiene de nuevo la misma semántica y sustituye TCP por QUIC, un protocolo de transporte basado en UDP (user datagram protocol). Los streams de QUIC son independientes hasta la capa inferior, por lo que un paquete perdido sólo detiene el stream al que pertenecía. TLS 1.3 está integrado en el handshake de QUIC en lugar de estar superpuesto, por lo que una conexión nueva necesita menos intercambios. De esto se derivan dos consecuencias prácticas: el puerto UDP 443 debe estar abierto en todos los firewalls del trayecto, y cualquier red que limite o bloquee UDP hará que los clientes vuelvan a HTTP/2.

Qué cambia para usted, en concreto. Los navegadores nunca comienzan con HTTP/3. Se conectan mediante HTTP/2 o HTTP/1.1, ven una cabecera Alt-Svc: h3=":443"; ma=86400 en la respuesta y usan HTTP/3 en las conexiones posteriores con ese host. Por tanto, la cabecera no es un adorno opcional. Es el mecanismo de descubrimiento. En nginx, HTTP/2 pasó a tener su propia directiva en la versión 1.25.1 (http2 on; dentro del bloque server, que sustituye al antiguo parámetro listen ... http2), y QUIC llegó a la rama mainline 1.25.0. En ella, un sitio HTTP/3 necesita listen 443 quic reuseport; junto con el listen 443 ssl; ordinario.

Los proxies tienen distintos niveles de madurez en este aspecto, y conviene comprobarlo según la versión que realmente ejecute. En agosto de 2026, Caddy sirve HTTP/3 de forma predeterminada sin configuración adicional. nginx necesita el listener quic explícito y la cabecera Alt-Svc descrita arriba. Traefik lo habilita por entry point mediante una opción http3 explícita. Si termina TLS en Traefik delante de varias aplicaciones Docker, la versión del protocolo que reciben sus visitantes se decide allí, y el salto del proxy al contenedor suele ser HTTP/1.1 normal, independientemente de lo que haya negociado el navegador.

Verifíquelo en lugar de darlo por supuesto. curl --http3 -sS -o /dev/null -D - https://example.com/ sólo funciona si curl -V incluye HTTP3 entre sus funciones, y la mayoría de las compilaciones de las distribuciones no lo incluyen. La comprobación fiable es su propio registro: añada $server_protocol al formato y compruebe qué negocian los navegadores reales. Antes de todo eso, confirme que UDP 443 está realmente abierto, porque un firewall que sólo permita TCP 443 hará que HTTP/3 falle silenciosamente mientras el sitio sigue funcionando mediante HTTP/2. Saber qué puertos están abiertos y escuchando en su servidor Linux es lo primero que debe comprobar.

HTTPS: HTTP es el protocolo y TLS es la capa de protección

HTTPS no es un protocolo independiente. Son las mismas solicitudes y los mismos códigos de estado dentro de una sesión TLS. El puerto 80 las transporta sin cifrar y el puerto 443 las transporta cifradas. El handshake de TLS se completa primero. Después, la solicitud HTTP viaja dentro del canal cifrado. Por eso un problema de certificado nunca tiene asociado un código de estado: el fallo ocurre antes de que se envíe un solo byte HTTP, así que no existe ninguna respuesta que numerar.

En un servidor que aloja varios sitios, importa un detalle del orden. El certificado se selecciona mediante SNI (indicación del nombre del servidor), un campo del handshake de TLS que transporta el nombre de host sin cifrar antes de que exista cualquier cabecera HTTP. Por tanto, el servidor selecciona primero un certificado a partir de SNI y después selecciona un host virtual a partir de la cabecera Host. Son dos búsquedas independientes que normalmente coinciden. Cuando no coinciden, el navegador muestra un error de nombre como NET::ERR_CERT_COMMON_NAME_INVALID y no envía ninguna solicitud, porque se ofreció el certificado del servidor predeterminado para un nombre que no cubre.

Para un sitio público, obtenga un certificado válido y configure su renovación automática. Certbot con Let's Encrypt en nginx escribe las rutas de los certificados en el bloque de servidor e instala el temporizador de renovación. Para un nombre de host que ninguna autoridad pública pueda validar, como un nombre interno o una dirección IP sin nombre en su propia red, un certificado autofirmado en Ubuntu es la opción adecuada, siempre que acepte que debe indicar a cada cliente que confíe en él.

Cuando TLS funcione, envíe todo lo que llegue al puerto 80 al puerto 443:

server {
    listen 80;
    server_name example.com;
    return 301 https://$host$request_uri;
}

Añada Strict-Transport-Security sólo cuando esté seguro. La cabecera add_header Strict-Transport-Security "max-age=63072000; includeSubDomains" always; indica a los navegadores que rechacen HTTP sin cifrar para ese nombre de host durante dos años. Los navegadores obedecen esta directiva desde su propia caché, por lo que eliminar después la cabecera no la revierte. Empiece con un max-age de unas horas. Confirme que todos los subdominios usan realmente HTTPS y después aumente el valor.

FAQ

¿Cuál es la diferencia entre HTTP y HTTPS?

HTTPS es HTTP transmitido dentro de una sesión TLS (seguridad de la capa de transporte). Los métodos y los códigos de estado son idénticos. Lo que cambia es que los bytes se cifran entre el cliente y el componente que termina TLS, y el puerto predeterminado cambia de 80 a 443. Como el handshake de TLS termina antes de enviar el primer byte HTTP, un fallo del certificado nunca produce un código de estado. Por eso, la advertencia de certificado del navegador muestra un nombre de error como NET::ERR_CERT_COMMON_NAME_INVALID en lugar de un número como 403.

¿Por qué mi sitio devuelve 502 Bad Gateway?

Un 502 de nginx significa que nginx no pudo obtener una respuesta utilizable del upstream al que hace proxy. Por tanto, la petición del visitante era correcta y el problema estaba detrás de nginx. Lea /var/log/nginx/error.log. connect() failed (111: Connection refused) while connecting to upstream significa que no hay ningún proceso escuchando en la dirección y el puerto de proxy_pass. Compruebe que la aplicación esté en ejecución y vinculada a la dirección esperada. no live upstreams while connecting to upstream significa que todos los servidores del bloque upstream se han marcado como inactivos después de varios fallos. Compárelo con 504 Gateway Timeout, que significa que el upstream aceptó la conexión y después no respondió dentro de proxy_read_timeout.

¿Por qué mi registro de acceso muestra la misma dirección IP para todos los visitantes?

Porque $remote_addr registra la dirección que abrió la conexión TCP. Detrás de un reverse proxy o de una red de distribución de contenido, esa dirección es la del proxy. La dirección del visitante llega en la cabecera X-Forwarded-For. Configure proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; en el proxy. Después, en el nginx receptor, configure set_real_ip_from con el rango de direcciones del proxy y real_ip_header X-Forwarded-For;. Incluya sólo rangos que controle, porque esa cabecera es texto que cualquier cliente puede enviar. Si confía en ella desde todo internet, un visitante podrá elegir la dirección que registra y la dirección que se usa para limitar la tasa.

¿Necesito activar HTTP/2 o HTTP/3?

Conviene activar HTTP/2. Es una directiva en un sitio que ya tiene TLS y elimina el límite de solicitudes por conexión que ralentiza las páginas con muchos archivos pequeños. HTTP/3 ofrece una mejora menor y menos segura, y requiere un puerto UDP 443 abierto y una compilación del proxy con compatibilidad con QUIC. Recuerde que los navegadores cambian a HTTP/3 sólo después de ver una cabecera Alt-Svc en una respuesta anterior. Sin esa cabecera, nada cambia, independientemente de lo que indique la línea listen. Añada $server_protocol al formato del registro y mida qué protocolo negocian realmente los visitantes antes de invertir tiempo en ello.

¿Qué significa 403 Forbidden cuando el archivo existe?

En un sitio estático, un 403 suele deberse a permisos del sistema de archivos y no a una regla HTTP. open() ... failed (13: Permission denied) en /var/log/nginx/error.log significa que el usuario de trabajo de nginx no puede leer el archivo. Lo más habitual es que a un directorio principal le falte el permiso de ejecución para otros usuarios, y no que el modo del archivo sea incorrecto. directory index of ... is forbidden significa que la petición se resolvió en un directorio sin archivo de índice, mientras autoindex está desactivado. Una regla deny explícita en el bloque location correspondiente también devuelve 403. Revise ese bloque cuando el registro de errores no muestre nada.

#http#https#web-server#headers#http3