esp32 intermediate 30 min

ESP32: push readings into InfluxDB over HTTP

Write ESP32 sensor readings straight into a self-hosted InfluxDB over HTTP with line protocol. Time-series storage on your Pi, graphed by Grafana, no cloud in the middle.

Code available for: ESP32 ArduinoArduino C
Published Sep 22, 2026

You have InfluxDB and Grafana running on the Pi (the raspberry-pi-influxdb-grafana tutorial covers the server side, the token, and the bucket named home). This tutorial is the device side: an ESP32 that pushes BME280 readings into that InfluxDB with a plain HTTP POST. No MQTT, no broker process to babysit, no client library. Line protocol over HTTP is one of the simplest thing-to-database writes in all of IoT.

The trap: the write fails with HTTP 401 or 404 and people assume the ESP32 code is wrong. It is almost always the URL. InfluxDB 2.x wants /api/v2/write?org=...&bucket=..., with a Token header, and the org and bucket must match what you typed during server setup exactly (e.g. org=home when you created the org as home). One typo and you spend an hour recompiling for nothing.

What you need

Needed

  • ESP32 dev board
  • A BME280 module for the example (any sensor works; swap the read
  • A Pi (or any Linux box) with InfluxDB 2.x running, reachable on
  • The API token from the InfluxDB setup (the
  • 4 jumper wires and a breadboard

Nice to have

  • A soldering iron and solder (only if you solder the header pins yourself)
  • Helping hands or a vise to hold the board while you work
  • An anti-static wristband (cheap insurance for the ESP32’s pins)

Wiring

Wire key: VCC3.3VGNDSDAGPIOSCL
BME280ESP32
VCC3.3V
GNDGND
SDAGPIO 21
SCLGPIO 22

The stock ESP32 I2C pins. Nothing exotic here; the interesting part of this tutorial is all in the HTTP write.

Install

In the Arduino IDE: Sketch >> Include Library >> Manage Libraries >> search Adafruit BME280 Library (pulls in the Adafruit Unified Sensor dependency; install that too when the IDE asks). The HTTP client is built into the ESP32 core, so there is no MQTT broker, no extra protocol library, nothing else.

The code

Line protocol is one text line per measurement: measurement,tag=value field=value timestamp. Without a timestamp the server stamps arrival time, which is what you want 95% of the time.

#include <WiFi.h>
#include <HTTPClient.h>
#include <Wire.h>
#include <Adafruit_BME280.h>

const char* WIFI_SSID   = "your-wifi-ssid";
const char* WIFI_PASS   = "your-wifi-password";
const char* INFLUX_HOST = "192.168.1.50";        // the Pi
const char* INFLUX_ORG  = "home";
const char* INFLUX_BUCKET = "home";
const char* INFLUX_TOKEN  = "your-long-influxdb-token";

Adafruit_BME280 bme;

String writeUrl() {
  return String("http://") + INFLUX_HOST +
         ":8086/api/v2/write?org=" + INFLUX_ORG +
         "&bucket=" + INFLUX_BUCKET + "&precision=s";
}

bool influxWrite(const String& line) {
  HTTPClient http;
  http.begin(writeUrl());
  http.addHeader("Content-Type", "text/plain; charset=utf-8");
  http.addHeader("Authorization", String("Token ") + INFLUX_TOKEN);
  int code = http.POST(line);
  http.end();
  if (code != 204) {          // 204 No Content is Influx's success code
    Serial.printf("Write failed, HTTP %d\n", code);
    return false;
  }
  return true;
}

void setup() {
  Serial.begin(115200);
  Wire.begin(21, 22);
  if (!bme.begin(0x76)) {
    Serial.println("BME280 not found. Check wiring/address.");
    while (true) delay(100);
  }
  WiFi.begin(WIFI_SSID, WIFI_PASS);
  while (WiFi.status() != WL_CONNECTED) { delay(300); }
  Serial.print("IP: ");
  Serial.println(WiFi.localIP());
}

void loop() {
  static uint32_t last = 0;
  if (millis() - last < 60000) return;   // one reading per minute
  last = millis();

  float t = bme.readTemperature();
  float h = bme.readHumidity();
  float p = bme.readPressure() / 100.0F;

  // line protocol: measurement, tags, then fields, space-separated
  String line = String("home_sensor,room=desk,device=esp32-1 ") +
    "temperature=" + String(t, 2) + "," +
    "humidity="    + String(h, 2) + "," +
    "pressure="    + String(p, 2);

  if (influxWrite(line)) {
    Serial.println("Wrote: " + line);
  }
}

Upload, then check it landed. In the Influx UI (http://pi-ip:8086): Explore >> pick the bucket home >> you should see the home_sensor measurement with three fields. Or just query the API:

curl -G "http://pi-ip:8086/api/v2/query?org=home" \
  -H "Authorization: Token your-long-influxdb-token" \
  -H "Accept: application/csv" \
  --data-urlencode 'from(bucket:"home") |> range(start: -10m)'

HTTP, not HTTPS, on a trusted LAN is a normal choice here. If your Pi is reachable from the wider network, do two things: give the token read-write scope only, and consider putting the Pi behind WireGuard instead of port-forwarding (the wireguard tutorial covers that).

Batch writes (the version I actually run)

One HTTP request per reading is fine at once-a-minute rates. When a device reports every few seconds, buffer lines in RAM and flush a batch. The endpoint accepts multiple lines separated by \n in one POST, which cuts the request count by the batch size.

String batch = "";
int pending = 0;

void queueReading(float t, float h, float p) {
  batch += String("home_sensor,room=desk ") +
           "temperature=" + String(t, 2) + "," +
           "humidity=" + String(h, 2) + "\n";
  if (++pending >= 10) {          // flush every 10 readings
    HTTPClient http;
    http.begin(writeUrl());
    http.addHeader("Authorization", String("Token ") + INFLUX_TOKEN);
    http.POST(batch);
    http.end();
    batch = "";
    pending = 0;
  }
}

Keep batches under about 5 KB and flush before deep sleep (unwritten lines die with the RAM).

What you learned

  • Line protocol is measurement,tags fields: one text line per point, POSTed to /api/v2/write.
  • The Token header carries the API token; 204 means success.
  • Tags go in the line and make Grafana filtering free (e.g. room=desk lets one dashboard show every room).
  • Batch multiple lines in one POST to cut request overhead.

When something breaks

  • HTTP 401. The token is wrong, missing the Token prefix, or scoped to a different org. The header must be exactly Authorization: Token <value>.
  • HTTP 404 or 422. The org or bucket name in the URL does not match the server. Re-check against Explore in the Influx UI (e.g. org=home fails if the org is actually myhome).
  • HTTP 429 or writes slow down over time. You are writing too fast for the Pi’s SD card. Raise the interval to 30 s or more, batch, and set a retention policy so the card is not writing forever.
  • Data appears but Grafana shows nothing. The Grafana data source token usually has read scope while you created it before the bucket. Check the data source’s token, not the ESP32’s.
  • Readings jump around wildly. You put the reading value in a tag instead of a field. Tags are strings and indexes; fields are the numbers. Swap them and the graphs behave.

What to build next

  • Read the raspberry-pi-influxdb-grafana tutorial end to end if you have not yet: this device write is half of that stack.
  • The MQTT publish-subscribe tutorial is the alternative feed: MQTT broker in the middle when many devices write to many consumers at once.
  • The web server sensor dashboard tutorial pairs with this as the device’s own local dashboard while Influx handles history.
  • The book IoT with ESP32 walks the full device-to-dashboard arc with retention and alerting included.