The Short Answer: Does Arduino HTTPClient Use CORS?
The direct answer is no, the Arduino HTTPClient library does not use, enforce, or even understand CORS (Cross-Origin Resource Sharing). CORS is strictly a web browser security mechanism designed to enforce the Same-Origin Policy. Microcontrollers like the ESP32 or Arduino do not have a browser rendering engine; they open raw TCP sockets, send plaintext or TLS-encrypted HTTP strings, and read the byte stream back.
If your ESP32 is failing to fetch data from an API and you suspect a CORS issue, you are likely misinterpreting a server-side rejection. When a browser makes a cross-origin request, it sends an Origin header, and the server replies with Access-Control-Allow-Origin. The ESP32 HTTPClient does not send an Origin header by default, nor does it care if the server omits the CORS headers in the response. It simply reads the HTTP status code.
Most makers encounter "CORS errors" in one of two ways:
1. They test their ESP32's API endpoint using a browser-based frontend (like a React app), and the browser throws a CORS error because the ESP32 web server isn't sending the right headers.
2. The ESP32 is fetching from a cloud API, gets an
HTTP 403 Forbidden or Connection Refused, and the developer assumes it's a CORS block because the same URL works perfectly in their Chrome browser.
To prove this and give you a robust starting point, let's build a secure HTTPS API fetcher using the ESP32, complete with the exact error handling needed to diagnose these "CORS-like" failures.
Project Build: Secure ESP32 API Fetcher
This build targets the ESP32 DevKit V1 (ESP32-WROOM-32 module). We will use WiFiClientSecure to connect to a public JSON API, handling TLS handshakes and HTTP status codes properly. We will also map an I2C OLED display to show the connection status locally.
Parts List
- MCU: ESP32 DevKit V1 (30-pin or 38-pin variant, ESP32-WROOM-32)
- Display: 0.96" I2C OLED (SSD1306 driver, 128x64)
- Wiring: Breadboard and female-to-female jumper wires
- Power: Micro-USB cable or 5V 2A USB power supply
Pin Mapping Table
| Component | Pin Label | ESP32 GPIO | Notes |
|---|---|---|---|
| SSD1306 OLED | VCC | 3V3 | Do not use 5V; the ESP32 I2C bus is 3.3V logic. |
| SSD1306 OLED | GND | GND | Common ground required. |
| SSD1306 OLED | SCL | GPIO 22 | Default I2C clock pin for ESP32. |
| SSD1306 OLED | SDA | GPIO 21 | Default I2C data pin for ESP32. |
| Onboard LED | Anode | GPIO 2 | Used for connection status indication. |
Complete Compilable Code
This code requires the Adafruit SSD1306 and Adafruit GFX libraries installed via the Arduino Library Manager. It targets the ESP32 board package (v2.0.x or v3.0.x).
#include <WiFi.h>
#include <HTTPClient.h>
#include <WiFiClientSecure.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
// --- Pin Definitions ---
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
#define OLED_RESET -1
#define SCREEN_ADDRESS 0x3C
#define LED_PIN 2
// --- Network Credentials ---
const char* ssid = "YOUR_WIFI_SSID";
const char* password = "YOUR_WIFI_PASSWORD";
// --- API Target ---
const char* apiEndpoint = "https://api.coindesk.com/v1/bpi/currentprice.json";
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
void setup() {
Serial.begin(115200);
pinMode(LED_PIN, OUTPUT);
if(!display.begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS)) {
Serial.println(F("SSD1306 allocation failed"));
for(;;); // Halt
}
display.clearDisplay();
display.setTextColor(SSD1306_WHITE);
display.setTextSize(1);
WiFi.begin(ssid, password);
display.setCursor(0,0);
display.print("Connecting WiFi...");
display.display();
int attempts = 0;
while (WiFi.status() != WL_CONNECTED && attempts < 40) {
delay(500);
attempts++;
}
if (WiFi.status() == WL_CONNECTED) {
digitalWrite(LED_PIN, HIGH);
display.clearDisplay();
display.setCursor(0,0);
display.print("WiFi Connected!");
display.display();
delay(1000);
} else {
display.clearDisplay();
display.setCursor(0,0);
display.print("WiFi FAILED!");
display.display();
}
}
void loop() {
if (WiFi.status() == WL_CONNECTED) {
fetchApiData();
}
delay(15000); // Fetch every 15 seconds
}
void fetchApiData() {
WiFiClientSecure *client = new WiFiClientSecure;
if(!client){
Serial.println("Unable to create secure client");
return;
}
// Bypass root CA validation for this demo.
// In production, use client->setCACert(rootCACertificate);
client->setInsecure();
HTTPClient http;
http.begin(*client, apiEndpoint);
// Crucial: Add a User-Agent. Many APIs block default ESP32 agents.
http.addHeader("User-Agent", "ESP32HTTPClient/FluxBuild");
http.addHeader("Accept", "application/json");
Serial.print("[HTTPS] GET...\n");
int httpCode = http.GET();
if (httpCode > 0) {
Serial.printf("[HTTPS] GET... code: %d\n", httpCode);
if (httpCode == HTTP_CODE_OK) {
String payload = http.getString();
Serial.println("Payload received:");
Serial.println(payload.substring(0, 150) + "...");
display.clearDisplay();
display.setCursor(0,0);
display.println("HTTP 200 OK");
display.println("Data received.");
display.display();
} else {
Serial.printf("[HTTPS] GET failed, error: %s\n", http.errorToString(httpCode).c_str());
displayError(httpCode);
}
} else {
Serial.printf("[HTTPS] GET... failed, error: %s\n", http.errorToString(httpCode).c_str());
displayError(httpCode);
}
http.end();
delete client;
}
void displayError(int code) {
display.clearDisplay();
display.setCursor(0,0);
display.print("HTTP Error: ");
display.println(code);
display.display();
}
Debugging "CORS-Like" Failures on the ESP32
When the code above fails, makers often blame CORS. Here are the ranked causes for API rejections on microcontrollers, complete with the exact error strings you will see in the Serial Monitor.
1. Missing or Blocked User-Agent (HTTP 403)
Exact Error String: [HTTPS] GET... code: 403 or HTTP Error code: 403.
The Reality: Cloudflare, AWS WAF, and custom Nginx configurations frequently block requests that lack a standard browser User-Agent header. The ESP32's default agent might be flagged as a bot. This is not CORS; it is bot-mitigation.
The Fix: Always explicitly set a recognizable User-Agent, as shown in the code above: http.addHeader("User-Agent", "ESP32HTTPClient/FluxBuild");.
2. TLS Handshake Failures (Connection Level)
Exact Error String: [HTTP-Client][end] too less data, handshake failed, or HTTPC_ERROR_CONNECTION_REFUSED.
The Reality: The server requires a modern TLS 1.2/1.3 handshake, but the ESP32's root CA bundle is outdated, or the server's certificate chain is incomplete. Browsers have built-in mechanisms to fetch missing intermediate certificates; the ESP32 does not.
The Fix: For quick prototyping, use client->setInsecure(). For production, download the server's root CA in PEM format and pass it via client->setCACert(rootCACertificate).
3. Server-Side IP Blocking or Rate Limiting
Exact Error String: Connection refused or HTTPC_ERROR_CONNECTION_FAILED.
The Reality: If you are polling an API every 2 seconds from a residential IP, the API provider's firewall will silently drop your TCP SYN packets. It looks like a network failure, but it's an intentional block.
The Fix: Increase your polling interval (e.g., delay(15000)) and check the API documentation for rate limits.
- Verify the User-Agent: Did you explicitly add an
User-Agentheader? If not, add one. - Check the HTTP Status Code: Is it returning 200, 403, or 401? A 4xx code means the server received the request and rejected it (authentication or WAF), proving the connection is fine.
- Inspect the TLS Certificate: If the connection drops before an HTTP code is returned, use
client->setInsecure()to isolate whether the failure is network routing or TLS validation.
Extending and Simplifying the Build
Depending on your project constraints, you may need to scale this architecture up or down.
How to Simplify (Drop the OLED and TLS)
If you are hitting memory limits on a smaller board (like an ESP8266 NodeMCU) or talking to a local server:
- Remove the Display: Delete the Adafruit library includes and rely entirely on
Serial.println(). This frees up roughly 15KB of flash and significant RAM. - Use Plain HTTP: If your local server doesn't support HTTPS, swap
WiFiClientSecurefor the standardWiFiClient. Change your URL tohttp://...and remove theclient->setInsecure()line. This drastically reduces RAM overhead, as TLS handshakes require ~25KB of heap memory on the ESP32.
How to Extend (JSON Parsing and Authentication)
Raw strings are useless for IoT dashboards. To make this production-ready:
- Parse the Payload: Install the ArduinoJson library. Use
JsonDocument doc;anddeserializeJson(doc, payload)to extract specific variables without using memory-heavyStringmanipulations. - Add Bearer Tokens: If your API requires authentication, add the header before calling
http.GET():http.addHeader("Authorization", "Bearer YOUR_TOKEN_HERE");.
Frequently Asked Questions
Does the ESP32 HTTPClient send an Origin header?
No. By default, the Arduino HTTPClient only sends Host, User-Agent, Connection, and Accept-Encoding. It does not send an Origin or Referer header. If a server strictly requires an Origin header to process the request (common in poorly configured GraphQL endpoints), you must add it manually using http.addHeader("Origin", "https://yourdomain.com");.
Why does my browser show a CORS error when talking to my ESP32 web server?
This is the reverse scenario. If you host a web server on the ESP32 (using WebServer.h or ESPAsyncWebServer) and try to fetch data from it using JavaScript in Chrome, the browser will block the response. The ESP32 doesn't know what CORS is, so it isn't sending the required Access-Control-Allow-Origin: * header. To fix this, add server.sendHeader("Access-Control-Allow-Origin", "*"); in your ESP32 route handler before sending the response.
Can I force the Arduino HTTPClient to bypass server-side CORS?
You don't need to. Because the ESP32 is not a browser, the server's CORS headers are irrelevant to it. If the server is actively blocking your ESP32, it is doing so via a Web Application Firewall (WAF), IP reputation check, or missing User-Agent—not CORS. Focus on mimicking a standard browser's request headers rather than trying to "bypass" CORS.
How do I fix the "[HTTP-Client][end] too less data" error?
This exact error string usually means the TCP connection was established, but the server closed the socket before sending a complete HTTP response, or the ESP32 ran out of heap memory while buffering the payload. First, check your available heap using ESP.getFreeHeap() before the request. If it's below 30KB, you likely have a memory leak. Second, ensure the server isn't using chunked transfer encoding in a way the older HTTPClient library struggles to parse; try adding http.addHeader("Accept-Encoding", "identity"); to force the server to send uncompressed, unchunked data.






