ESP32 Sync Time with NTP Server

This project demonstrates how to synchronize an ESP32 with an online NTP (Network Time Protocol) server. The ESP32 connects to WiFi, queries an NTP server for the current date and time, and updates its internal clock automatically.

How It Works

NTP — Network Time Protocol — lets a connected device fetch the current time from an internet time server. For a WiFi board this removes the need for a battery-backed RTC entirely: the time comes from the network at boot and is periodically refreshed.

The protocol accounts for network delay. The client records when it sent the request and when the reply arrived, and the server includes its own timestamps, so the round-trip can be halved and subtracted. Over a typical internet connection this yields accuracy of tens of milliseconds — far better than any crystal oscillator.

NTP delivers UTC, not local time. Converting to local time requires a timezone offset and, in most of the world, daylight-saving rules that change twice a year. The ESP32 core includes POSIX timezone support, so a string such as GMT0BST,M3.5.0/1,M10.5.0 handles the transitions automatically.

Once synchronised, the board's internal RTC keeps time between refreshes. That internal clock drifts — typically seconds per day — which is why periodic resynchronisation matters for long-running devices.

Components Needed

  • ESP32
  • WiFi Connection
  • USB Cable
  • ESP32

Wiring to the ESP32

No additional wiring is required — the board needs only power and a WiFi connection. Add a display such as an SSD1306 on GPIO21 (SDA) and GPIO22 (SCL) by default if the clock should be readable without a computer.

Because the board depends on the network, consider what should happen if WiFi is unavailable at boot. A device that blocks forever waiting to connect is a poor design for anything unattended.

For a clock that must survive internet outages, pair NTP with a DS3231. NTP sets the RTC when available; the RTC carries the time when it is not.

RequirementDetailNotes
WiFi network2.4 GHz with internet accessNTP needs to reach the internet
NTP serverpool.ntp.orgUse a regional pool where possible
Timezone stringPOSIX TZ formatHandles daylight saving automatically
Serial monitor115200 baudFor status output

Build and Upload

Upload the sketch using the Arduino IDE and open the Serial Monitor to view synchronized time updates.

Example Code

Fetching time over NTP with timezone handling and periodic resync. Upload it with the board set to ESP32 and open the Serial Monitor at 115200 baud.

Fetching time over NTP with timezone handling and periodic resync
#include <WiFi.h>
#include <time.h>

const char* WIFI_SSID     = "YOUR_SSID";
const char* WIFI_PASSWORD = "YOUR_PASSWORD";

const char* NTP_SERVER = "pool.ntp.org";
// POSIX timezone string — this example is UK time with BST transitions
const char* TZ_INFO = "GMT0BST,M3.5.0/1,M10.5.0";

const unsigned long RESYNC_MS = 3600000UL;   // re-sync hourly
unsigned long lastSync = 0;

bool syncTime() {
  configTzTime(TZ_INFO, NTP_SERVER);

  struct tm t;
  if (!getLocalTime(&t, 10000)) {            // wait up to 10 s
    Serial.println("NTP sync failed");
    return false;
  }
  Serial.println("time synchronised");
  lastSync = millis();
  return true;
}

void setup() {
  Serial.begin(115200);
  WiFi.begin(WIFI_SSID, WIFI_PASSWORD);

  Serial.print("connecting");
  while (WiFi.status() != WL_CONNECTED) { delay(400); Serial.print("."); }
  Serial.println(" connected");

  syncTime();
}

void loop() {
  if (millis() - lastSync > RESYNC_MS) syncTime();

  struct tm t;
  if (getLocalTime(&t)) {
    char buf[40];
    strftime(buf, sizeof(buf), "%A %d %B %Y  %H:%M:%S", &t);
    Serial.println(buf);
  }
  delay(1000);
}

Applications

A ntp synchronised clock turns up in a wide range of projects. These are the uses where it is the right choice rather than a compromise:

  • Clocks and displays that never need setting
  • Data loggers with accurate timestamps for later correlation
  • Scheduled automation — lights and heating on real local time
  • Event timestamping for security and access systems
  • Any device where a user setting the time is an unacceptable burden

Working with the ESP32

The ESP32 is built around the ESP32 and runs on 3.3 V logic with 520 KB of SRAM and 4 MB typically of program flash. These details change how this circuit is wired and what the sketch can do, so they are worth stating plainly before you build.

The ESP32 is a 3.3 V part; 5 V sensor outputs need a divider or level shifter before they touch a GPIO.

Its ADC is 12-bit, so readings span 0–4095 rather than the 0–1023 an Arduino returns.

ADC2 pins stop working once WiFi is active — keep analog sensors on ADC1 (GPIO32–GPIO39) in any connected project.

GPIO34–GPIO39 are input-only and have no internal pull-ups.

ESP32 characteristicValueWhy it matters here
Logic voltage3.3 VSensor outputs above this level need a divider or level shifter
ADC resolution12-bit (0–4095)Sets how finely an analog reading can be resolved
Analog inputsADC1 on GPIO32–GPIO39 and ADC2 on several other pinsDetermines how many analog sensors can share the board
PWM outputsany GPIO through the LEDC peripheralNeeded for brightness, speed and tone control
I²C pinsGPIO21 (SDA) and GPIO22 (SCL) by defaultFixed by hardware — wiring copied from another board may not match
Interrupt pinsany GPIORequired for counting fast or asynchronous events
Serialthree hardware UARTsMonitor runs at 115200 baud by default

Troubleshooting

Most problems with this module fall into a handful of categories. Work through these before suspecting the part itself:

  • Time reads as 1970 — the sync failed and the clock is at the Unix epoch. Check internet access, not just WiFi association.
  • The time is correct but off by hours — the timezone string is wrong, or UTC is being displayed directly.
  • Daylight saving does not change — a fixed offset is being used instead of a POSIX TZ string with transition rules.
  • Sync works at boot but the clock drifts over days — periodic resynchronisation is missing.
  • It hangs at startup when the router is down — the connect loop blocks forever. Add a timeout and continue without time.
  • The sketch compiles but the board resets or behaves erratically — a 5 V module output is being driven into a 3.3 V pin. Measure the signal before connecting it.
  • Readings differ from an Arduino tutorial for the same part — the 12-bit ADC returns 0–4095, not 0–1023, so any constant copied from an Uno example needs rescaling.

Taking It Further on the ESP32

Once the basic reading works, where you go next depends very much on which board you are using. These are the directions that suit the ESP32 specifically:

With WiFi and Bluetooth built in, the natural extension is to publish readings over MQTT or expose them as a BLE characteristic that a phone app can subscribe to directly.

The ESP32’s dual cores let acquisition and networking run independently. Pin a FreeRTOS task reading the sensor to one core and the WiFi stack to the other, and network delays stop disturbing sample timing.

Deep sleep with the RTC timer brings average current into the microamp range, and the ULP co-processor can keep watching an input while the main cores stay asleep — practical for battery sensors that must last months.

Notes and Practical Limits

Always verify the sync succeeded before using the time. getLocalTime() returning false means the clock is meaningless, and logging timestamps from 1970 is a common and avoidable error.

Use a regional NTP pool such as uk.pool.ntp.org or asia.pool.ntp.org rather than the global pool. The reduced round-trip improves accuracy and spreads load across the volunteer server network.