// Engineering Log
HTTPS y servidor web en la práctica: Parte 3 — Qué es CORS: errores en la consola del navegador y configuración en Nginx
Publicado el 02.11.2026 · Autor: Mikhail Gorbachev
// Ruta rapida
Este articulo pertenece al tema Servidores e infraestructura.
CORS (Cross-Origin Resource Sharing) — mecanismo mediante el cual el servidor permite que el código JavaScript de otros sitios lea sus respuestas. Por defecto el navegador no lo permite: un script en https://app.example.ru puede enviar una solicitud a https://api.example.ru, pero no podrá leer la respuesta si el servidor del API no lo permite explícitamente. De ahí aparecen errores del tipo «blocked by CORS policy» en la consola, que surgen tan pronto como el frontend y el API están en direcciones distintas. A continuación — cómo funciona la comprobación, qué significa cada error típico y cómo configurar el servidor. Los propios encabezados HTTP están explicados brevemente en el artículo «Encabezados HTTP: cuáles existen y qué hacen».
Qué es el «origen»
El origen (origin) es esquema, host y puerto juntos. Dos direcciones pertenecen al mismo origen solo si las tres partes coinciden:
| Dirección de la página | Dirección de la solicitud | ¿Mismo origen? |
|---|---|---|
https://example.ru | https://example.ru/api | sí |
https://example.ru | https://api.example.ru | no — distinto host |
https://example.ru | http://example.ru | no — distinto esquema |
http://localhost:3000 | http://localhost:8000 | no — distinto puerto |
Un subdominio ya es otro origen. El desarrollo local con frontend en el puerto 3000 y API en 8000 también.
Qué es exactamente lo que bloquea el navegador
La política de mismo origen (same-origin policy) prohíbe que un script lea respuestas de orígenes ajenos. En la mayoría de los casos no prohíbe enviar solicitudes. CORS es la manera de relajar esta regla para orígenes concretos.
De aquí tres consecuencias importantes:
- CORS protege al usuario, no al servidor. curl, Postman, un script en Python u otro servidor no verifican CORS y siempre obtienen la respuesta. Restringir el acceso al API solo es posible con autenticación;
- Una solicitud «simple» desde un sitio tercero llega al servidor y se ejecuta — el navegador solo no entrega la respuesta al script. Lo que protege contra la falsificación de tales solicitudes no es CORS, sino tokens CSRF y
SameSiteen las cookies («Ataques a nivel HTTP»); - Un error de CORS es una respuesta del servidor sin los encabezados necesarios, no una falla de red. El servidor puede haber devuelto 200 o 500: en la pestaña Network se ve el código real.
Solicitudes simples y preflight
Un navegador envía una solicitud simple inmediatamente y comprueba los encabezados de la respuesta. Una solicitud se considera simple si simultáneamente:
- el método es GET, HEAD o POST;
- los encabezados son solo de una lista corta permitida (
Accept,Accept-Language,Content-Language,Content-Typey algunos otros), sinAuthorizationniX-…personalizados; Content-Typees soloapplication/x-www-form-urlencoded,multipart/form-dataotext/plain.
Cualquier otra solicitud — PUT, PATCH, DELETE, JSON con Content-Type: application/json, solicitud con Authorization — requiere una solicitud previa (preflight). El navegador primero envía un OPTIONS:
OPTIONS /v1/orders HTTP/1.1
Host: api.example.ru
Origin: https://app.example.ru
Access-Control-Request-Method: PUT
Access-Control-Request-Headers: authorization, content-typeEl servidor debe responder con un código 2xx y permisos:
HTTP/1.1 204 No Content
Access-Control-Allow-Origin: https://app.example.ru
Access-Control-Allow-Methods: GET, POST, PUT, PATCH, DELETE
Access-Control-Allow-Headers: Authorization, Content-Type
Access-Control-Max-Age: 7200
Vary: OriginSolo después de esto se envía el PUT real, y su respuesta también debe contener Access-Control-Allow-Origin. Access-Control-Max-Age permite al navegador cachear el resultado de la preflight; Chrome limita este tiempo a dos horas.
Encabezados de respuesta
Access-Control-Allow-Origin— qué origen puede leer la respuesta: concreto (https://app.example.ru) o cualquiera (*). Solo un valor: no se permite una lista separada por comas;Access-Control-Allow-Methods,Access-Control-Allow-Headers— métodos y encabezados permitidos, solo en la respuesta aOPTIONS;Access-Control-Allow-Credentials: true— permitir solicitudes con cookie yAuthorization;Access-Control-Expose-Headers— qué encabezados de la respuesta son accesibles para el script. Por defecto el script ve solo unos pocos básicos (Content-Type,Content-Length,Cache-Control, etc.); para leerX-Total-CountoContent-Dispositionhay que listarlos;Access-Control-Max-Age— cacheo de la preflight, en segundos;Vary: Origin— obligatorio siAllow-Origindepende delOriginde la solicitud. Si no, un CDN o el navegador cacheará la respuesta con permiso para un sitio y se la dará a otro.
Solicitudes con cookie y autenticación
Por defecto fetch a un origen ajeno no envía cookies. Para que lo haga:
fetch("https://api.example.ru/v1/me", { credentials: "include" });Entonces el servidor debe:
- devolver un origen concreto en
Access-Control-Allow-Origin—*está prohibido junto con credenciales; - devolver
Access-Control-Allow-Credentials: true; - la cookie debe tener
SameSiteadecuado: para otro sitio —SameSite=None; Secure. Para subdominios de un mismo sitio (app.example.ruyapi.example.ru— un «sitio» desde el punto de vista deSameSite) basta conLax.
Un token en el encabezado Authorization es más sencillo que la cookie: no hace falta credentials: "include", pero sí que Authorization aparezca en Access-Control-Allow-Headers.
Errores en la consola y qué significan
No 'Access-Control-Allow-Origin' header is present on the requested resource
El servidor no devolvió el encabezado. Razones: CORS no está configurado; está configurado, pero la respuesta vino con error (404, 500, 502) y los encabezados se añaden solo en respuestas exitosas — en Nginx falta always; la solicitud fue enviada a la dirección equivocada. Mire el código real en la pestaña Network.
Response to preflight request doesn't pass access control check: It does not have HTTP ok status
La respuesta a OPTIONS no es 2xx. Lo más habitual es que el OPTIONS requiera autenticación (401), no esté soportado por el framework (404, 405) o redirija. La preflight siempre va sin cookie ni Authorization — el servidor debe responder a ella sin comprobar el acceso.
Redirect is not allowed for a preflight request
El OPTIONS devolvió una redirección: de http a https, de con slash a sin slash (/v1/orders → /v1/orders/), a la página de login. Corrija la dirección en el frontend para evitar la redirección.
The value of the 'Access-Control-Allow-Origin' header in the response must not be the wildcard '*' when the request's credentials mode is 'include'
Solicitud con cookie, y en la respuesta *. Se necesita un origen concreto y Allow-Credentials: true.
Request header field authorization is not allowed by Access-Control-Allow-Headers in preflight response
El encabezado no está listado en Access-Control-Allow-Headers. Lo mismo para content-type al enviar JSON y para cualquier encabezado personalizado.
Method PUT is not allowed by Access-Control-Allow-Methods in preflight response
El método no está listado en Access-Control-Allow-Methods.
The 'Access-Control-Allow-Origin' header contains multiple values '…, …', but only one is allowed
El encabezado lo añaden dos capas a la vez — por ejemplo, Nginx y la aplicación, o la aplicación y un CDN. Deje CORS en un solo lugar.
La respuesta llega, pero response.headers.get("X-Total-Count") devuelve null
El encabezado no está listado en Access-Control-Expose-Headers.
Configuración en Nginx
Orígenes permitidos — en una lista con map, para no reflejar cualquier Origin:
map $http_origin $cors_origin {
default "";
"https://app.example.ru" $http_origin;
"https://admin.example.ru" $http_origin;
"~^http://localhost:\d+$" $http_origin; # solo para el entorno de pruebas
}
server {
listen 443 ssl;
server_name api.example.ru;
location / {
if ($request_method = OPTIONS) {
add_header Access-Control-Allow-Origin $cors_origin always;
add_header Access-Control-Allow-Credentials true always;
add_header Access-Control-Allow-Methods "GET, POST, PUT, PATCH, DELETE, OPTIONS" always;
add_header Access-Control-Allow-Headers "Authorization, Content-Type, X-Request-ID" always;
add_header Access-Control-Max-Age 7200 always;
add_header Vary Origin always;
return 204;
}
add_header Access-Control-Allow-Origin $cors_origin always;
add_header Access-Control-Allow-Credentials true always;
add_header Access-Control-Expose-Headers "X-Total-Count, Content-Disposition" always;
add_header Vary Origin always;
proxy_pass http://127.0.0.1:8080;
}
}Aclaraciones:
- El
ifconreturndentro delocation— uno de los usos permitidos deifen Nginx; - si el origen no está en la lista,
$cors_originqueda vacío, y Nginx no emite un encabezado con valor vacío — el navegador bloqueará la lectura de la respuesta; alwayses necesario para que los encabezados estén también en respuestas 4xx/5xx — de lo contrario, en lugar de un error comprensible del API el frontend verá un error de CORS;- si en
locationaparece otroadd_header, los encabezados a nivelserverdejarán de aplicarse en él — mantenga todos los encabezados en un solo lugar.
Configuración en la aplicación
Si CORS se configura en la aplicación, Nginx no añade encabezados en absoluto:
- Express: paquete
cors—app.use(cors({ origin: ["https://app.example.ru"], credentials: true })); - Django:
django-cors-headers—CORS_ALLOWED_ORIGINS = ["https://app.example.ru"],CORS_ALLOW_CREDENTIALS = True; - FastAPI:
CORSMiddlewareconallow_origins,allow_credentials,allow_methods,allow_headers; - Spring:
@CrossOrigino unCorsConfigurationSourceglobal.
El middleware de CORS en la aplicación debe estar antes de la autenticación, de lo contrario OPTIONS recibirá 401.
Cómo prescindir de CORS
La solución más simple es no hacer solicitudes cross-domain:
- un dominio para frontend y API:
https://example.ru/— el sitio,https://example.ru/api/— el API a través del mismo Nginx. El origen es el mismo, CORS no es necesario, las cookies funcionan sinSameSite=None; - proxy en desarrollo: en Vite —
server.proxy, en Create React App — el campoproxyenpackage.json. El frontend hace llamadas a/apien su propio puerto, y el servidor de desarrollo reenvía las solicitudes al API.
Cómo comprobar
La preflight se puede reproducir con curl:
curl -si -X OPTIONS https://api.example.ru/v1/orders \
-H 'Origin: https://app.example.ru' \
-H 'Access-Control-Request-Method: PUT' \
-H 'Access-Control-Request-Headers: authorization, content-type' \
| grep -i '^access-control\|^HTTP\|^vary'Y la principal — con el mismo Origin:
curl -si https://api.example.ru/v1/orders -H 'Origin: https://app.example.ru' | grep -i '^access-control'Con un origen que no esté en la lista no debe aparecer el encabezado Access-Control-Allow-Origin.
Errores típicos
Access-Control-Allow-Originse toma delOriginde la solicitud sin comprobarlo contra una lista — eso permite cualquier sitio, además con cookies;Access-Control-Allow-Origin: *en un API con autenticación por cookie — no funciona y revela que CORS se configuró a la ligera;- Falta
Vary: Originen una respuesta que depende deOrigin— el CDN sirve una respuesta con permiso para un origen ajeno; - CORS está configurado tanto en Nginx como en la aplicación — encabezado duplicado;
OPTIONSprotegido por autenticación o limitación de frecuencia — todo el frontend se rompe bajo cualquier carga;- Intentar «arreglar CORS» en el frontend (
mode: "no-cors") — la solicitud sale, pero la respuesta queda totalmente inaccesible para el script.
// Tarea parecida
Si estas resolviendo algo parecido
Este articulo pertenece a uno de los temas principales de trabajo. Puedes seguir leyendo sobre el tema, ir a la pagina principal para entender a que me dedico o abrir directamente los servicios.
Tema del articulo
Servidores e infraestructura
VPS, Linux, stack web, migraciones, hosting, bases de datos y operacion base.
Tareas frecuentes de esta tema
- Migrar un sitio o servicio a un nuevo servidor
- Configurar Linux, Nginx, base de datos y copias de seguridad
- Entender por que el sistema funciona de forma inestable
// Siguiente paso
Si necesitas ayuda con este tema y no solo otro articulo, es mejor ir directo a la pagina del servicio. La pagina principal y la seleccion de materiales quedan como rutas secundarias.
Abrir servicios// Reviews
Reseñas relacionadas
Gracias a Mijaíl por su disposición. Hablamos por teléfono; me explicó cómo hacerlo yo mismo. Es la segunda vez que recurro a él: todo genial y muy rápido.
Gracias a Михаил por su amabilidad. Hablamos por teléfono, me explicó cómo hacerlo por mi cuenta. Es la segunda vez que recurro, todo genial y rápido.
Quiero expresar mi enorme agradecimiento al especialista que me configuró las URLs amigables en OpenCart. Fue fácil y sencillo, y me alegra haber encontrado finalmente a un profesional que lo hizo todo con calidad y sin complicaciones. Antes cambié a cuatro especialistas y cada vez surgían problemas con la configuración, pero esta persona resolvió la tarea a la perfección.
Quiero expresar mi enorme agradecimiento al especialista que me configuró las URLs amigables en OpenCart. Configurar las URLs amigables resultó fácil y sencillo, y me alegra haber finalmente encontrado a un profesional …
Excelente trabajo, no es la primera vez que recurro; encuentra soluciones a problemas complejos. Lo recomiendo.
Excelente trabajo, recurro no por primera vez, encuentra soluciones a problemas complejos. Recomiendo.
¡Excelente trabajo! Cumplió con la tarea a tiempo y sin errores. Fue un placer colaborar, lo recomiendo.
¡Excelente trabajo! Completó la tarea encomendada a tiempo y sin errores. Fue un placer colaborar, lo recomiendo.
Excelente especialista, se adentró en el problema, lo entendió y lo solucionó. Lo recomiendo.
Excelente especialista, se involucró en el problema, lo entendió y lo solucionó. Recomiendo.
Corrección de la expresión regular en location de nginx
21.03.2024 · ★ 5/5
Había que resolver un problema con el certificado SSL en el servidor, que había sido emitido mediante Ngnix Proxy manager. Mijaíl aclaró todos los detalles de cómo tengo todo configurado, pidió accesos para evaluar la viabilidad de la solución, ya que antes no se había enfrentado a un servicio similar. Se familiarizó rápidamente y resolvió mi problema. Colaboración perfecta)
Necesitaba resolver un problema con el certificado SSL en el servidor, que fue emitido a través de Ngnix Proxy manager. Mikhail aclaró todos los detalles sobre cómo tengo todo organizado, pidió accesos para evaluar la …
Diagnóstico de Nginx Proxy Manager en un contenedor Docker y solución del problema
15.03.2024 · ★ 5/5
// Contact
¿Necesitas ayuda?
Escríbeme y te ayudaré a resolver el problema
Respondo en un día laborable (03:00-13:00 GMT)
O deje su solicitud aquí:
// Related