An ESP32 NTP Stratum 1 Time Server for your home network
This project was born with a simple goal: create a low-budget, Stratum 1 NTP time server solution with no need for periodic Linux OS updates. Rather just set it up and let it do its job.
It uses a GNSS (North America & Europe) receiver as its time source, making it a true Stratum 1 server — meaning it gets its time directly from satellites rather than from another NTP server. Every device on your home network can then synchronize to it for highly accurate local time.
A full write-up of the original (version 1) project is available on Hackaday.io.
-
New microcontroller board — now built around the WaveShare ESP32-P4-ETH instead of the Olimex ESP32-PoE-ISO. The ESP32-P4 is more modern and faster than the ESP32-WROOM-32/32E used in v1.
-
More accurate NTP responses — PPS (Pulse Per Second) pin support is now fully exploited to discipline the time reference, delivering sub-millisecond accuracy when using a PPS-capable GPS module.
-
Over-the-Ethernet OTA updates — firmware can now be updated over the Ethernet connection directly from VS Code, with on-screen progress shown on the LCD.
-
LCD is now optional — in v1 an LCD 2004 screen was required; in v2 it is entirely optional.
-
Optional up time / reset button — carry-over from v1, still supported.
-
Updated 3D printed case — the enclosure files have been updated for the new WaveShare ESP32-P4-ETH board.
-
Built with ESP-IDF — rewritten from the ground up in C++ on ESP-IDF v5.5.3 using FreeRTOS tasks.
-
NVS-backed GPS state — the GPS module identity and baud rate are persisted across reboots so following the initial setup startup speed is quick.
-
Broader GNSS module support — still works great with the SparkFun MAX-M10S (recommended), but now also supports lower-cost, generic GNSS modules, even those (although not recommended) that do not expose a PPS pin.
-
Ability to set a custom MAC Address — (version 2.2) allows the use of the default ESP32-P4's MAC address or for a custom MAC address to be set.
-
Ability to set a Static IP Address — (version 2.3) allows the use of a DHCP assigned or static IP address.
-
Handles greater throughput and number of concurrent requests (version 2.4.1).
-
IPv6 support - (version 2.5) IPv6 support has been added.
-
MQTT publishing — (version 2.5) optional MQTT publishing of time server stats is now available.
For more information see this document
-
Improved GNSS Satellite lock and PPS discipline tracking and recovery - (version 2.5) with returned results being tagged as Stratum 16 (undefined) until a lost lock and/or failed PPS discipline is recovered.
-
Conditional compilation - (version 2.5) when optional features are disabled in the settings their associated code will now be excluded from the executable.
The source code for Version 1 (Arduino / PlatformIO) remains available at: https://github.com/roblatour/ESP32TimeServer/releases/tag/v1.0.0.0
| Qty | Item |
|---|---|
| 1 | WaveShare ESP32-P4-ETH development board (with or without optional PoE Hat) 1 |
| 1 | GPS/GNSS module Recommeded: SparkFun GNSS Receiver Breakout - MAX-M10S (Qwiic) 1. Note: while some lower-cost generic modules (AliExpress - NEO8M) are also supported, those including a PPS pin are strongly preferred |
| 1 | GPS/GNSS antenna with SMA connector (SparkFun GPS/GNSS Magnetic Mount Antenna - 3m (SMA)) 1 |
| 1 | (Optional) 4×20 I²C LCD display with HD44780 controller with PCF8574 I²C backpack (AliExpress) 1 |
| 1 | (Optional) Momentary push button for displaying up time and triggering a reset (AliExpress) 1 |
| 1 | (Optional) USB C extension cable (with right angle end) (AliExpress) 1 + two M3*8 |
| — | Miscellaneous: Ethernet cable, female dupont connection wires, small 4" .1" zip ties, solder 2 |
| — | A PoE-capable switch, PoE injector, or USB-C power supply and USB-C cable |
⚠️ WARNING — Do NOT power the ESP32-P4-ETH via both its USB-C connector and a PoE powered Ethernet cable at the same time. Powering from both simultaneously may damage the ESP32-P4-ETH board, POE switch, or device providing USB power. Either power source alone is sufficient to power the board, GPS module, and LCD screen.
1 (Optional) 3D Printable Case designed with these specific components in mind (for more information see below)
2 (Optional) To cut down on wiring a PCB can be used. One designed
to work within this project's 3D printed enclosure is available in the
PCB folder. If your interested, please review the Readme.md file in
that folder for more information.
GPS module → ESP32-P4-ETH (mandatory)
| GPS pin | ESP32-P4-ETH pin |
|---|---|
| GND | GND |
| VCC | 3V3 |
| PPS | GPIO 20 |
| TXD | GPIO 21 (RX) |
| RXD | GPIO 22 (TX) |
Important: the above pin selections have changed in version 2.4 for better forward and backward compatibility between revisions of the ESP32-P4 chip. However, if an earlier version of the software has been working fine for you then you may be able to use the old pin specifications if you want to avoid rewiring your project. Please see the ESP32TimeServerSettings.h file for more information.
LCD 2004 (HD44780 + PCF8574 I²C backpack) → ESP32-P4-ETH (optional)
| LCD pin | ESP32-P4-ETH pin |
|---|---|
| GND | GND |
| VCC | 3V3 |
| SDA | GPIO 8 |
| SLC/SCL | GPIO 7 |
Up time / Reset button → ESP32-P4-ETH (optional)
| Button | ESP32-P4-ETH pin |
|---|---|
| One terminal | GND |
| The other terminal | GPIO 3 |
Refer to the pin definitions in
main/ESP32TimeServerSettings.h.
A downloadable 3D printable enclosure designed for use the WaveShare
ESP32-P4-ETH (with or without the POE hat) + Sparkfun GPS boards is included in
the 3D printable case/ folder. Both .stl
(print-ready) and .f3z (Fusion 360 editable source) files are provided so you
can tweak the design to suit your needs.
Note: the 3D printable enclosure files also include the needed models for version 1 of this project which used the Olimex ESP32-PoE-ISO Rev. B.
This release is built using:
(It has been tested to compile on 5.5.3+ and 6.0.2+)
The
SparkFun u-blox GNSS Arduino Library v3
(v3.1.14) is included as a git submodule in
3rdparty/SparkFun_u-blox_GNSS_v3. Since
it is an Arduino library and is not published to the ESP-IDF Component Registry,
it is manually created during Setup Step 1.
The remaining dependencies are managed automatically via the ESP-IDF Component
Manager (declared in main/idf_component.yml):
esp-idf-lib/hd44780— LCD driveresp-idf-lib/pcf8574— I²C LCD backpack driverespressif/arduino-esp32— Arduino compatibility layer for OTA and serial
Note: The ESP-IDF Component Manager resolves these dependencies dynamically at build time. A
dependencies.lockfile is generated locally on first build but is intentionally not committed because it is git-ignored.
Create a folder for this project, clone this repository into it, and update the SparkFun library within it. For example:
c:mkdir c:\temp\ESP32TimeServerProjectcd \temp\ESP32TimeserverProjectgit clone --recursive https://github.com/roblatour/ESP32TimeServercd ESP32TimeServergit submodule update --init --recursiveAll user-configurable settings — GPIO pins, GPS options, LCD options, button support, time zone, etc. — are centralized in:
main/ESP32TimeServerSettings.h
Edit this file to match your desired hardware setup before building.
Note: The settings file indicates whether certain features are enabled. These include support for an Uptime/Restart Button, a Liquid Crystal Display, MQTT reporting, and Over the Ethernet Updates. Please also note, that if a Liquid Crystal Display is not connected,
LIQUID_CRYSTAL_DISPLAY_ENABLEDmust be0(disabled) or a critical runtime error will occur.
The ESP32-P4 has different revisions; the build steps are detailed below for each.
del sdkconfig-
For current ESP32-P4 modules at revision 3.1 and above:
idf.py -D SDKCONFIG_DEFAULTS="sdkconfig.defaults;config/esp32p4_rev_v3_1.defaults" set-target esp32p4 build -
For ESP32-P4 modules at revision 3.0:
idf.py -D SDKCONFIG_DEFAULTS="sdkconfig.defaults;config/esp32p4_rev_v3_0.defaults" set-target esp32p4 build -
For older ESP32-P4 modules with revisions prior to version 3.0:
idf.py -D SDKCONFIG_DEFAULTS="sdkconfig.defaults;config/esp32p4_rev_pre_v3.defaults" set-target esp32p4 build
call C:\esp\v5.5.3\esp-idf\export.batidf.py -p COMx flash monitorNote: Replace
COMxwith your actual serial port. For example: COM6Important: Do not use
--forceto flash an image to an earlier revision ESP32-P4.
Following the initial flash, OTA updates can be performed if
OTE_UPDATES_ENABLED was set to 1 (Enabled) for the initial build and flash.
A VS Code task "ESP-IDF: OTA Upload over Ethernet" is included in
.vscode/tasks.json. It builds the firmware and deploys it to the device over
Ethernet using espota.py, so no USB cable is needed after the first flash.
Alternatively, use the terminal command in
Useful Power Shell Commands.txt
in the misc folder.
Please see Setup.md
Once running, and with your Network and System changes setup, you can validate accuracy at https://time.is.
You're also welcome to use this open source software (also developed by me) to stress test your time server:
https://github.com/roblatour/TimeServerStressTest
This project is released under the MIT License — see the LICENSE file for details.
To help support this project, or to just say thanks, you're welcome to 'buy me a coffee'.
Copyright © 2026 Rob Latour

