---
title: "Erro de CORS (blocked by CORS policy): como resolver"
description: "Um erro de CORS significa que o navegador não deixou a sua página ler a resposta de outra origem. Veja o que é blocked by CORS policy e como resolver."
url: https://proxynet.io/pt-br/blog/cors-error
date: 2026-10-06
author: "Acar Diveroli"
category: "Tutoriais, Web scraping"
lang: pt-BR
---

# Erro de CORS (blocked by CORS policy): como resolver

O seu front-end (um app React com Vite, por exemplo) roda em `http://localhost:5173`, e a sua API em `http://localhost:3001`. A API devolve JSON no curl e no Postman, mas no navegador o `fetch()` lança `TypeError: Failed to fetch` e o console mostra: `Access to fetch at 'http://localhost:3001/api/products' from origin 'http://localhost:5173' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.` Mesmo assim, o log da API mostra que a requisição chegou e foi respondida com `200`.

A seguir: o que é uma origem, por que o navegador bloqueia a resposta, requisições simples e requisições preflight, cada mensagem do Chrome, correções testadas para Express, Flask e Vite, uma rota no back-end para APIs que você não controla e por que código de scraping nunca esbarra no CORS.

> **Nota: Resposta rápida**
>
> Um erro de CORS significa que o navegador recebeu uma resposta de outra origem, mas não a entregou ao seu JavaScript, porque o servidor não enviou um cabeçalho `Access-Control-Allow-Origin` com a origem da sua página. Uma origem é o esquema, o host e a porta juntos, então `localhost:5173` e `localhost:3001` são origens diferentes. Corrija no servidor: permita a sua origem exata, responda às requisições preflight `OPTIONS` e liste os métodos e cabeçalhos que você usa. Em desenvolvimento, um proxy do servidor de desenvolvimento coloca a API na mesma origem; uma API que não é sua, chame pelo seu back-end. `mode: "no-cors"`, extensões de navegador, proxies CORS públicos, VPNs e proxies não resolvem.

## O que é um erro de CORS?

