Códigos HTTP: chuleta completa (1xx-5xx) con ejemplos
Abres DevTools y la pestaña Network está medio en rojo. Tu endpoint devuelve 502 en producción, 200 en local, y un compañero acaba de preguntar en Slack: «¿esto debería ser un 401 o un 403?». Los códigos HTTP parecen sencillos (tres dígitos, cinco categorías), pero la elección equivocada filtra información, rompe el SEO y vuelve insufribles los turnos de guardia.
Esta guía es una chuleta completa de códigos HTTP pensada para desarrolladores que están en el ruedo. Recibes tres cosas: (1) una tabla de consulta rápida con todos los códigos que aparecen de verdad en producción, (2) matrices de decisión para los pares que más se confunden (301 vs 302, 401 vs 403, 404 vs 410, 502 vs 504), y (3) una sección de herramientas que muestra cómo inspeccionar códigos de estado con curl, fetch y Python requests. Cada código que verás abajo está respaldado por la RFC 9110, el estándar vigente de semántica HTTP, y por el Registro IANA de Códigos HTTP.
Consulta rápida: todos los códigos HTTP de un vistazo
Estos son los códigos que te vas a encontrar en producción, agrupados por clase. Guarda esta tabla en favoritos: el resto del artículo se dedica a los más complicados.
| Código | Nombre | Cuándo lo verás |
|---|---|---|
| 100 | Continue | Envías un cuerpo POST grande con Expect: 100-continue |
| 101 | Switching Protocols | Handshake de WebSocket, upgrade a HTTP/2 |
| 103 | Early Hints | El servidor envía cabeceras Link antes de la respuesta real |
| 200 | OK | Éxito por defecto en GET, PUT, PATCH |
| 201 | Created | POST que crea un recurso (devuelve Location) |
| 202 | Accepted | Trabajo asíncrono encolado, aún no completado |
| 204 | No Content | DELETE exitoso, PUT sin cuerpo de respuesta |
| 206 | Partial Content | Range request, búsqueda en video, descarga reanudable |
| 301 | Moved Permanently | URL antigua retirada, los buscadores transfieren autoridad |
| 302 | Found | Redirección temporal, la URL original sigue siendo canónica |
| 303 | See Other | Patrón Post/Redirect/Get tras un POST de formulario |
| 304 | Not Modified | GET condicional con ETag o If-Modified-Since que coincide |
| 307 | Temporary Redirect | Como 302, pero conserva método y cuerpo |
| 308 | Permanent Redirect | Como 301, pero conserva método y cuerpo |
| 400 | Bad Request | JSON mal formado, falta un campo obligatorio, falla de esquema |
| 401 | Unauthorized | Sin credenciales o token caducado |
| 403 | Forbidden | Autenticado pero sin permiso |
| 404 | Not Found | El recurso no existe (o lo estás ocultando) |
| 405 | Method Not Allowed | POST a un endpoint solo de GET (debe incluir Allow) |
| 408 | Request Timeout | El cliente tardó demasiado en enviar la petición |
| 409 | Conflict | Falla de bloqueo optimista, clave duplicada |
| 410 | Gone | Recurso eliminado de forma permanente, no volverá |
| 415 | Unsupported Media Type | Content-Type incorrecto, p. ej. XML a una API JSON |
| 422 | Unprocessable Content | Sintaxis válida, semántica inválida (error de validación) |
| 425 | Too Early | Riesgo de replay con early-data en TLS 1.3 |
| 428 | Precondition Required | El servidor exige If-Match para evitar pisar cambios |
| 429 | Too Many Requests | Tasa limitada (debe incluir Retry-After) |
| 451 | Unavailable for Legal Reasons | DMCA, retiro por GDPR, geo-bloqueo |
| 500 | Internal Server Error | Excepción no controlada en tu código |
| 501 | Not Implemented | Método o función no soportada (raro en REST) |
| 502 | Bad Gateway | El upstream devolvió una respuesta inválida |
| 503 | Service Unavailable | Modo mantenimiento o sobrecarga |
| 504 | Gateway Timeout | El upstream no respondió a tiempo |
| 507 | Insufficient Storage | WebDAV se quedó sin disco |
| 508 | Loop Detected | Redirección infinita o recursión en WebDAV |
| 511 | Network Authentication Required | Portal cautivo en el WiFi de un hotel o aeropuerto |
El resto del artículo desmenuza cada clase con matrices de decisión, antipatrones y las consecuencias SEO de equivocarse.
Cómo funcionan los códigos HTTP (anatomía de 3 dígitos)
¿Por qué tres dígitos?
Los códigos HTTP usan tres dígitos decimales porque HTTP/0.9 necesitaba una señal de ancho fijo lo bastante pequeña para que un parser pudiera ramificar rápido y lo bastante grande para dejar espacio a códigos nuevos. Tres dígitos te dan 900 valores posibles (100–999), más que suficientes: el registro IANA usa apenas unos 60 hoy en día.
El primer dígito es la clase. El segundo y el tercero son el código específico dentro de esa clase. Un cliente que no reconozca 418 debería tratarlo como un 4xx genérico. La RFC 9110 §15 lo deja explícito: los clientes deben tratar los códigos no reconocidos como el x00 de su clase.
Las cinco categorías de un vistazo
| Clase | Significado | ¿Requiere cuerpo? | ¿Cacheable por defecto? |
|---|---|---|---|
1xx | Informativo: provisional, viene más | No | No |
2xx | Éxito: la petición fue entendida y aceptada | A menudo | Depende del método |
3xx | Redirección: hace falta otra acción | Opcional | 301, 308 sí; 302, 307 no |
4xx | Error del cliente: tu culpa, corrige la petición | Sí (explica) | Por lo general no |
5xx | Error del servidor: nuestra culpa, reintentar puede ayudar | Sí (explica) | No |
La columna “cacheable por defecto” importa. Las CDN y los navegadores cachean 301 y 308 con agresividad y para siempre: elegir el código de redirección equivocado en producción es difícil de revertir porque los usuarios tienen la redirección guardada en su caché. Volveremos a esto en la sección de SEO.
Si quieres profundizar en la estructura de las URL (que es sobre lo que operan los códigos de redirección), la guía Codificación y Decodificación de URL recorre la codificación porcentual, las query strings y el pipeline a nivel de bytes que determina qué hace válida a una URL.
1xx — Informativos (cuándo los verás de verdad)
La mayoría de desarrolladores pasa años sin ver un 1xx directamente. Son respuestas provisionales: el servidor le está diciendo al cliente «sigo aquí, tú continúa». Las DevTools del navegador suelen ocultarlas, y casi todas las librerías HTTP las colapsan dentro de la respuesta final.
Para cada código que viene a continuación, la referencia de códigos HTTP de MDN es la consulta cruzada más amigable si quieres una segunda mirada sobre alguna definición.
100 Continue
El cliente envía Expect: 100-continue en sus cabeceras y espera antes de transmitir un cuerpo de petición grande. El servidor responde 100 Continue si está dispuesto a aceptar el cuerpo, o un 4xx si va a rechazar la petición de todos modos. Esto ahorra ancho de banda en subidas grandes: no tiene sentido enviar 200 MB si el servidor los va a rechazar por una cabecera faltante.
curl -v -H "Expect: 100-continue" \
-H "Content-Type: application/octet-stream" \
--data-binary @big-file.bin \
https://api.example.com/upload
Si no ves < HTTP/1.1 100 Continue en la salida verbosa, lo más probable es que tu cliente eliminara la cabecera o que el servidor no la soporte.
101 Switching Protocols
El handshake que convierte una conexión HTTP en una conexión WebSocket o HTTP/2. El cliente envía Upgrade: websocket, el servidor responde 101 Switching Protocols, y desde ese momento la conexión habla un protocolo distinto. Lo verás en la pestaña Network de cualquier app de chat, dashboard en vivo o herramienta de colaboración.
103 Early Hints
Un código relativamente nuevo (RFC 8297, 2017) que permite al servidor mandar cabeceras Link con pistas de precarga antes de que la respuesta principal esté lista. El navegador empieza a descargar CSS y JS mientras el servidor todavía está renderizando. A día de 2026, Cloudflare, Fastly y Vercel ya soportan 103 en producción: es la alternativa moderna al server push de HTTP/2 (que Chrome dejó obsoleto).
HTTP/1.1 103 Early Hints
Link: </styles.css>; rel=preload; as=style
Link: </app.js>; rel=preload; as=script
HTTP/1.1 200 OK
Content-Type: text/html
...
Verificación de antipatrón. Si tu cliente nunca ve códigos 1xx cuando los esperas, el problema casi siempre es un proxy inverso. Las versiones antiguas de nginx eliminan Expect: 100-continue y 103 Early Hints. Revisa la configuración de tu proxy antes de asumir que el servidor está roto.
2xx — Éxito (más allá del simple 200)
Devolver 200 OK para todo es el code-smell más común en las APIs REST. La familia 2xx carga información semántica que vuelve más inteligentes a los clientes y más eficientes a los cachés.
200 OK
El predeterminado. Un GET devuelve el recurso, un PUT devuelve el recurso actualizado (o 204), un PATCH devuelve el recurso parcheado. Si no tienes una razón para usar un código más específico, usa 200.
201 Created
Un POST que crea un recurso nuevo debería devolver 201 más una cabecera Location apuntando al recurso recién creado. Así descubren los clientes RESTful la URL canónica de aquello que acaban de crear.
HTTP/1.1 201 Created
Location: /api/users/42
Content-Type: application/json
{"id": 42, "name": "Ada Lovelace"}
202 Accepted
El servidor aceptó la petición pero no terminó de procesarla. Úsalo para trabajo asíncrono: el cliente debería hacer polling, suscribirse a un webhook o consultar un endpoint de estado. Acompáñalo con un job ID en el cuerpo.
204 No Content
Éxito sin cuerpo. Habitual en DELETE (el recurso ya no existe, ¿qué devolverías?) y en operaciones PUT donde el cliente ya conoce el nuevo estado. Los navegadores no cambian de página si una submisión de formulario devuelve 204: útil para acciones tipo “dispara y olvida” en aplicaciones de una sola página.
206 Partial Content
Se devuelve para range requests: el cliente pidió los bytes 1000-2000 con una cabecera Range: bytes=1000-2000, y el servidor respondió justo con esa porción. El streaming de video, las descargas reanudables y el sync de archivos por HTTP dependen todos de 206.
Decisión: 200 vs 201 vs 204 para POST
| Escenario | Código | Cuerpo |
|---|---|---|
| POST crea un recurso nuevo | 201 Created | Recurso nuevo (o solo el ID) + Location |
| POST dispara trabajo asíncrono, resultado no listo | 202 Accepted | Job ID, URL de polling |
POST es una acción sin recurso (p. ej. /login) | 200 OK | Resultado de la acción (token, estado) |
| POST tiene éxito pero la respuesta va vacía | 204 No Content | (ninguno) |
Si no sabes decidir entre 200 y 201, pregúntate: «¿el servidor creó un recurso que ahora tiene su propia URL?». Si la respuesta es sí, 201. Si no, 200.
3xx — Redirección (301 vs 302 vs 307 vs 308)
Las redirecciones son la clase peor usada. Las diferencias entre 301, 302, 307 y 308 se reducen a tres preguntas independientes: ¿es permanente el cambio?, ¿se conserva el método?, ¿es cacheable la respuesta?
301 Moved Permanently
El recurso se movió y no va a volver. Los buscadores transfieren la autoridad de enlaces a la URL nueva. Navegadores y CDN cachean 301 indefinidamente: si rediriges /old a /new con un 301 y luego cambias de idea, los usuarios con la redirección en caché seguirán yendo a /new para siempre (o hasta que vacíen la caché).
Históricamente, los navegadores podían reescribir el método de la petición ante un 301 (POST → GET); por eso HTTP/1.1 introdujo 308, para arreglarlo.
302 Found
Redirección temporal. La URL original sigue siendo la canónica: los buscadores deberían seguir indexándola. Úsala para enrutado de pruebas A/B, páginas de mantenimiento o flujos de “inicia sesión para continuar”.
Igual que con 301, los navegadores reescribían históricamente POST a GET ante un 302. Si necesitas redirigir un POST y que siga siendo POST, usa 307.
303 See Other
Siempre reescribe el método a GET. El patrón Post/Redirect/Get: el formulario hace POST a /submit, el servidor responde 303 con Location: /thank-you, el navegador hace GET /thank-you. Recargar la página de agradecimiento no reenvía el formulario. Para esto se diseñó 303.
304 Not Modified
La respuesta condicional. El cliente envía If-None-Match: "abc123" (o If-Modified-Since), el servidor revisa si el recurso cambió, y si no, devuelve 304 sin cuerpo. El navegador usa su copia cacheada. Así mantienen ágil tu sitio cada CDN y cada capa de caché.
307 Temporary Redirect
Como 302, pero el método no debe cambiar. POST sigue siendo POST, el cuerpo se conserva. Úsalo cuando quieras una redirección temporal sobre una petición que no es GET.
308 Permanent Redirect
Como 301, pero el método no debe cambiar. La elección moderna y más segura para redirecciones permanentes en APIs que aceptan POST/PUT.
Matriz de decisión: ¿qué código de redirección?
| Permanente (cachea para siempre) | Temporal (no cachear) | |
|---|---|---|
| El método puede cambiar a GET | 301 Moved Permanently | 302 Found |
| El método debe permanecer igual | 308 Permanent Redirect | 307 Temporary Redirect |
Caso especial: si específicamente quieres POST → GET (el patrón Post/Redirect/Get), usa 303 See Other.
Para páginas HTML con navegación de navegador, 301 y 302 suelen valer porque GET es GET. Para APIs y formularios, prefiere 308 y 307 para evitar reescrituras de método inesperadas.
4xx — Errores del cliente (eligiendo el correcto)
4xx significa que el cliente hizo algo mal. Cuanto más rico sea tu vocabulario 4xx, más fácil será usar tu API: los clientes pueden ramificar por el código en lugar de parsear cadenas de error.
400 Bad Request
Error genérico de sintaxis. JSON mal formado, falta un campo obligatorio a nivel estructural, una petición que el servidor ni siquiera puede parsear. Si la petición se parsea pero falla la validación de negocio, prefiere 422.
401 Unauthorized vs 403 Forbidden
El par más confundido de HTTP. La distinción es simple en cuanto la ves:
401 Unauthorized— la petición no tiene autenticación válida. El servidor no sabe quién eres. Reenviar las credenciales (o refrescar el token) podría arreglarlo. La respuesta debe incluir una cabeceraWWW-Authenticatesegún la RFC 9110 §15.5.2.403 Forbidden— el servidor sabe quién eres y aun así se niega. Reenviar la petición no servirá. Hacen falta otras credenciales u otros permisos.
| Lo que ves | Lo que está pasando |
|---|---|
401 con WWW-Authenticate: Bearer | Sin token, token caducado o token inválido |
403 después de un login exitoso | Estás logueado, pero este usuario no puede acceder a este recurso |
401 después de un login exitoso | Bug: probablemente quieres 403 |
Antipatrón: 403 disfrazado de 404. Algunos sitios devuelven 403 cuando un usuario no autenticado pide /admin/dashboard. Eso filtra la existencia de /admin/dashboard. GitHub lo resuelve devolviendo 404 para repos privados de los que no eres miembro: el recurso «no existe» desde tu perspectiva. Es una decisión deliberada de ocultar información, no un bug.
404 Not Found vs 410 Gone
Ambos dicen “este recurso no está aquí”. La diferencia es la permanencia y el SEO.
404 Not Found— puede que exista, puede que no, puede que vuelva. Los buscadores seguirán comprobando.410 Gone— estaba aquí, se eliminó a propósito, no volverá. Los buscadores la sacan del índice mucho más rápido.
Si borras una página de producto y quieres que salga del índice de Google ya, 410 es la decisión correcta. Si una URL solo está rota temporalmente, 404 está bien.
405 Method Not Allowed
La URL existe pero no acepta este método. La respuesta debe incluir una cabecera Allow listando los métodos soportados.
HTTP/1.1 405 Method Not Allowed
Allow: GET, HEAD, OPTIONS
Content-Type: application/json
{"error": "POST is not allowed on this endpoint"}
Olvidarse de la cabecera Allow es la violación de contrato número uno en APIs REST hechas a mano.
408 Request Timeout
El cliente empezó a enviar una petición y luego se quedó callado. El servidor se cansó de esperar. Distinto de 504 Gateway Timeout, que tiene que ver con el upstream: 408 es «tú, el cliente, tardaste demasiado».
409 Conflict
La petición entra en conflicto con el estado actual. Uso más común: bloqueo optimista. El cliente envía If-Match: "etag-v3" y el ETag actual del servidor es "etag-v4", así que la actualización se rechaza con 409.
410 Gone
Ver más arriba: borrado permanente. Útil para sacar de los índices de búsqueda registros con borrado lógico.
415 Unsupported Media Type
El cliente envió un cuerpo que el servidor no entiende. Hacer POST de XML a una API que solo habla JSON da 415. La respuesta debería sugerir los tipos aceptables.
422 Unprocessable Content
La petición se parsea bien, pero falla la validación semántica. La RFC 9110 por fin lo promovió de WebDAV al núcleo del estándar en 2022. Usa 422 para errores de validación:
{
"error": "validation_failed",
"details": [
{"field": "email", "message": "must be a valid email"},
{"field": "age", "message": "must be at least 13"}
]
}
Si tu API duda entre 400 y 422, la regla práctica es: 400 para «ni siquiera puedo parsear esto», 422 para «lo parseé y no tiene sentido».
425 Too Early
Se envía cuando el servidor no quiere arriesgarse a procesar una petición que podría ser un replay de early-data de TLS 1.3. Sobre todo relevante para CDN y proxies inversos.
428 Precondition Required
El servidor exige que envíes If-Match o If-Unmodified-Since para evitar el problema del lost update. Se usa en APIs de edición colaborativa.
429 Too Many Requests
Tasa limitada. La respuesta debe incluir Retry-After (en segundos, o como fecha HTTP) para que los clientes bien educados puedan hacer back-off.
HTTP/1.1 429 Too Many Requests
Retry-After: 30
Content-Type: application/json
{"error": "rate_limited", "limit": 100, "window": "1m"}
451 Unavailable for Legal Reasons
El número es un guiño a Bradbury. El caso de uso no es ficción: retiros por DMCA, eliminaciones por derecho al olvido del GDPR y geo-bloqueos a nivel de país justifican un 451. La respuesta debería incluir una cabecera Link apuntando a la autoridad legal que exige el bloqueo, según la RFC 7725.
418 I’m a Teapot (el huevo de pascua)
Sí, existe de verdad. La RFC 2324 (April Fools de 1998) y la IETF lo dejaron en los libros porque demasiados productos lo implementaron en broma. No envíes 418 en una API real: la mayoría de proxies inversos y balanceadores de carga lo manejan mal.
Matriz de decisión: ¿qué 4xx?
| Situación | Código |
|---|---|
| El cuerpo está mal formado o no se puede parsear | 400 |
| Sin autenticación / token caducado | 401 |
| Autenticado pero sin permiso | 403 |
| La URL no existe (o la estás ocultando) | 404 |
| La URL existía, se eliminó a propósito | 410 |
| Método HTTP equivocado | 405 (con Allow) |
Content-Type equivocado | 415 |
| Conflicto de bloqueo optimista | 409 |
| Error de validación (parsea, no valida) | 422 |
| Tasa limitada | 429 (con Retry-After) |
| Bloqueado por motivos legales | 451 |
5xx — Errores del servidor (qué está roto de verdad)
5xx es «culpa nuestra». A los ingenieros de guardia les importa sobre todo qué 5xx los despertó a las 3 AM, porque el código te dice qué capa investigar primero.
500 Internal Server Error
El comodín. Casi siempre significa que una excepción no controlada llegó hasta el handler por defecto del framework. No te dice nada de la causa: por eso el logging estructurado importa más que el código de estado en este caso.
501 Not Implemented
El servidor no soporta el método en absoluto. Distinto de 405 (este método no está permitido para esta URL): 501 dice «este servidor no tiene ni idea de qué significa PROPFIND». Raro en APIs REST.
502 Bad Gateway
Un proxy inverso o balanceador de carga recibió una respuesta inválida del upstream. El upstream respondió, pero con basura: protocolo equivocado, cabeceras mal formadas, conexión caída a mitad de respuesta. Si ves 502 desde tu CDN, lo más probable es que el origen esté crasheando o devolviendo cuerpos truncados.
503 Service Unavailable
El servidor no está atendiendo peticiones a propósito ahora mismo. Úsalo para ventanas de mantenimiento o respuestas elegantes ante sobrecarga. Debería incluir Retry-After.
504 Gateway Timeout
El proxy inverso esperó al upstream y el upstream nunca respondió a tiempo. El upstream está lento o atascado: distinto de 502, donde el upstream respondió con basura.
502 vs 504: el diagnóstico de guardia
| Lo que ves | Primera cosa a revisar |
|---|---|
502 Bad Gateway | El upstream responde con datos inválidos: revisa los logs del origen en busca de crashes, respuestas mal formadas o desajustes de protocolo |
504 Gateway Timeout | El upstream se cuelga: revisa CPU del origen, queries de BD, llamadas a APIs aguas abajo y el proxy_read_timeout del proxy |
Una confusión típica: una query de base de datos que tarda 60 segundos saldrá como 504 si tu proxy hace timeout a los 30 segundos, pero como 500 si el servidor de aplicación hace timeout a los 90 segundos y lanza una excepción. Misma causa raíz, código distinto, línea de log distinta: entrena tus dashboards para que muestren ambos.
507 Insufficient Storage
Específico de WebDAV. Disco lleno en el servidor. Si lo ves desde una API que no es WebDAV, alguien le está estirando el significado.
508 Loop Detected
Recursión infinita en operaciones PROPFIND de WebDAV. Muy raro.
511 Network Authentication Required
Códigos de portal cautivo: el WiFi de un hotel o aeropuerto envía 511 para decirle a tu navegador «primero tienes que iniciar sesión en el portal». La respuesta incluye un Location apuntando a la página del portal.
Matriz de troubleshooting: qué capa revisar primero
| Código | App | Proxy | BD | Red |
|---|---|---|---|---|
500 | Sí | — | Quizá (error de BD no capturado) | — |
502 | — | Sí (upstream mal formado) | — | Quizá (TCP reset) |
503 | Sí (flag de mantenimiento) | Sí (rechazo por rate limit) | — | — |
504 | Sí (handler lento) | Sí (config de timeout) | Sí (query lenta) | Sí (DNS, pérdida de paquetes) |
Antipatrones comunes con códigos HTTP
Estos cinco errores explican la mayor parte del código malo que reviso.
1. Envolver errores en 200 OK
HTTP/1.1 200 OK
{"success": false, "error": "user_not_found"}
Cada herramienta de monitoreo, CDN y caché ahora cree que la petición tuvo éxito. La lógica de reintentos falla. Los balanceadores que miran códigos de estado enrutan tráfico defectuoso a backends «sanos». Este patrón viene de JSON-RPC y lo heredó GraphQL (GraphQL lo hace porque los éxitos parciales necesitan reportes de error por campo, lo cual es razonable). REST no tiene excusa: usa 4xx para errores de cliente, 5xx para errores de servidor, y deja el detalle estructurado en el cuerpo.
2. Mezclar 401 y 403
Si tu 401 y tu 403 no son consistentes, los atacantes pueden sondear tu API para descubrir qué recursos existen. Adopta una política: o devuelves 404 para «no puedes ver esto» (el enfoque de GitHub para repos privados) o devuelves 403 de manera consistente. La inconsistencia filtra información.
3. Esconder 403 detrás de 404
A veces es correcto, a menudo es un bug. Que GitHub devuelva 404 para repos privados es deliberado: la propia existencia del repo es información sensible. Pero si tu API devuelve 404 para «esta cuenta de usuario está suspendida», los usuarios legítimos ya no pueden distinguir si tecleó mal el nombre de usuario o si lo suspendieron. Documenta tu política de forma explícita y aplícala con consistencia.
4. Usar 500 como atrapatodo por defecto
Los frameworks lo facilitan, y ese es el problema. Cada excepción no capturada se convierte en 500 y tu sistema de alertas no puede distinguir entre «la base de datos cayó» y «el usuario pasó un UUID mal formado». Captura los errores de validación y lanza 400 o 422. Captura NotFound desde tu ORM y lanza 404. Reserva 500 para fallos verdaderamente inesperados, y cuando lo lances, registra un request ID para poder correlacionar.
5. Cadenas de redirecciones largas
Cada salto cuesta una ida y vuelta. Si /old → /intermediate → /canonical, eso son dos resoluciones DNS extra y dos handshakes TCP extra (en el peor caso). Google específicamente baja la prioridad de crawl para cadenas de más de 3 saltos, y los navegadores limitan las cadenas de redirección a unos 20 para evitar bucles. Colapsa las cadenas en el origen: en la configuración de tu CDN o en el mapa de redirecciones de tu aplicación.
Códigos HTTP y SEO
Los buscadores tratan los códigos de estado como señales autoritativas para decidir si conservan, descartan o transfieren una URL. Equivócate y tu posicionamiento se mueve.
301 vs 302 (autoridad de enlaces)
301 Moved Permanently transfiere PageRank: Google trata la nueva URL como destino canónico de todas las señales que apuntaban a la antigua. 302 Found no transfiere autoridad de enlaces (o la transfiere despacio, según las heurísticas de Google). Si renombraste una URL para siempre, usa 301. Si rediriges a un invitado a /login, usa 302.
404 vs 410 vs Soft 404
Google distingue tres estados de “no encontrado”:
404 Not Found— Google revisa periódicamente y mantiene la URL en el índice un tiempo.410 Gone— Google la elimina más rápido, a menudo en un solo ciclo de crawl.- Soft 404 — el término de Google para una página que devuelve
200 OKpero muestra un mensaje de «no encontrado». Google lo detecta a partir de patrones de contenido y lo trata como un404de todos modos, pero ya gastaste una petición de crawl y posiblemente diluiste tu contenido real.
Si estás limpiando un índice obsoleto, devuelve 410 reales para las URL eliminadas de forma permanente.
5xx y crawl budget
El crawler de Google reduce su ritmo cuando un sitio devuelve 5xx persistentes. El reporte de Crawl Stats de Search Console lo muestra: un pico sostenido de errores 5xx puede tirar abajo tu crawl budget durante días, lo que significa que las páginas nuevas tardan más en indexarse. Trata las tasas de 5xx como una métrica SEO, no solo de fiabilidad.
200 OK que en realidad está roto
Devolver 200 OK con una página de error (el antipatrón soft-404) es lo peor que le puede pasar a tu SEO. Google indexa el mensaje de error, lo posiciona para nada, y poco a poco descubre que la página está rota. Devuelve siempre el código de estado correcto desde el servidor, aunque tu single-page app renderice una UI de error amigable.
Cómo inspeccionar códigos HTTP (herramientas)
No puedes arreglar lo que no puedes ver. Toda persona desarrolladora en activo debería dominar al menos tres de estas.
Panel Network de DevTools del navegador
Chrome, Firefox y Safari muestran una columna Status en la pestaña Network. Haz clic derecho en la cabecera de la columna para añadir Status Text si no está visible. Trucos útiles:
- Preserve log — conserva las entradas entre navegaciones para que puedas ver la cadena completa de redirecciones.
- Filtrar por estado — escribe
status-code:5xx(Chrome) para ver solo errores de servidor. - Replay XHR — clic derecho sobre cualquier petición → Replay XHR para reejecutarla sin recargar la página.
Para redirecciones, expande la petición y verás cada salto y el código de estado en cada paso.
curl (la respuesta universal)
curl lo muestra todo. Tres patrones que resuelven el 90% del debugging:
# Solo el código de estado
curl -o /dev/null -s -w "%{http_code}\n" https://api.example.com/users/1
# Solo cabeceras (petición HEAD, siguiendo redirecciones)
curl -I -L https://example.com
# Verboso completo con cabeceras de petición y respuesta
curl -v https://api.example.com/users/1
Cuando estés armando URL de prueba con caracteres especiales en query strings, usa --data-urlencode para que curl se encargue de la codificación, o pega la URL en nuestro Decodificador y Codificador URL para verificar qué bytes van a viajar de verdad por la red.
# curl te codifica el valor del query
curl -G "https://api.example.com/search" \
--data-urlencode "q=hello world & friends"
# Sends: GET /search?q=hello%20world%20%26%20friends
fetch en JavaScript
La propiedad Response.status tiene el código entero. Response.ok es true para cualquier 2xx.
const res = await fetch('https://api.example.com/users/1');
console.log(res.status); // 200
console.log(res.statusText); // "OK"
console.log(res.ok); // true
if (!res.ok) {
if (res.status === 401) {
// refresh token and retry
} else if (res.status === 429) {
const retryAfter = Number(res.headers.get('Retry-After')) || 1;
await new Promise(r => setTimeout(r, retryAfter * 1000));
} else if (res.status >= 500) {
throw new Error(`Server error: ${res.status}`);
}
}
En axios, la misma lógica vive en los interceptors:
import axios from 'axios';
axios.interceptors.response.use(
response => response,
error => {
const status = error.response?.status;
if (status === 401) {
// redirect to login
}
return Promise.reject(error);
}
);
Python requests
import requests
r = requests.get('https://api.example.com/users/1')
print(r.status_code) # 200
print(r.reason) # 'OK'
# Raises requests.exceptions.HTTPError for 4xx/5xx
r.raise_for_status()
# Manual handling
if r.status_code == 429:
retry_after = int(r.headers.get('Retry-After', '1'))
time.sleep(retry_after)
elif 500 <= r.status_code < 600:
raise RuntimeError(f'Server error: {r.status_code}')
raise_for_status() es el modismo de Python para “falla a gritos ante 4xx/5xx”. Úsalo en scripts donde quieras excepciones ante errores en lugar de ramificar por status_code.
Postman y Bruno
Ambos te permiten hacer asserts sobre códigos de estado dentro de un script de test:
// Postman/Bruno test script
pm.test("Status is 201", () => {
pm.response.to.have.status(201);
});
pm.test("Has Location header", () => {
pm.expect(pm.response.headers.get('Location')).to.match(/^\/users\/\d+$/);
});
Ejecuta esto contra staging en CI para cazar violaciones de contrato antes de producción.
Preguntas frecuentes
¿Cuál es la diferencia entre 401 y 403?
401 Unauthorized significa que el servidor no sabe quién eres: tus credenciales faltan, caducaron o son inválidas. 403 Forbidden significa que el servidor sabe quién eres y aun así se niega. Si enviar credenciales distintas podría arreglarlo, usa 401. Si no, usa 403.
¿Cuándo debo usar 301 vs 302?
Usa 301 cuando el cambio sea permanente: la URL antigua nunca volverá y quieres que los buscadores transfieran la autoridad de enlaces a la nueva URL. Usa 302 para redirecciones temporales donde la URL original sigue siendo la canónica (flujos de login, pruebas A/B, páginas de mantenimiento). Para APIs, prefiere 308 y 307 porque conservan el método de la petición.
¿Qué significa un error 502 Bad Gateway?
502 significa que un proxy inverso o balanceador de carga recibió una respuesta inválida del servidor upstream. El upstream respondió, pero con basura: protocolo equivocado, cabeceras mal formadas o conexión caída. Es distinto de 504 Gateway Timeout, donde el upstream no respondió en absoluto. Primer sitio donde mirar: los logs del servidor de origen, en busca de crashes o respuestas truncadas.
¿Qué es un “soft 404”?
Un “soft 404” es una página que devuelve 200 OK pero en realidad muestra un mensaje de «no encontrado». Google los detecta heurísticamente y los trata como 404 igualmente. Desperdician crawl budget y pueden diluir tu contenido real. Devuelve siempre códigos de estado 404 o 410 reales desde el servidor, aunque tu single-page app renderice una UI de error amigable.
¿Cuándo debo usar 422 en lugar de 400?
Usa 400 Bad Request cuando el servidor ni siquiera puede parsear la petición: JSON mal formado, faltan campos estructurales, errores de sintaxis. Usa 422 Unprocessable Content cuando la petición se parsea bien pero falla la validación de negocio: formato de email inválido, valor fuera de rango, campos semánticamente inconsistentes. La regla rápida: 400 para sintaxis, 422 para semántica.
¿Cómo respondo a un 429 Too Many Requests?
Lee la cabecera Retry-After (un número de segundos o una fecha HTTP) y haz back-off durante al menos ese tiempo antes de reintentar. Si falta Retry-After, usa exponential backoff con jitter empezando alrededor de 1 segundo. Nunca reintentes de inmediato: así te ganas un baneo.
¿Los códigos informativos 1xx siguen usándose en 2026?
Sí, pero la mayoría son invisibles para el código de aplicación. 100 Continue y 101 Switching Protocols son funciones básicas de HTTP/1.1. Cloudflare, Fastly y Vercel cada vez usan más 103 Early Hints para enviar pistas de precarga antes de la respuesta principal: mejora notablemente el Largest Contentful Paint. La mayoría de librerías HTTP colapsan los 1xx dentro de la respuesta final, así que típicamente solo los ves en DevTools o con curl -v.
¿Es real el código 418 “I’m a teapot”?
Sí, por sorprendente que parezca. La RFC 2324 fue una broma de April Fools de 1998, pero suficientes productos lo implementaron como para que la IETF lo conservara en los libros con la RFC 7168. No envíes 418 en producción: muchos proxies inversos y balanceadores de carga no lo manejan correctamente, y no sirve a ningún propósito real fuera del chiste.