É o navegador se recusando a entregar uma resposta ao seu script. CORS, Cross-Origin Resource Sharing (compartilhamento de recursos entre origens), é um conjunto de cabeçalhos de resposta HTTP com os quais um servidor diz ao navegador quais outras origens podem ler as respostas dele. O [Fetch Standard](https://fetch.spec.whatwg.org/#http-cors-protocol) define esse mecanismo como uma permissão que o servidor precisa liberar de forma explícita, para que dados atrás de um firewall ou de um login não vazem para outros sites por padrão. Sem cabeçalhos que batam, o navegador aplica a regra padrão, a política de mesma origem (same-origin policy), e bloqueia a leitura.

Daí saem três consequências. Os logs do servidor parecem normais, porque quem gerou o erro foi o navegador. O seu código recebe só uma falha genérica, `TypeError: Failed to fetch` do `fetch()` ou `AxiosError: Network Error` (código `ERR_NETWORK`) do Axios, e o motivo aparece apenas no console. E a correção fica no servidor que responde, não no código que pergunta.

## O que é uma origem e o que a política de mesma origem bloqueia?

Uma origem (origin) é o esquema, o host e a porta de uma URL. Duas URLs têm a mesma origem só quando os três coincidem:

- `http://localhost:5173` e `http://localhost:3001`: portas diferentes, origens diferentes.
- `http://example.com` e `https://example.com`: esquemas diferentes.
- `https://example.com` e `https://api.example.com`: hosts diferentes; um subdomínio é uma origem própria.
- `https://example.com/shop` e `https://example.com/api`: mesma origem; o caminho não conta.

A política de mesma origem continua deixando uma página incorporar imagens, scripts e folhas de estilo de outros sites, criar links para eles e enviar formulários a eles. O que ela impede é a leitura: um script não pode ler uma resposta de outra origem, a menos que essa origem permita ([MDN: Same-origin policy](https://developer.mozilla.org/en-US/docs/Web/Security/Same-origin_policy)). O CORS é a forma de o servidor dar essa permissão.

## Como funciona uma requisição entre origens, passo a passo?

1. A sua página na origem A chama `fetch()` para uma URL na origem B.
2. O navegador verifica se a requisição é "simples". Se não for, ele envia antes um preflight (próxima seção).
3. Ele adiciona um cabeçalho `Origin`, como `Origin: http://localhost:5173`. Scripts não podem definir nem remover esse cabeçalho.
4. O servidor executa o código dele e responde. O que o código fez já aconteceu.
5. O navegador compara o `Access-Control-Allow-Origin` da resposta com a origem da página. Ele aceita uma correspondência exata, ou `*` quando nenhuma credencial, como cookies, foi enviada.
6. Se bater, o seu código recebe a resposta; se não, o navegador descarta a resposta, a promise é rejeitada e o console informa o motivo.

O passo 4 é o que as pessoas esquecem: o CORS não impede que as requisições cheguem ao servidor, e curl, scripts e outros servidores pulam o passo 5 por completo. Ele protege os visitantes, para que uma página que eles abrem não consiga ler os dados deles em outros sites usando os cookies deles; ele não protege a sua API.

## Requisições simples e preflight: por que o GET funciona e o POST falha

Uma requisição é "simples" quando usa `GET`, `HEAD` ou `POST` e só cabeçalhos da lista segura: `Accept`, `Accept-Language`, `Content-Language`, `Range` e `Content-Type` com `application/x-www-form-urlencoded`, `multipart/form-data` ou `text/plain`. Todo o resto passa antes por um preflight (verificação prévia): uma requisição `OPTIONS` com `Access-Control-Request-Method` e `Access-Control-Request-Headers`, depois da qual o navegador só envia a requisição real se a resposta permitir ([MDN: CORS](https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/CORS)). Um corpo JSON, um cabeçalho `Authorization`, `PUT`, `PATCH`, `DELETE` ou um cabeçalho personalizado como `X-Request-ID` disparam um preflight.

Enviamos um `GET` simples e um `POST` com corpo JSON de uma página na porta 5173 para uma API na porta 3001 que não envia cabeçalhos CORS. A API registrou:

```text
[no-cors-api] GET /api/products origin=http://localhost:5173
[no-cors-api] OPTIONS /api/products origin=http://localhost:5173
```

O `GET` foi respondido com `200` e os dados, e mesmo assim o navegador o bloqueou. Do `POST`, só o preflight chegou; ele falhou, então o `POST` nunca foi enviado. Já um `POST` simples entre origens, como um enviado em formato de formulário, chega ao servidor e é executado mesmo que a página veja um erro. Por isso, proteja essas ações com autenticação e tokens CSRF, não com CORS.

O `Access-Control-Max-Age` permite que o navegador guarde em cache a resposta de um preflight. O padrão do Fetch Standard é de 5 segundos; o Chromium limita o valor a 2 horas, e o Firefox a 24 horas.

## O que significa cada mensagem "blocked by CORS policy"?

Toda mensagem do Chrome começa com `Access to fetch at '<URL>' from origin '<origin>' has been blocked by CORS policy:`, ou com `Access to XMLHttpRequest at` no Axios e no `XMLHttpRequest`. O Chromium mantém esses textos fixos em inglês no código-fonte, por isso eles aparecem em inglês também em um navegador em português. O texto depois dos dois-pontos é a causa. As cinco primeiras linhas da tabela apareceram palavra por palavra no nosso teste com um navegador baseado no Chromium 152; as demais vêm do código-fonte do Chromium.

| Mensagem depois de "blocked by CORS policy:" | Significado | Correção |
|---|---|---|
| `No 'Access-Control-Allow-Origin' header is present on the requested resource.` | Falta o cabeçalho CORS: não foi configurado, ou uma página de erro respondeu | Permita a sua origem; confira o status real |
| `Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present…` | A resposta ao `OPTIONS` não tinha cabeçalho CORS; a requisição real não foi enviada | Responda ao `OPTIONS` com cabeçalhos CORS |
| `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'.` | Cookies enviados, o servidor respondeu `*` | Origem exata mais `Access-Control-Allow-Credentials: true` |
| `Request header field x-debug is not allowed by Access-Control-Allow-Headers in preflight response.` | Um cabeçalho que você envia não é permitido | Permita o cabeçalho ou pare de enviá-lo |
| `Method PATCH is not allowed by Access-Control-Allow-Methods in preflight response.` | O método não é permitido | Adicione o método |
| `The 'Access-Control-Allow-Origin' header has a value '…' that is not equal to the supplied origin.` | Outra origem está liberada: porta errada, `http`, barra no final | Corrija a lista de permissões |
| `The 'Access-Control-Allow-Origin' header contains multiple values '…', but only one is allowed.` | Duas camadas adicionam o cabeçalho, como o app e o nginx | Defina o cabeçalho em um só lugar |
| `Response to preflight request doesn't pass access control check: It does not have HTTP ok status.` | O `OPTIONS` recebeu `401`, `404`, `405` ou `500` | Deixe o `OPTIONS` passar antes da autenticação |
| `Response to preflight request doesn't pass access control check: Redirect is not allowed for a preflight request.` | O `OPTIONS` foi redirecionado, por exemplo para `https` ou para uma página de login | Chame a URL final |

Versões antigas do Chrome adicionavam uma frase sugerindo `mode: 'no-cors'`; o Chromium a removeu em março de 2025 porque ela confundia as pessoas (as perguntas frequentes explicam por quê). O Firefox escreve `Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at … (Reason: CORS header 'Access-Control-Allow-Origin' missing)`.

## Como encontrar a causa real?

Abra as ferramentas de desenvolvedor (F12) e leia a linha na aba **Console** (o nome é o mesmo na interface em português): URL, origem, motivo. Na aba **Network** (**Rede** em português), a requisição bloqueada mostra `CORS error` (`Erro de CORS`) na coluna **Status**, e uma chamada com preflight tem uma entrada `OPTIONS` separada cuja coluna **Initiator** (**Iniciador**) diz `Preflight` (`Simulação` na interface em português). Clique nela para ver os cabeçalhos.

O código de status real passa despercebido com facilidade. No nosso teste, um gateway respondeu `502` sem cabeçalhos CORS: o console mostrou só a linha "No 'Access-Control-Allow-Origin' header", enquanto `curl -i` na mesma URL imprimiu `HTTP/1.1 502 Bad Gateway`. Páginas de erro do nginx, de balanceadores de carga ou de um app que caiu raramente trazem cabeçalhos CORS, então quedas parecem problemas de CORS. A diretiva `add_header` do nginx só se aplica a respostas 200, 201, 204, 206, 301, 302, 303, 304, 307 e 308, a menos que você adicione o parâmetro `always` ([documentação do nginx](https://nginx.org/en/docs/http/ngx_http_headers_module.html#add_header)).

## Como resolver um erro de CORS quando a API é sua?

Envie os cabeçalhos pela própria API, para as origens exatas do seu front-end. Testamos cada trecho em 6 de outubro de 2026 com Node.js 24.11, Express 5.2.1, cors 2.8.6, Vite 8.3.3, Python 3.13, Flask 3.1.3 e Flask-CORS 6.0.5.

### Express

Instale com `npm install express cors` e defina `"type": "module"` no `package.json`.

```js
// server.js: uma API que permite um único front-end (Express 5, cors 2.8)
import express from "express";
import cors from "cors";

const app = express();

app.use(cors({
  origin: ["https://app.example.com", "http://localhost:5173"],
  methods: ["GET", "POST", "PUT", "DELETE"],
  allowedHeaders: ["Content-Type", "Authorization"],
  credentials: true,
  maxAge: 600,
}));
app.use(express.json());

app.get("/api/products", (req, res) => {
  res.json([{ id: 1, name: "Desk lamp" }]);
});

app.post("/api/products", (req, res) => {
  res.status(201).json({ created: req.body });
});

app.listen(3000, () => console.log("API on http://localhost:3000"));
```

Registrado com `app.use()` antes das rotas, o middleware também responde a todo preflight `OPTIONS` ([middleware cors do Express](https://expressjs.com/en/resources/middleware/cors.html)). Escreva as origens exatamente como o navegador as envia, sem barra no final. Outras origens não recebem cabeçalho `Access-Control-Allow-Origin`, e a ideia é justamente essa. Mantenha `credentials: true` só se o front-end enviar cookies com `credentials: "include"`.

Confira o preflight em um terminal:

```bash
curl -i -X OPTIONS http://localhost:3000/api/products \
  -H "Origin: http://localhost:5173" \
  -H "Access-Control-Request-Method: POST" \
  -H "Access-Control-Request-Headers: content-type"
```

As linhas relevantes da nossa execução:

```text
HTTP/1.1 204 No Content
Access-Control-Allow-Origin: http://localhost:5173
Vary: Origin
Access-Control-Allow-Credentials: true
Access-Control-Allow-Methods: GET,POST,PUT,DELETE
Access-Control-Allow-Headers: Content-Type,Authorization
Access-Control-Max-Age: 600
```

O `Vary: Origin` avisa caches e CDNs de que a resposta depende do cabeçalho `Origin`. Com `Origin: https://evil.example`, o mesmo comando retorna `204`, mas sem a linha `Access-Control-Allow-Origin`.

### Flask

Instale com `pip install flask flask-cors`.

```python
# app.py: a mesma API em Flask 3 com Flask-CORS 6
from flask import Flask, jsonify, request
from flask_cors import CORS

app = Flask(__name__)
CORS(
    app,
    resources={r"/api/*": {"origins": ["https://app.example.com", "http://localhost:5173"]}},
    allow_headers=["Content-Type", "Authorization"],
    supports_credentials=True,
    max_age=600,
)

@app.get("/api/products")
def list_products():
    return jsonify([{"id": 1, "name": "Desk lamp"}])

@app.post("/api/products")
def create_product():
    return jsonify({"created": request.get_json()}), 201

if __name__ == "__main__":
    app.run(port=5000)
```

Na nossa execução, o preflight retornou `200` com a origem, os métodos e `Vary: Origin`, e o `POST` da página retornou `201`. Sempre passe uma lista de origens junto com `supports_credentials=True`: sem ela, o Flask-CORS 6.0.5 devolveu para nós uma origem qualquer, `https://evil.example`, com `Access-Control-Allow-Credentials: true`.

### nginx, gateways e CDNs

Se um proxy reverso define os cabeçalhos no lugar do app, dê a cada `add_header` o parâmetro `always`, para que as respostas de erro também os tragam, e deixe só uma camada defini-los. Nunca copie qualquer `Origin` recebido para a resposta enquanto permite credenciais: qualquer site poderia então ler os dados dos seus usuários com os cookies deles. Compare as origens com uma lista fixa.

## Desenvolvimento local: deixe o servidor de desenvolvimento encaminhar a API

Em desenvolvimento, a correção mais simples é colocar a API na mesma origem. O servidor de desenvolvimento do Vite pode encaminhar todo caminho que começa com `/api`:

```js
// vite.config.js: em desenvolvimento, /api passa pelo Vite até a API
import { defineConfig } from "vite";

export default defineConfig({
  server: {
    port: 5173,
    proxy: {
      "/api": {
        target: "http://localhost:3000",
        changeOrigin: true,
      },
    },
  },
});
```

O front-end chama `fetch("/api/products")` sem host. O navegador vê a própria origem, então nenhuma verificação de CORS acontece, e o Vite repassa a requisição para a porta 3000; no nosso teste, o `GET` retornou `200` e o `POST` `201`. O `changeOrigin` coloca o destino no cabeçalho `Host`. O webpack-dev-server oferece o mesmo com `devServer.proxy`. Em produção, sirva o front-end e a API sob uma mesma origem pelo seu servidor web, ou mantenha a configuração de CORS acima.

## Quando a API não é sua: chame-a pelo seu back-end

Uma API de terceiros sem cabeçalhos CORS normalmente foi feita para servidores, muitas vezes porque a chave secreta dela nunca pode chegar a um navegador. Adicione uma rota ao seu back-end: o navegador chama a sua origem, e o seu servidor chama a API.

```js
// relay.js: o seu back-end chama a API de terceiros; o navegador só fala com você
import express from "express";

const app = express();
const PARTNER_URL = "https://api.partner.example/v1/products";

app.get("/api/partner-products", async (req, res) => {
  try {
    const r = await fetch(PARTNER_URL, {
      headers: { Authorization: `Bearer ${process.env.PARTNER_API_KEY}` },
      signal: AbortSignal.timeout(10_000),
    });
    res.status(r.status).type(r.headers.get("content-type") ?? "application/json");
    res.send(await r.text());
  } catch (err) {
    res.status(502).json({ error: "partner API unreachable", detail: err.cause?.code ?? err.name });
  }
});

app.listen(8080, () => console.log("relay on http://localhost:8080"));
```

Contra um substituto local da API parceira sem cabeçalhos CORS, a rota retornou o JSON com `200`. Sirva a rota a partir da origem da página; a chave fica em uma variável de ambiente do servidor. Cabeçalhos, corpos e autenticação com `fetch` e Axios no lado do servidor estão explicados em [cURL em JavaScript](/pt-br/blog/curl-in-javascript).

Mantenha o destino fixo. Uma rota que busca qualquer coisa que chegue em `?url=` transforma o seu servidor em um proxy aberto que qualquer pessoa pode usar, inclusive contra endereços internos que só o seu servidor alcança. Os termos de uso e os limites de taxa da API continuam valendo: a chamada mudou de lugar, as regras não.

## Por que proxies CORS públicos são um risco

Um proxy CORS público busca uma URL por você e adiciona `Access-Control-Allow-Origin: *` à resposta. O erro some, mas:

- O operador vê a URL completa, todos os cabeçalhos (inclusive chaves de API e tokens) e a resposta.
- Ele pode alterar a resposta, e a sua página vai confiar nela.
- As requisições dos seus usuários passam por uma empresa com a qual você não tem nenhum acordo.
- Os limites de taxa e as quedas dele passam a ser seus.

## CORS e web scraping: por que o seu script em Python ou Node.js nunca vê esse erro

O CORS só existe em navegadores. Chamamos a mesma API que o navegador havia bloqueado, desta vez pelo Node.js 24:

```js
// A mesma requisição pelo Node.js: sem navegador, sem verificação de CORS
const r = await fetch("http://localhost:3001/api/products");
console.log(r.status, r.headers.get("access-control-allow-origin"), await r.text());
```

```text
200 null [{"id":1,"name":"Desk lamp"}]
```

Nenhum cabeçalho `Access-Control-Allow-Origin`, e o Node.js lê a resposta mesmo assim; o Requests do Python e o curl se comportam da mesma forma. Se você tenta coletar dados com `fetch()` no console do navegador ou em um app de front-end, o erro de CORS está dizendo que esse trabalho pertence ao código do lado do servidor. Qual linguagem combina com ele é o que comparamos em [Extração de dados: JavaScript ou Python?](/pt-br/blog/web-scraping-javascript-vs-python). As regras do site continuam valendo ali: siga o `robots.txt`, mantenha o ritmo de requisições baixo e use a API oficial quando ela existir.

Um proxy não resolve um erro de CORS. O navegador compara a origem da página com o cabeçalho `Access-Control-Allow-Origin`, e o endereço IP não faz parte dessa verificação; então um proxy residencial, um proxy de datacenter ou uma VPN não mudam nada. Proxies pertencem ao cliente HTTP do seu código do lado do servidor, como mostramos em [Como usar proxy no Node.js](/pt-br/blog/nodejs-proxy).

Para coletas permitidas que precisam de resultados locais em muitos países e cidades, os [Proxies residenciais](https://proxynet.io/pt-br/residential-proxy) enviam as requisições por conexões residenciais, com segmentação por país e cidade.

Para grandes volumes de requisições a APIs públicas e páginas pouco protegidas, os [Proxies de datacenter](https://proxynet.io/pt-br/datacenter-proxy) oferecem endereços IPv4 dedicados com tráfego sem cota.

## Quem encontra erros de CORS?

- Desenvolvedores front-end cujo app e cuja API rodam em portas diferentes.
- Aplicações de página única (SPAs) que chamam uma API de terceiros direto do navegador.
- Equipes que movem uma API para um subdomínio como `api.example.com`, ou para `https`.
- Front-ends gerados por IA que chamam uma API diretamente, às vezes com a chave dentro da página.

## Erros comuns

- **Adicionar `Access-Control-Allow-Origin` à requisição.** É um cabeçalho de resposta; no `fetch()` ele vira um cabeçalho personalizado que dispara um preflight.
- **Usar `mode: "no-cors"`.** A resposta volta opaca: status `0`, corpo ilegível.
- **Uma extensão "allow CORS" ou a segurança web desativada.** Isso muda só o seu próprio navegador, e uma extensão assim pode ler as páginas em que roda.
- **Responder `*` enquanto o front-end envia cookies.** O navegador rejeita essa combinação.
- **`app.options("*", cors())` no Express 5.** O app para na inicialização com `PathError [TypeError]: Missing parameter name at index 1: *`; `app.use(cors())` já responde aos preflights.
- **Middleware de autenticação que rejeita `OPTIONS`.** Preflights não levam token, então falham com "It does not have HTTP ok status".

## Guia de decisão

| Situação | O que fazer |
|---|---|
| Front-end e API em portas diferentes no desenvolvimento | Proxy do servidor de desenvolvimento (`server.proxy` no Vite) |
| A sua API, chamada pelo seu front-end | Permita as origens exatas; responda ao `OPTIONS` |
| O front-end envia cookies | Origem exata, `Access-Control-Allow-Credentials: true`, `Vary: Origin` |
| Um cabeçalho ou método "is not allowed" | Adicione em `Access-Control-Allow-Headers` ou `-Methods` |
| O erro aparece depois de um deploy ou sob carga | Confira o status real com curl; `always` no nginx |
| Uma API de terceiros sem cabeçalhos CORS | Chame pelo seu back-end, com a chave no servidor |
| Coletar dados de outros sites | Código no lado do servidor; ali o CORS não se aplica |

## Perguntas frequentes

### Por que o Postman ou o curl funcionam enquanto o navegador mostra um erro de CORS?

Só navegadores aplicam o CORS. O servidor envia a mesma resposta para qualquer cliente, e só o navegador verifica `Access-Control-Allow-Origin` antes de entregá-la a um script. Um curl que funciona prova que a API está no ar, não que um navegador pode ler a resposta.

### Por que recebo um erro de CORS no localhost?

Uma porta diferente é uma origem diferente: `localhost:5173` e `localhost:3000` são duas origens na mesma máquina. Permita a origem do front-end na API ou use o proxy do servidor de desenvolvimento. Além disso, desde o Chrome 142, um site público que chama `localhost` ou um dispositivo da sua rede doméstica precisa da sua permissão; se você recusar, o console informa um espaço de endereços `loopback` ou `local` negado.

### O mode: "no-cors" resolve um erro de CORS?

Não. A requisição é enviada, mas o navegador devolve uma resposta opaca; no nosso teste o status foi `0`, o tipo `opaque` e o corpo ilegível. Só serve para requisições cuja resposta você nunca vai ler.

### O CORS é um recurso de segurança para a minha API?

Não. Ele impede que um site leia os dados de outro site com os cookies de um visitante, mas qualquer pessoa ainda pode chamar a sua API com um script. Proteja a API com autenticação, autorização e limites de taxa.

### Um proxy ou uma VPN resolvem um erro de CORS?

Não. O navegador compara a origem da página com o cabeçalho `Access-Control-Allow-Origin` da resposta; o endereço IP não faz parte da verificação.

### Por que só a minha requisição POST falha enquanto o GET funciona?

O `POST` provavelmente precisa de um preflight por causa de um corpo JSON, de um cabeçalho `Authorization` ou de um cabeçalho personalizado. Se a API não responde corretamente à requisição `OPTIONS`, o navegador nunca envia o `POST`. Procure a entrada `OPTIONS` na aba Network (Rede) ou envie o preflight com curl.

## Em resumo

Um erro de CORS é o navegador se recusando a entregar ao seu script a resposta de outra origem; a requisição normalmente chegou ao servidor. Leia o motivo no console, confira o status real com curl e corrija o servidor: origens exatas, uma resposta ao `OPTIONS`, os métodos e cabeçalhos certos, `Vary: Origin`. Use o proxy do servidor de desenvolvimento enquanto desenvolve e o seu próprio back-end para APIs que você não controla. A coleta de dados pertence ao código do lado do servidor, onde o CORS não se aplica; os tipos de proxy para esse trabalho estão na nossa página de [serviços de proxy](/pt-br/proxy).
